Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1341)

Side by Side Diff: appengine/config_service/ui/bower_components/web-component-tester/browser.js

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /**
2 * @license
3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
6 * The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
7 * Code distributed by Google as part of the polymer project is also
8 * subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
9 */
10
11 /**
12 * THIS FILE IS AUTOMATICALLY GENERATED!
13 * To make changes to browser.js, please edit the source files in the repo's `br owser/` directory!
14 */
15
16 (function () {
17 'use strict';
18
19 /**
20 * @param {function()} callback A function to call when the active web component
21 * frameworks have loaded.
22 */
23 function whenFrameworksReady(callback) {
24 debug('whenFrameworksReady');
25 var done = function() {
26 debug('whenFrameworksReady done');
27 callback();
28 };
29
30 // If webcomponents script is in the document, wait for WebComponentsReady.
31 if (window.WebComponents && !window.WebComponents.ready) {
32 debug('WebComponentsReady?');
33 window.addEventListener('WebComponentsReady', function wcReady() {
34 window.removeEventListener('WebComponentsReady', wcReady);
35 debug('WebComponentsReady');
36 done();
37 });
38 } else {
39 done();
40 }
41 }
42
43 /**
44 * @param {number} count
45 * @param {string} kind
46 * @return {string} '<count> <kind> tests' or '<count> <kind> test'.
47 */
48 function pluralizedStat(count, kind) {
49 if (count === 1) {
50 return count + ' ' + kind + ' test';
51 } else {
52 return count + ' ' + kind + ' tests';
53 }
54 }
55
56 /**
57 * @param {string} path The URI of the script to load.
58 * @param {function} done
59 */
60 function loadScript(path, done) {
61 var script = document.createElement('script');
62 script.src = path;
63 if (done) {
64 script.onload = done.bind(null, null);
65 script.onerror = done.bind(null, 'Failed to load script ' + script.src);
66 }
67 document.head.appendChild(script);
68 }
69
70 /**
71 * @param {string} path The URI of the stylesheet to load.
72 * @param {function} done
73 */
74 function loadStyle(path, done) {
75 var link = document.createElement('link');
76 link.rel = 'stylesheet';
77 link.href = path;
78 if (done) {
79 link.onload = done.bind(null, null);
80 link.onerror = done.bind(null, 'Failed to load stylesheet ' + link.href);
81 }
82 document.head.appendChild(link);
83 }
84
85 /**
86 * @param {...*} var_args Logs values to the console when the `debug`
87 * configuration option is true.
88 */
89 function debug(var_args) {
90 if (!get('verbose')) return;
91 var args = [window.location.pathname];
92 args.push.apply(args, arguments);
93 (console.debug || console.log).apply(console, args);
94 }
95
96 // URL Processing
97
98 /**
99 * @param {string} url
100 * @return {{base: string, params: string}}
101 */
102 function parseUrl(url) {
103 var parts = url.match(/^(.*?)(?:\?(.*))?$/);
104 return {
105 base: parts[1],
106 params: getParams(parts[2] || ''),
107 };
108 }
109
110 /**
111 * Expands a URL that may or may not be relative to `base`.
112 *
113 * @param {string} url
114 * @param {string} base
115 * @return {string}
116 */
117 function expandUrl(url, base) {
118 if (!base) return url;
119 if (url.match(/^(\/|https?:\/\/)/)) return url;
120 if (base.substr(base.length - 1) !== '/') {
121 base = base + '/';
122 }
123 return base + url;
124 }
125
126 /**
127 * @param {string=} opt_query A query string to parse.
128 * @return {!Object<string, !Array<string>>} All params on the URL's query.
129 */
130 function getParams(opt_query) {
131 var query = typeof opt_query === 'string' ? opt_query : window.location.search ;
132 if (query.substring(0, 1) === '?') {
133 query = query.substring(1);
134 }
135 // python's SimpleHTTPServer tacks a `/` on the end of query strings :(
136 if (query.slice(-1) === '/') {
137 query = query.substring(0, query.length - 1);
138 }
139 if (query === '') return {};
140
141 var result = {};
142 query.split('&').forEach(function(part) {
143 var pair = part.split('=');
144 if (pair.length !== 2) {
145 console.warn('Invalid URL query part:', part);
146 return;
147 }
148 var key = decodeURIComponent(pair[0]);
149 var value = decodeURIComponent(pair[1]);
150
151 if (!result[key]) {
152 result[key] = [];
153 }
154 result[key].push(value);
155 });
156
157 return result;
158 }
159
160 /**
161 * Merges params from `source` into `target` (mutating `target`).
162 *
163 * @param {!Object<string, !Array<string>>} target
164 * @param {!Object<string, !Array<string>>} source
165 */
166 function mergeParams(target, source) {
167 Object.keys(source).forEach(function(key) {
168 if (!(key in target)) {
169 target[key] = [];
170 }
171 target[key] = target[key].concat(source[key]);
172 });
173 }
174
175 /**
176 * @param {string} param The param to return a value for.
177 * @return {?string} The first value for `param`, if found.
178 */
179 function getParam(param) {
180 var params = getParams();
181 return params[param] ? params[param][0] : null;
182 }
183
184 /**
185 * @param {!Object<string, !Array<string>>} params
186 * @return {string} `params` encoded as a URI query.
187 */
188 function paramsToQuery(params) {
189 var pairs = [];
190 Object.keys(params).forEach(function(key) {
191 params[key].forEach(function(value) {
192 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
193 });
194 });
195 return (pairs.length > 0) ? ('?' + pairs.join('&')) : '';
196 }
197
198 /**
199 * @param {!Location|string} location
200 * @return {string}
201 */
202 function basePath(location) {
203 return (location.pathname || location).match(/^.*\//)[0];
204 }
205
206 /**
207 * @param {!Location|string} location
208 * @param {string} basePath
209 * @return {string}
210 */
211 function relativeLocation(location, basePath) {
212 var path = location.pathname || location;
213 if (path.indexOf(basePath) === 0) {
214 path = path.substring(basePath.length);
215 }
216 return path;
217 }
218
219 /**
220 * @param {!Location|string} location
221 * @return {string}
222 */
223 function cleanLocation(location) {
224 var path = location.pathname || location;
225 if (path.slice(-11) === '/index.html') {
226 path = path.slice(0, path.length - 10);
227 }
228 return path;
229 }
230
231 /**
232 * Like `async.parallelLimit`, but our own so that we don't force a dependency
233 * on downstream code.
234 *
235 * @param {!Array<function(function(*))>} runners Runners that call their given
236 * Node-style callback when done.
237 * @param {number|function(*)} limit Maximum number of concurrent runners.
238 * (optional).
239 * @param {?function(*)} done Callback that should be triggered once all runners
240 * have completed, or encountered an error.
241 */
242 function parallel(runners, limit, done) {
243 if (typeof limit !== 'number') {
244 done = limit;
245 limit = 0;
246 }
247 if (!runners.length) return done();
248
249 var called = false;
250 var total = runners.length;
251 var numActive = 0;
252 var numDone = 0;
253
254 function runnerDone(error) {
255 if (called) return;
256 numDone = numDone + 1;
257 numActive = numActive - 1;
258
259 if (error || numDone >= total) {
260 called = true;
261 done(error);
262 } else {
263 runOne();
264 }
265 }
266
267 function runOne() {
268 if (limit && numActive >= limit) return;
269 if (!runners.length) return;
270 numActive = numActive + 1;
271 runners.shift()(runnerDone);
272 }
273 runners.forEach(runOne);
274 }
275
276 /**
277 * Finds the directory that a loaded script is hosted on.
278 *
279 * @param {string} filename
280 * @return {string?}
281 */
282 function scriptPrefix(filename) {
283 var scripts = document.querySelectorAll('script[src*="' + filename + '"]');
284 if (scripts.length !== 1) return null;
285 var script = scripts[0].src;
286 return script.substring(0, script.indexOf(filename));
287 }
288
289 var util = Object.freeze({
290 whenFrameworksReady: whenFrameworksReady,
291 pluralizedStat: pluralizedStat,
292 loadScript: loadScript,
293 loadStyle: loadStyle,
294 debug: debug,
295 parseUrl: parseUrl,
296 expandUrl: expandUrl,
297 getParams: getParams,
298 mergeParams: mergeParams,
299 getParam: getParam,
300 paramsToQuery: paramsToQuery,
301 basePath: basePath,
302 relativeLocation: relativeLocation,
303 cleanLocation: cleanLocation,
304 parallel: parallel,
305 scriptPrefix: scriptPrefix
306 });
307
308 // TODO(thedeeno): Consider renaming subsuite. IIRC, childRunner is entirely
309 // distinct from mocha suite, which tripped me up badly when trying to add
310 // plugin support. Perhaps something like 'batch', or 'bundle'. Something that
311 // has no mocha correlate. This may also eliminate the need for root/non-root
312 // suite distinctions.
313
314 /**
315 * A Mocha suite (or suites) run within a child iframe, but reported as if they
316 * are part of the current context.
317 */
318 function ChildRunner(url, parentScope) {
319 var urlBits = parseUrl(url);
320 mergeParams(
321 urlBits.params, getParams(parentScope.location.search));
322 delete urlBits.params.cli_browser_id;
323
324 this.url = urlBits.base + paramsToQuery(urlBits.params);
325 this.parentScope = parentScope;
326
327 this.state = 'initializing';
328 }
329
330 // ChildRunners get a pretty generous load timeout by default.
331 ChildRunner.loadTimeout = 60000;
332
333 // We can't maintain properties on iframe elements in Firefox/Safari/???, so we
334 // track childRunners by URL.
335 ChildRunner._byUrl = {};
336
337 /**
338 * @return {ChildRunner} The `ChildRunner` that was registered for this window.
339 */
340 ChildRunner.current = function() {
341 return ChildRunner.get(window);
342 };
343
344 /**
345 * @param {!Window} target A window to find the ChildRunner of.
346 * @param {boolean} traversal Whether this is a traversal from a child window.
347 * @return {ChildRunner} The `ChildRunner` that was registered for `target`.
348 */
349 ChildRunner.get = function(target, traversal) {
350 var childRunner = ChildRunner._byUrl[target.location.href];
351 if (childRunner) return childRunner;
352 if (window.parent === window) { // Top window.
353 if (traversal) {
354 console.warn('Subsuite loaded but was never registered. This most likely i s due to wonky history behavior. Reloading...');
355 window.location.reload();
356 }
357 return null;
358 }
359 // Otherwise, traverse.
360 return window.parent.WCT._ChildRunner.get(target, true);
361 };
362
363 /**
364 * Loads and runs the subsuite.
365 *
366 * @param {function} done Node-style callback.
367 */
368 ChildRunner.prototype.run = function(done) {
369 debug('ChildRunner#run', this.url);
370 this.state = 'loading';
371 this.onRunComplete = done;
372
373 this.iframe = document.createElement('iframe');
374 this.iframe.src = this.url;
375 this.iframe.classList.add('subsuite');
376
377 var container = document.getElementById('subsuites');
378 if (!container) {
379 container = document.createElement('div');
380 container.id = 'subsuites';
381 document.body.appendChild(container);
382 }
383 container.appendChild(this.iframe);
384
385 // let the iframe expand the URL for us.
386 this.url = this.iframe.src;
387 ChildRunner._byUrl[this.url] = this;
388
389 this.timeoutId = setTimeout(
390 this.loaded.bind(this, new Error('Timed out loading ' + this.url)), ChildR unner.loadTimeout);
391
392 this.iframe.addEventListener('error',
393 this.loaded.bind(this, new Error('Failed to load document ' + this.url)));
394
395 this.iframe.contentWindow.addEventListener('DOMContentLoaded', this.loaded.bin d(this, null));
396 };
397
398 /**
399 * Called when the sub suite's iframe has loaded (or errored during load).
400 *
401 * @param {*} error The error that occured, if any.
402 */
403 ChildRunner.prototype.loaded = function(error) {
404 debug('ChildRunner#loaded', this.url, error);
405
406 // Not all targets have WCT loaded (compatiblity mode)
407 if (this.iframe.contentWindow.WCT) {
408 this.share = this.iframe.contentWindow.WCT.share;
409 }
410
411 if (error) {
412 this.signalRunComplete(error);
413 this.done();
414 }
415 };
416
417 /**
418 * Called in mocha/run.js when all dependencies have loaded, and the child is
419 * ready to start running tests
420 *
421 * @param {*} error The error that occured, if any.
422 */
423 ChildRunner.prototype.ready = function(error) {
424 debug('ChildRunner#ready', this.url, error);
425 if (this.timeoutId) {
426 clearTimeout(this.timeoutId);
427 }
428 if (error) {
429 this.signalRunComplete(error);
430 this.done();
431 }
432 };
433
434 /** Called when the sub suite's tests are complete, so that it can clean up. */
435 ChildRunner.prototype.done = function done() {
436 debug('ChildRunner#done', this.url, arguments);
437
438 // make sure to clear that timeout
439 this.ready();
440 this.signalRunComplete();
441
442 if (!this.iframe) return;
443 // Be safe and avoid potential browser crashes when logic attempts to interact
444 // with the removed iframe.
445 setTimeout(function() {
446 this.iframe.parentNode.removeChild(this.iframe);
447 this.iframe = null;
448 }.bind(this), 1);
449 };
450
451 ChildRunner.prototype.signalRunComplete = function signalRunComplete(error) {
452 if (!this.onRunComplete) return;
453 this.state = 'complete';
454 this.onRunComplete(error);
455 this.onRunComplete = null;
456 };
457
458 /**
459 * The global configuration state for WCT's browser client.
460 */
461 var _config = {
462 /**
463 * `.js` scripts to be loaded (synchronously) before WCT starts in earnest.
464 *
465 * Paths are relative to `scriptPrefix`.
466 */
467 environmentScripts: [
468 'stacky/browser.js',
469 'async/lib/async.js',
470 'lodash/lodash.js',
471 'mocha/mocha.js',
472 'chai/chai.js',
473 'sinonjs/sinon.js',
474 'sinon-chai/lib/sinon-chai.js',
475 'accessibility-developer-tools/dist/js/axs_testing.js'
476 ],
477
478 environmentImports: [
479 'test-fixture/test-fixture.html'
480 ],
481
482 /** Absolute root for client scripts. Detected in `setup()` if not set. */
483 root: null,
484
485 /** By default, we wait for any web component frameworks to load. */
486 waitForFrameworks: true,
487
488 /** Alternate callback for waiting for tests.
489 * `this` for the callback will be the window currently running tests.
490 */
491 waitFor: null,
492
493 /** How many `.html` suites that can be concurrently loaded & run. */
494 numConcurrentSuites: 1,
495
496 /** Whether `console.error` should be treated as a test failure. */
497 trackConsoleError: true,
498
499 /** Configuration passed to mocha.setup. */
500 mochaOptions: {
501 timeout: 10 * 1000
502 },
503
504 /** Whether WCT should emit (extremely verbose) debugging log messages. */
505 verbose: false,
506 };
507
508 /**
509 * Merges initial `options` into WCT's global configuration.
510 *
511 * @param {Object} options The options to merge. See `browser/config.js` for a
512 * reference.
513 */
514 function setup(options) {
515 var childRunner = ChildRunner.current();
516 if (childRunner) {
517 _deepMerge(_config, childRunner.parentScope.WCT._config);
518 // But do not force the mocha UI
519 delete _config.mochaOptions.ui;
520 }
521
522 if (options && typeof options === 'object') {
523 _deepMerge(_config, options);
524 }
525
526 if (!_config.root) {
527 // Sibling dependencies.
528 var root = scriptPrefix('browser.js');
529 _config.root = basePath(root.substr(0, root.length - 1));
530 if (!_config.root) {
531 throw new Error('Unable to detect root URL for WCT sources. Please set WCT .root before including browser.js');
532 }
533 }
534 }
535
536 /**
537 * Retrieves a configuration value.
538 *
539 * @param {string} key
540 * @return {*}
541 */
542 function get(key) {
543 return _config[key];
544 }
545
546 // Internal
547
548 function _deepMerge(target, source) {
549 Object.keys(source).forEach(function (key) {
550 if (target[key] !== null && typeof target[key] === 'object' && !Array.isArra y(target[key])) {
551 _deepMerge(target[key], source[key]);
552 } else {
553 target[key] = source[key];
554 }
555 });
556 }
557
558 var htmlSuites$1 = [];
559 var jsSuites$1 = [];
560
561 // We process grep ourselves to avoid loading suites that will be filtered.
562 var GREP = getParam('grep');
563 // work around mocha bug (https://github.com/mochajs/mocha/issues/2070)
564 if (GREP) {
565 GREP = GREP.replace(/\\\./g, '.');
566 }
567
568 /**
569 * Loads suites of tests, supporting both `.js` and `.html` files.
570 *
571 * @param {!Array.<string>} files The files to load.
572 */
573 function loadSuites(files) {
574 files.forEach(function(file) {
575 if (/\.js(\?.*)?$/.test(file)) {
576 jsSuites$1.push(file);
577 } else if (/\.html(\?.*)?$/.test(file)) {
578 htmlSuites$1.push(file);
579 } else {
580 throw new Error('Unknown resource type: ' + file);
581 }
582 });
583 }
584
585 /**
586 * @return {!Array.<string>} The child suites that should be loaded, ignoring
587 * those that would not match `GREP`.
588 */
589 function activeChildSuites() {
590 var subsuites = htmlSuites$1;
591 if (GREP) {
592 var cleanSubsuites = [];
593 for (var i = 0, subsuite; subsuite = subsuites[i]; i++) {
594 if (GREP.indexOf(cleanLocation(subsuite)) !== -1) {
595 cleanSubsuites.push(subsuite);
596 }
597 }
598 subsuites = cleanSubsuites;
599 }
600 return subsuites;
601 }
602
603 /**
604 * Loads all `.js` sources requested by the current suite.
605 *
606 * @param {!MultiReporter} reporter
607 * @param {function} done
608 */
609 function loadJsSuites(reporter, done) {
610 debug('loadJsSuites', jsSuites$1);
611
612 var loaders = jsSuites$1.map(function(file) {
613 // We only support `.js` dependencies for now.
614 return loadScript.bind(util, file);
615 });
616
617 parallel(loaders, done);
618 }
619
620 /**
621 * @param {!MultiReporter} reporter
622 * @param {!Array.<string>} childSuites
623 * @param {function} done
624 */
625 function runSuites(reporter, childSuites, done) {
626 debug('runSuites');
627
628 var suiteRunners = [
629 // Run the local tests (if any) first, not stopping on error;
630 _runMocha.bind(null, reporter),
631 ];
632
633 // As well as any sub suites. Again, don't stop on error.
634 childSuites.forEach(function(file) {
635 suiteRunners.push(function(next) {
636 var childRunner = new ChildRunner(file, window);
637 reporter.emit('childRunner start', childRunner);
638 childRunner.run(function(error) {
639 reporter.emit('childRunner end', childRunner);
640 if (error) reporter.emitOutOfBandTest(file, error);
641 next();
642 });
643 });
644 });
645
646 parallel(suiteRunners, get('numConcurrentSuites'), function(error) {
647 reporter.done();
648 done(error);
649 });
650 }
651
652 /**
653 * Kicks off a mocha run, waiting for frameworks to load if necessary.
654 *
655 * @param {!MultiReporter} reporter Where to send Mocha's events.
656 * @param {function} done A callback fired, _no error is passed_.
657 */
658 function _runMocha(reporter, done, waited) {
659 if (get('waitForFrameworks') && !waited) {
660 var waitFor = (get('waitFor') || whenFrameworksReady).bind(window);
661 waitFor(function() {
662 _fixCustomElements();
663 _runMocha(reporter, done, true);
664 });
665 return;
666 }
667 debug('_runMocha');
668 var mocha = window.mocha;
669 var Mocha = window.Mocha;
670
671 mocha.reporter(reporter.childReporter(window.location));
672 mocha.suite.title = reporter.suiteTitle(window.location);
673 mocha.grep(GREP);
674
675 // We can't use `mocha.run` because it bashes over grep, invert, and friends.
676 // See https://github.com/visionmedia/mocha/blob/master/support/tail.js#L137
677 var runner = Mocha.prototype.run.call(mocha, function(error) {
678 if (document.getElementById('mocha')) {
679 Mocha.utils.highlightTags('code');
680 }
681 done(); // We ignore the Mocha failure count.
682 });
683
684 // Mocha's default `onerror` handling strips the stack (to support really old
685 // browsers). We upgrade this to get better stacks for async errors.
686 //
687 // TODO(nevir): Can we expand support to other browsers?
688 if (navigator.userAgent.match(/chrome/i)) {
689 window.onerror = null;
690 window.addEventListener('error', function(event) {
691 if (!event.error) return;
692 if (event.error.ignore) return;
693 runner.uncaught(event.error);
694 });
695 }
696 }
697 /**
698 * In Chrome57 custom elements in the document might not get upgraded when
699 * there is a high GC https://bugs.chromium.org/p/chromium/issues/detail?id=7016 01
700 * We clone and replace the ones that weren't upgraded.
701 */
702 function _fixCustomElements() {
703 // Bail out if it is not Chrome 57.
704 var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
705 var isM57 = raw && raw[2] === '57';
706 if (!isM57) return;
707
708 var elements = document.body.querySelectorAll('*:not(script):not(style)');
709 var constructors = {};
710 for (var i = 0; i < elements.length; i++) {
711 var el = elements[i];
712 // This child has already been cloned and replaced by its parent, skip it!
713 if (!el.isConnected) continue;
714
715 var tag = el.localName;
716 // Not a custom element!
717 if (tag.indexOf('-') === -1) continue;
718
719 // Memoize correct constructors.
720 constructors[tag] = constructors[tag] || document.createElement(tag).constru ctor;
721 // This one was correctly upgraded.
722 if (el instanceof constructors[tag]) continue;
723
724 debug('_fixCustomElements: found non-upgraded custom element ' + el);
725 var clone = document.importNode(el, true);
726 el.parentNode.replaceChild(clone, el);
727 }
728 }
729
730 // We capture console events when running tests; so make sure we have a
731 // reference to the original one.
732 var console$1 = window.console;
733
734 var FONT = ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-ser if;';
735 var STYLES = {
736 plain: FONT,
737 suite: 'color: #5c6bc0' + FONT,
738 test: FONT,
739 passing: 'color: #259b24' + FONT,
740 pending: 'color: #e65100' + FONT,
741 failing: 'color: #c41411' + FONT,
742 stack: 'color: #c41411',
743 results: FONT + 'font-size: 16px',
744 };
745
746 // I don't think we can feature detect this one...
747 var userAgent = navigator.userAgent.toLowerCase();
748 var CAN_STYLE_LOG = userAgent.match('firefox') || userAgent.match('webkit');
749 var CAN_STYLE_GROUP = userAgent.match('webkit');
750 // Track the indent for faked `console.group`
751 var logIndent = '';
752
753 function log(text, style) {
754 text = text.split('\n').map(function(l) { return logIndent + l; }).join('\n');
755 if (CAN_STYLE_LOG) {
756 console$1.log('%c' + text, STYLES[style] || STYLES.plain);
757 } else {
758 console$1.log(text);
759 }
760 }
761
762 function logGroup(text, style) {
763 if (CAN_STYLE_GROUP) {
764 console$1.group('%c' + text, STYLES[style] || STYLES.plain);
765 } else if (console$1.group) {
766 console$1.group(text);
767 } else {
768 logIndent = logIndent + ' ';
769 log(text, style);
770 }
771 }
772
773 function logGroupEnd() {
774 if (console$1.groupEnd) {
775 console$1.groupEnd();
776 } else {
777 logIndent = logIndent.substr(0, logIndent.length - 2);
778 }
779 }
780
781 function logException(error) {
782 log(error.stack || error.message || error, 'stack');
783 }
784
785 /**
786 * A Mocha reporter that logs results out to the web `console`.
787 *
788 * @param {!Mocha.Runner} runner The runner that is being reported on.
789 */
790 function Console(runner) {
791 Mocha.reporters.Base.call(this, runner);
792
793 runner.on('suite', function(suite) {
794 if (suite.root) return;
795 logGroup(suite.title, 'suite');
796 }.bind(this));
797
798 runner.on('suite end', function(suite) {
799 if (suite.root) return;
800 logGroupEnd();
801 }.bind(this));
802
803 runner.on('test', function(test) {
804 logGroup(test.title, 'test');
805 }.bind(this));
806
807 runner.on('pending', function(test) {
808 logGroup(test.title, 'pending');
809 }.bind(this));
810
811 runner.on('fail', function(test, error) {
812 logException(error);
813 }.bind(this));
814
815 runner.on('test end', function(test) {
816 logGroupEnd();
817 }.bind(this));
818
819 runner.on('end', this.logSummary.bind(this));
820 }
821
822 /** Prints out a final summary of test results. */
823 Console.prototype.logSummary = function logSummary() {
824 logGroup('Test Results', 'results');
825
826 if (this.stats.failures > 0) {
827 log(pluralizedStat(this.stats.failures, 'failing'), 'failing');
828 }
829 if (this.stats.pending > 0) {
830 log(pluralizedStat(this.stats.pending, 'pending'), 'pending');
831 }
832 log(pluralizedStat(this.stats.passes, 'passing'));
833
834 if (!this.stats.failures) {
835 log('test suite passed', 'passing');
836 }
837 log('Evaluated ' + this.stats.tests + ' tests in ' + this.stats.duration + 'ms .');
838 logGroupEnd();
839 };
840
841 /**
842 * @license
843 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
844 * This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
845 * The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
846 * The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
847 * Code distributed by Google as part of the polymer project is also
848 * subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
849 */
850
851 /**
852 * WCT-specific behavior on top of Mocha's default HTML reporter.
853 *
854 * @param {!Mocha.Runner} runner The runner that is being reported on.
855 */
856 function HTML(runner) {
857 var output = document.createElement('div');
858 output.id = 'mocha';
859 document.body.appendChild(output);
860
861 runner.on('suite', function(test) {
862 this.total = runner.total;
863 }.bind(this));
864
865 Mocha.reporters.HTML.call(this, runner);
866 }
867
868 // Woo! What a hack. This just saves us from adding a bunch of complexity around
869 // style loading.
870 var style = document.createElement('style');
871 style.textContent = 'html, body {' +
872 ' position: relative;' +
873 ' height: 100%;' +
874 ' width: 100%;' +
875 ' min-width: 900px;' +
876 '}' +
877 '#mocha, #subsuites {' +
878 ' height: 100%;' +
879 ' position: absolute;' +
880 ' top: 0;' +
881 '}' +
882 '#mocha {' +
883 ' box-sizing: border-box;' +
884 ' margin: 0 !important;' +
885 ' overflow-y: auto;' +
886 ' padding: 60px 20px;' +
887 ' right: 0;' +
888 ' left: 500px;' +
889 '}' +
890 '#subsuites {' +
891 ' -ms-flex-direction: column;' +
892 ' -webkit-flex-direction: column;' +
893 ' display: -ms-flexbox;' +
894 ' display: -webkit-flex;' +
895 ' display: flex;' +
896 ' flex-direction: column;' +
897 ' left: 0;' +
898 ' width: 500px;' +
899 '}' +
900 '#subsuites .subsuite {' +
901 ' border: 0;' +
902 ' width: 100%;' +
903 ' height: 100%;' +
904 '}' +
905 '#mocha .test.pass .duration {' +
906 ' color: #555 !important;' +
907 '}';
908 document.head.appendChild(style);
909
910 var STACKY_CONFIG = {
911 indent: ' ',
912 locationStrip: [
913 /^https?:\/\/[^\/]+/,
914 /\?.*$/,
915 ],
916 filter: function(line) {
917 return line.location.match(/\/web-component-tester\/[^\/]+(\?.*)?$/);
918 },
919 };
920
921 // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36-46
922 var MOCHA_EVENTS = [
923 'start',
924 'end',
925 'suite',
926 'suite end',
927 'test',
928 'test end',
929 'hook',
930 'hook end',
931 'pass',
932 'fail',
933 'pending',
934 'childRunner end'
935 ];
936
937 // Until a suite has loaded, we assume this many tests in it.
938 var ESTIMATED_TESTS_PER_SUITE = 3;
939
940 /**
941 * A Mocha-like reporter that combines the output of multiple Mocha suites.
942 *
943 * @param {number} numSuites The number of suites that will be run, in order to
944 * estimate the total number of tests that will be performed.
945 * @param {!Array.<!Mocha.reporters.Base>} reporters The set of reporters that
946 * should receive the unified event stream.
947 * @param {MultiReporter} parent The parent reporter, if present.
948 */
949 function MultiReporter(numSuites, reporters, parent) {
950 this.reporters = reporters.map(function(reporter) {
951 return new reporter(this);
952 }.bind(this));
953
954 this.parent = parent;
955 this.basePath = parent && parent.basePath || basePath(window.location);
956
957 this.total = numSuites * ESTIMATED_TESTS_PER_SUITE;
958 // Mocha reporters assume a stream of events, so we have to be careful to only
959 // report on one runner at a time...
960 this.currentRunner = null;
961 // ...while we buffer events for any other active runners.
962 this.pendingEvents = [];
963
964 this.emit('start');
965 }
966
967 /**
968 * @param {!Location|string} location The location this reporter represents.
969 * @return {!Mocha.reporters.Base} A reporter-like "class" for each child suite
970 * that should be passed to `mocha.run`.
971 */
972 MultiReporter.prototype.childReporter = function childReporter(location) {
973 var name = this.suiteTitle(location);
974 // The reporter is used as a constructor, so we can't depend on `this` being
975 // properly bound.
976 var self = this;
977 function reporter(runner) {
978 runner.name = name;
979 self.bindChildRunner(runner);
980 }
981 reporter.title = name;
982 return reporter;
983 };
984
985 /** Must be called once all runners have finished. */
986 MultiReporter.prototype.done = function done() {
987 this.complete = true;
988 this.flushPendingEvents();
989 this.emit('end');
990 };
991
992 /**
993 * Emit a top level test that is not part of any suite managed by this reporter.
994 *
995 * Helpful for reporting on global errors, loading issues, etc.
996 *
997 * @param {string} title The title of the test.
998 * @param {*} opt_error An error associated with this test. If falsy, test is
999 * considered to be passing.
1000 * @param {string} opt_suiteTitle Title for the suite that's wrapping the test.
1001 * @param {?boolean} opt_estimated If this test was included in the original
1002 * estimate of `numSuites`.
1003 */
1004 MultiReporter.prototype.emitOutOfBandTest = function emitOutOfBandTest(title, op t_error, opt_suiteTitle, opt_estimated) {
1005 debug('MultiReporter#emitOutOfBandTest(', arguments, ')');
1006 var root = new Mocha.Suite();
1007 root.title = opt_suiteTitle || '';
1008 var test = new Mocha.Test(title, function() {
1009 });
1010 test.parent = root;
1011 test.state = opt_error ? 'failed' : 'passed';
1012 test.err = opt_error;
1013
1014 if (!opt_estimated) {
1015 this.total = this.total + ESTIMATED_TESTS_PER_SUITE;
1016 }
1017
1018 var runner = {total: 1};
1019 this.proxyEvent('start', runner);
1020 this.proxyEvent('suite', runner, root);
1021 this.proxyEvent('test', runner, test);
1022 if (opt_error) {
1023 this.proxyEvent('fail', runner, test, opt_error);
1024 } else {
1025 this.proxyEvent('pass', runner, test);
1026 }
1027 this.proxyEvent('test end', runner, test);
1028 this.proxyEvent('suite end', runner, root);
1029 this.proxyEvent('end', runner);
1030 };
1031
1032 /**
1033 * @param {!Location|string} location
1034 * @return {string}
1035 */
1036 MultiReporter.prototype.suiteTitle = function suiteTitle(location) {
1037 var path = relativeLocation(location, this.basePath);
1038 path = cleanLocation(path);
1039 return path;
1040 };
1041
1042 // Internal Interface
1043
1044 /** @param {!Mocha.runners.Base} runner The runner to listen to events for. */
1045 MultiReporter.prototype.bindChildRunner = function bindChildRunner(runner) {
1046 MOCHA_EVENTS.forEach(function(eventName) {
1047 runner.on(eventName, this.proxyEvent.bind(this, eventName, runner));
1048 }.bind(this));
1049 };
1050
1051 /**
1052 * Evaluates an event fired by `runner`, proxying it forward or buffering it.
1053 *
1054 * @param {string} eventName
1055 * @param {!Mocha.runners.Base} runner The runner that emitted this event.
1056 * @param {...*} var_args Any additional data passed as part of the event.
1057 */
1058 MultiReporter.prototype.proxyEvent = function proxyEvent(eventName, runner, var_ args) {
1059 var extraArgs = Array.prototype.slice.call(arguments, 2);
1060 if (this.complete) {
1061 console.warn('out of order Mocha event for ' + runner.name + ':', eventName, extraArgs);
1062 return;
1063 }
1064
1065 if (this.currentRunner && runner !== this.currentRunner) {
1066 this.pendingEvents.push(arguments);
1067 return;
1068 }
1069 debug('MultiReporter#proxyEvent(', arguments, ')');
1070
1071 // This appears to be a Mocha bug: Tests failed by passing an error to their
1072 // done function don't set `err` properly.
1073 //
1074 // TODO(nevir): Track down.
1075 if (eventName === 'fail' && !extraArgs[0].err) {
1076 extraArgs[0].err = extraArgs[1];
1077 }
1078
1079 if (eventName === 'start') {
1080 this.onRunnerStart(runner);
1081 } else if (eventName === 'end') {
1082 this.onRunnerEnd(runner);
1083 } else {
1084 this.cleanEvent(eventName, runner, extraArgs);
1085 this.emit.apply(this, [eventName].concat(extraArgs));
1086 }
1087 };
1088
1089 /**
1090 * Cleans or modifies an event if needed.
1091 *
1092 * @param {string} eventName
1093 * @param {!Mocha.runners.Base} runner The runner that emitted this event.
1094 * @param {!Array.<*>} extraArgs
1095 */
1096 MultiReporter.prototype.cleanEvent = function cleanEvent(eventName, runner, extr aArgs) {
1097 // Suite hierarchy
1098 if (extraArgs[0]) {
1099 extraArgs[0] = this.showRootSuite(extraArgs[0]);
1100 }
1101
1102 // Normalize errors
1103 if (eventName === 'fail') {
1104 extraArgs[1] = Stacky.normalize(extraArgs[1], STACKY_CONFIG);
1105 }
1106 if (extraArgs[0] && extraArgs[0].err) {
1107 extraArgs[0].err = Stacky.normalize(extraArgs[0].err, STACKY_CONFIG);
1108 }
1109 };
1110
1111 /**
1112 * We like to show the root suite's title, which requires a little bit of
1113 * trickery in the suite hierarchy.
1114 *
1115 * @param {!Mocha.Runnable} node
1116 */
1117 MultiReporter.prototype.showRootSuite = function showRootSuite(node) {
1118 var leaf = node = Object.create(node);
1119 while (node && node.parent) {
1120 var wrappedParent = Object.create(node.parent);
1121 node.parent = wrappedParent;
1122 node = wrappedParent;
1123 }
1124 node.root = false;
1125
1126 return leaf;
1127 };
1128
1129 /** @param {!Mocha.runners.Base} runner */
1130 MultiReporter.prototype.onRunnerStart = function onRunnerStart(runner) {
1131 debug('MultiReporter#onRunnerStart:', runner.name);
1132 this.total = this.total - ESTIMATED_TESTS_PER_SUITE + runner.total;
1133 this.currentRunner = runner;
1134 };
1135
1136 /** @param {!Mocha.runners.Base} runner */
1137 MultiReporter.prototype.onRunnerEnd = function onRunnerEnd(runner) {
1138 debug('MultiReporter#onRunnerEnd:', runner.name);
1139 this.currentRunner = null;
1140 this.flushPendingEvents();
1141 };
1142
1143 /**
1144 * Flushes any buffered events and runs them through `proxyEvent`. This will
1145 * loop until all buffered runners are complete, or we have run out of buffered
1146 * events.
1147 */
1148 MultiReporter.prototype.flushPendingEvents = function flushPendingEvents() {
1149 var events = this.pendingEvents;
1150 this.pendingEvents = [];
1151 events.forEach(function(eventArgs) {
1152 this.proxyEvent.apply(this, eventArgs);
1153 }.bind(this));
1154 };
1155
1156 var ARC_OFFSET = 0; // start at the right.
1157 var ARC_WIDTH = 6;
1158
1159 /**
1160 * A Mocha reporter that updates the document's title and favicon with
1161 * at-a-glance stats.
1162 *
1163 * @param {!Mocha.Runner} runner The runner that is being reported on.
1164 */
1165 function Title(runner) {
1166 Mocha.reporters.Base.call(this, runner);
1167
1168 runner.on('test end', this.report.bind(this));
1169 }
1170
1171 /** Reports current stats via the page title and favicon. */
1172 Title.prototype.report = function report() {
1173 this.updateTitle();
1174 this.updateFavicon();
1175 };
1176
1177 /** Updates the document title with a summary of current stats. */
1178 Title.prototype.updateTitle = function updateTitle() {
1179 if (this.stats.failures > 0) {
1180 document.title = pluralizedStat(this.stats.failures, 'failing');
1181 } else {
1182 document.title = pluralizedStat(this.stats.passes, 'passing');
1183 }
1184 };
1185
1186 /**
1187 * Draws an arc for the favicon status, relative to the total number of tests.
1188 *
1189 * @param {!CanvasRenderingContext2D} context
1190 * @param {number} total
1191 * @param {number} start
1192 * @param {number} length
1193 * @param {string} color
1194 */
1195 function drawFaviconArc(context, total, start, length, color) {
1196 var arcStart = ARC_OFFSET + Math.PI * 2 * (start / total);
1197 var arcEnd = ARC_OFFSET + Math.PI * 2 * ((start + length) / total);
1198
1199 context.beginPath();
1200 context.strokeStyle = color;
1201 context.lineWidth = ARC_WIDTH;
1202 context.arc(16, 16, 16 - ARC_WIDTH / 2, arcStart, arcEnd);
1203 context.stroke();
1204 }
1205
1206 /** Updates the document's favicon w/ a summary of current stats. */
1207 Title.prototype.updateFavicon = function updateFavicon() {
1208 var canvas = document.createElement('canvas');
1209 canvas.height = canvas.width = 32;
1210 var context = canvas.getContext('2d');
1211
1212 var passing = this.stats.passes;
1213 var pending = this.stats.pending;
1214 var failing = this.stats.failures;
1215 var total = Math.max(this.runner.total, passing + pending + failing);
1216 drawFaviconArc(context, total, 0, passing, '#0e9c57');
1217 drawFaviconArc(context, total, passing, pending, '#f3b300');
1218 drawFaviconArc(context, total, pending + passing, failing, '#ff5621');
1219
1220 this.setFavicon(canvas.toDataURL());
1221 };
1222
1223 /** Sets the current favicon by URL. */
1224 Title.prototype.setFavicon = function setFavicon(url) {
1225 var current = document.head.querySelector('link[rel="icon"]');
1226 if (current) {
1227 document.head.removeChild(current);
1228 }
1229
1230 var link = document.createElement('link');
1231 link.rel = 'icon';
1232 link.type = 'image/x-icon';
1233 link.href = url;
1234 link.setAttribute('sizes', '32x32');
1235 document.head.appendChild(link);
1236 };
1237
1238 /**
1239 * @param {CLISocket} socket The CLI socket, if present.
1240 * @param {MultiReporter} parent The parent reporter, if present.
1241 * @return {!Array.<!Mocha.reporters.Base} The reporters that should be used.
1242 */
1243 function determineReporters(socket, parent) {
1244 // Parents are greedy.
1245 if (parent) {
1246 return [parent.childReporter(window.location)];
1247 }
1248
1249 // Otherwise, we get to run wild without any parental supervision!
1250 var reporters = [Title, Console];
1251
1252 if (socket) {
1253 reporters.push(function(runner) {
1254 socket.observe(runner);
1255 });
1256 }
1257
1258 if (htmlSuites$1.length > 0 || jsSuites$1.length > 0) {
1259 reporters.push(HTML);
1260 }
1261
1262 return reporters;
1263 }
1264
1265 /**
1266 * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy.
1267 */
1268 function injectMocha(Mocha) {
1269 _injectPrototype(Console, Mocha.reporters.Base.prototype);
1270 _injectPrototype(HTML, Mocha.reporters.HTML.prototype);
1271 // Mocha doesn't expose its `EventEmitter` shim directly, so:
1272 _injectPrototype(MultiReporter, Object.getPrototypeOf(Mocha.Runner.prototype ));
1273 }
1274
1275 function _injectPrototype(klass, prototype) {
1276 var newPrototype = Object.create(prototype);
1277 // Only support
1278 Object.keys(klass.prototype).forEach(function(key) {
1279 newPrototype[key] = klass.prototype[key];
1280 });
1281
1282 klass.prototype = newPrototype;
1283 }
1284
1285 /**
1286 * Loads all environment scripts ...synchronously ...after us.
1287 */
1288 function loadSync() {
1289 debug('Loading environment scripts:');
1290 var a11ySuite = 'web-component-tester/data/a11ySuite.js';
1291 var scripts = get('environmentScripts');
1292 var a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySui te) > -1;
1293 if (!a11ySuiteWillBeLoaded) {
1294 // wct is running as a bower dependency, load a11ySuite from data/
1295 scripts.push(a11ySuite);
1296 }
1297 scripts.forEach(function(path) {
1298 var url = expandUrl(path, get('root'));
1299 debug('Loading environment script:', url);
1300 // Synchronous load.
1301 document.write('<script src="' + encodeURI(url) + '"></script>'); // jshint ignore:line
1302 });
1303 debug('Environment scripts loaded');
1304
1305 var imports = get('environmentImports');
1306 imports.forEach(function(path) {
1307 var url = expandUrl(path, get('root'));
1308 debug('Loading environment import:', url);
1309 // Synchronous load.
1310 document.write('<link rel="import" href="' + encodeURI(url) + '">'); // jshi nt ignore:line
1311 });
1312 debug('Environment imports loaded');
1313 }
1314
1315 /**
1316 * We have some hard dependencies on things that should be loaded via
1317 * `environmentScripts`, so we assert that they're present here; and do any
1318 * post-facto setup.
1319 */
1320 function ensureDependenciesPresent() {
1321 _ensureMocha();
1322 _checkChai();
1323 }
1324
1325 function _ensureMocha() {
1326 var Mocha = window.Mocha;
1327 if (!Mocha) {
1328 throw new Error('WCT requires Mocha. Please ensure that it is present in WCT .environmentScripts, or that you load it before loading web-component-tester/bro wser.js');
1329 }
1330 injectMocha(Mocha);
1331 // Magic loading of mocha's stylesheet
1332 var mochaPrefix = scriptPrefix('mocha.js');
1333 // only load mocha stylesheet for the test runner output
1334 // Not the end of the world, if it doesn't load.
1335 if (mochaPrefix && window.top === window.self) {
1336 loadStyle(mochaPrefix + 'mocha.css');
1337 }
1338 }
1339
1340 function _checkChai() {
1341 if (!window.chai) {
1342 debug('Chai not present; not registering shorthands');
1343 return;
1344 }
1345
1346 window.assert = window.chai.assert;
1347 window.expect = window.chai.expect;
1348 }
1349
1350 // We may encounter errors during initialization (for example, syntax errors in
1351 // a test file). Hang onto those (and more) until we are ready to report them.
1352 var globalErrors = [];
1353
1354 /**
1355 * Hook the environment to pick up on global errors.
1356 */
1357 function listenForErrors() {
1358 window.addEventListener('error', function(event) {
1359 globalErrors.push(event.error);
1360 });
1361
1362 // Also, we treat `console.error` as a test failure. Unless you prefer not.
1363 var origConsole = console;
1364 var origError = console.error;
1365 console.error = function wctShimmedError() {
1366 origError.apply(origConsole, arguments);
1367 if (get('trackConsoleError')) {
1368 throw 'console.error: ' + Array.prototype.join.call(arguments, ' ');
1369 }
1370 };
1371 }
1372
1373 var interfaceExtensions = [];
1374
1375 /**
1376 * Registers an extension that extends the global `Mocha` implementation
1377 * with new helper methods. These helper methods will be added to the `window`
1378 * when tests run for both BDD and TDD interfaces.
1379 */
1380 function extendInterfaces(helperName, helperFactory) {
1381 interfaceExtensions.push(function() {
1382 var Mocha = window.Mocha;
1383 // For all Mocha interfaces (probably just TDD and BDD):
1384 Object.keys(Mocha.interfaces).forEach(function(interfaceName) {
1385 // This is the original callback that defines the interface (TDD or BDD):
1386 var originalInterface = Mocha.interfaces[interfaceName];
1387 // This is the name of the "teardown" or "afterEach" property for the
1388 // current interface:
1389 var teardownProperty = interfaceName === 'tdd' ? 'teardown' : 'afterEach';
1390 // The original callback is monkey patched with a new one that appends to
1391 // the global context however we want it to:
1392 Mocha.interfaces[interfaceName] = function(suite) {
1393 // Call back to the original callback so that we get the base interface:
1394 originalInterface.apply(this, arguments);
1395 // Register a listener so that we can further extend the base interface:
1396 suite.on('pre-require', function(context, file, mocha) {
1397 // Capture a bound reference to the teardown function as a convenience :
1398 var teardown = context[teardownProperty].bind(context);
1399 // Add our new helper to the testing context. The helper is generated
1400 // by a factory method that receives the context, the teardown functio n
1401 // and the interface name and returns the new method to be added to
1402 // that context:
1403 context[helperName] = helperFactory(context, teardown, interfaceName);
1404 });
1405 };
1406 });
1407 });
1408 }
1409
1410 /**
1411 * Applies any registered interface extensions. The extensions will be applied
1412 * as many times as this function is called, so don't call it more than once.
1413 */
1414 function applyExtensions() {
1415 interfaceExtensions.forEach(function(applyExtension) {
1416 applyExtension();
1417 });
1418 }
1419
1420 extendInterfaces('fixture', function (context, teardown) {
1421
1422 // Return context.fixture if it is already a thing, for backwards
1423 // compatibility with `test-fixture-mocha.js`:
1424 return context.fixture || function fixture(fixtureId, model) {
1425
1426 // Automatically register a teardown callback that will restore the
1427 // test-fixture:
1428 teardown(function () {
1429 document.getElementById(fixtureId).restore();
1430 });
1431
1432 // Find the test-fixture with the provided ID and create it, returning
1433 // the results:
1434 return document.getElementById(fixtureId).create(model);
1435 };
1436 });
1437
1438 /**
1439 * stub
1440 *
1441 * The stub addon allows the tester to partially replace the implementation of
1442 * an element with some custom implementation. Usage example:
1443 *
1444 * beforeEach(function() {
1445 * stub('x-foo', {
1446 * attached: function() {
1447 * // Custom implementation of the `attached` method of element `x-foo`..
1448 * },
1449 * otherMethod: function() {
1450 * // More custom implementation..
1451 * },
1452 * getterSetterProperty: {
1453 * get: function() {
1454 * // Custom getter implementation..
1455 * },
1456 * set: function() {
1457 * // Custom setter implementation..
1458 * }
1459 * },
1460 * // etc..
1461 * });
1462 * });
1463 */
1464 extendInterfaces('stub', function(context, teardown) {
1465
1466 return function stub(tagName, implementation) {
1467 // Find the prototype of the element being stubbed:
1468 var proto = document.createElement(tagName).constructor.prototype;
1469
1470 // For all keys in the implementation to stub with..
1471 var stubs = Object.keys(implementation).map(function(key) {
1472 // Stub the method on the element prototype with Sinon:
1473 return sinon.stub(proto, key, implementation[key]);
1474 });
1475
1476 // After all tests..
1477 teardown(function() {
1478 stubs.forEach(function(stub) {
1479 stub.restore();
1480 });
1481 });
1482 };
1483 });
1484
1485 // replacement map stores what should be
1486 var replacements = {};
1487 var replaceTeardownAttached = false;
1488
1489 /**
1490 * replace
1491 *
1492 * The replace addon allows the tester to replace all usages of one element with
1493 * another element within all Polymer elements created within the time span of
1494 * the test. Usage example:
1495 *
1496 * beforeEach(function() {
1497 * replace('x-foo').with('x-fake-foo');
1498 * });
1499 *
1500 * All annotations and attributes will be set on the placement element the way
1501 * they were set for the original element.
1502 */
1503 extendInterfaces('replace', function (context, teardown) {
1504 return function replace(oldTagName) {
1505 return {
1506 with: function (tagName) {
1507 // Standardizes our replacements map
1508 oldTagName = oldTagName.toLowerCase();
1509 tagName = tagName.toLowerCase();
1510
1511 replacements[oldTagName] = tagName;
1512
1513 // If the function is already a stub, restore it to original
1514 if (document.importNode.isSinonProxy) {
1515 return;
1516 }
1517
1518 if (!Polymer.Element) {
1519 Polymer.Element = function () { };
1520 Polymer.Element.prototype._stampTemplate = function () { };
1521 }
1522
1523 // Keep a reference to the original `document.importNode`
1524 // implementation for later:
1525 var originalImportNode = document.importNode;
1526
1527 // Use Sinon to stub `document.ImportNode`:
1528 sinon.stub(document, 'importNode', function (origContent, deep) {
1529 var templateClone = document.createElement('template');
1530 var content = templateClone.content;
1531 var inertDoc = content.ownerDocument;
1532
1533 // imports node from inertDoc which holds inert nodes.
1534 templateClone.content.appendChild(inertDoc.importNode(origContent, tru e));
1535
1536 // optional arguments are not optional on IE.
1537 var nodeIterator = document.createNodeIterator(content,
1538 NodeFilter.SHOW_ELEMENT, null, true);
1539 var node;
1540
1541 // Traverses the tree. A recently-replaced node will be put next, so
1542 // if a node is replaced, it will be checked if it needs to be
1543 // replaced again.
1544 while (node = nodeIterator.nextNode()) {
1545 var currentTagName = node.tagName.toLowerCase();
1546
1547 if (replacements.hasOwnProperty(currentTagName)) {
1548 currentTagName = replacements[currentTagName];
1549
1550 // find the final tag name.
1551 while (replacements[currentTagName]) {
1552 currentTagName = replacements[currentTagName];
1553 }
1554
1555 // Create a replacement:
1556 var replacement = document.createElement(currentTagName);
1557
1558 // For all attributes in the original node..
1559 for (var index = 0; index < node.attributes.length; ++index) {
1560 // Set that attribute on the replacement:
1561 replacement.setAttribute(
1562 node.attributes[index].name, node.attributes[index].value);
1563 }
1564
1565 // Replace the original node with the replacement node:
1566 node.parentNode.replaceChild(replacement, node);
1567 }
1568 }
1569
1570 return originalImportNode.call(this, content, deep);
1571 });
1572
1573 if (!replaceTeardownAttached) {
1574 // After each test...
1575 teardown(function () {
1576 replaceTeardownAttached = true;
1577 // Restore the stubbed version of `document.importNode`:
1578 if (document.importNode.isSinonProxy) {
1579 document.importNode.restore();
1580 }
1581
1582 // Empty the replacement map
1583 replacements = {};
1584 });
1585 }
1586 }
1587 };
1588 };
1589 });
1590
1591 // Mocha global helpers, broken out by testing method.
1592 //
1593 // Keys are the method for a particular interface; values are their analog in
1594 // the opposite interface.
1595 var MOCHA_EXPORTS = {
1596 // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/tdd.js
1597 tdd: {
1598 'setup': '"before"',
1599 'teardown': '"after"',
1600 'suiteSetup': '"beforeEach"',
1601 'suiteTeardown': '"afterEach"',
1602 'suite': '"describe" or "context"',
1603 'test': '"it" or "specify"',
1604 },
1605 // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/bdd.js
1606 bdd: {
1607 'before': '"setup"',
1608 'after': '"teardown"',
1609 'beforeEach': '"suiteSetup"',
1610 'afterEach': '"suiteTeardown"',
1611 'describe': '"suite"',
1612 'context': '"suite"',
1613 'xdescribe': '"suite.skip"',
1614 'xcontext': '"suite.skip"',
1615 'it': '"test"',
1616 'xit': '"test.skip"',
1617 'specify': '"test"',
1618 'xspecify': '"test.skip"',
1619 },
1620 };
1621
1622 /**
1623 * Exposes all Mocha methods up front, configuring and running mocha
1624 * automatically when you call them.
1625 *
1626 * The assumption is that it is a one-off (sub-)suite of tests being run.
1627 */
1628 function stubInterfaces() {
1629 Object.keys(MOCHA_EXPORTS).forEach(function (ui) {
1630 Object.keys(MOCHA_EXPORTS[ui]).forEach(function (key) {
1631 window[key] = function wrappedMochaFunction() {
1632 _setupMocha(ui, key, MOCHA_EXPORTS[ui][key]);
1633 if (!window[key] || window[key] === wrappedMochaFunction) {
1634 throw new Error('Expected mocha.setup to define ' + key);
1635 }
1636 window[key].apply(window, arguments);
1637 };
1638 });
1639 });
1640 }
1641
1642 // Whether we've called `mocha.setup`
1643 var _mochaIsSetup = false;
1644
1645 /**
1646 * @param {string} ui Sets up mocha to run `ui`-style tests.
1647 * @param {string} key The method called that triggered this.
1648 * @param {string} alternate The matching method in the opposite interface.
1649 */
1650 function _setupMocha(ui, key, alternate) {
1651 var mochaOptions = get('mochaOptions');
1652 if (mochaOptions.ui && mochaOptions.ui !== ui) {
1653 var message = 'Mixing ' + mochaOptions.ui + ' and ' + ui + ' Mocha styles is not supported. ' +
1654 'You called "' + key + '". Did you mean ' + alternate + '?';
1655 throw new Error(message);
1656 }
1657 if (_mochaIsSetup) return;
1658
1659 applyExtensions();
1660 mochaOptions.ui = ui;
1661 mocha.setup(mochaOptions); // Note that the reporter is configured in run.js.
1662 }
1663
1664 var SOCKETIO_ENDPOINT = window.location.protocol + '//' + window.location.host;
1665 var SOCKETIO_LIBRARY = SOCKETIO_ENDPOINT + '/socket.io/socket.io.js';
1666
1667 /**
1668 * A socket for communication between the CLI and browser runners.
1669 *
1670 * @param {string} browserId An ID generated by the CLI runner.
1671 * @param {!io.Socket} socket The socket.io `Socket` to communicate over.
1672 */
1673 function CLISocket(browserId, socket) {
1674 this.browserId = browserId;
1675 this.socket = socket;
1676 }
1677
1678 /**
1679 * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting
1680 * interesting events back to the CLI runner.
1681 */
1682 CLISocket.prototype.observe = function observe(runner) {
1683 this.emitEvent('browser-start', {
1684 url: window.location.toString(),
1685 });
1686
1687 // We only emit a subset of events that we care about, and follow a more
1688 // general event format that is hopefully applicable to test runners beyond
1689 // mocha.
1690 //
1691 // For all possible mocha events, see:
1692 // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36
1693 runner.on('test', function(test) {
1694 this.emitEvent('test-start', {test: getTitles(test)});
1695 }.bind(this));
1696
1697 runner.on('test end', function(test) {
1698 this.emitEvent('test-end', {
1699 state: getState(test),
1700 test: getTitles(test),
1701 duration: test.duration,
1702 error: test.err,
1703 });
1704 }.bind(this));
1705
1706 runner.on('fail', function(test, err) {
1707 // fail the test run if we catch errors outside of a test function
1708 if (test.type !== 'test') {
1709 this.emitEvent('browser-fail', 'Error thrown outside of test function: ' + err.stack);
1710 }
1711 }.bind(this));
1712
1713 runner.on('childRunner start', function(childRunner) {
1714 this.emitEvent('sub-suite-start', childRunner.share);
1715 }.bind(this));
1716
1717 runner.on('childRunner end', function(childRunner) {
1718 this.emitEvent('sub-suite-end', childRunner.share);
1719 }.bind(this));
1720
1721 runner.on('end', function() {
1722 this.emitEvent('browser-end');
1723 }.bind(this));
1724 };
1725
1726 /**
1727 * @param {string} event The name of the event to fire.
1728 * @param {*} data Additional data to pass with the event.
1729 */
1730 CLISocket.prototype.emitEvent = function emitEvent(event, data) {
1731 this.socket.emit('client-event', {
1732 browserId: this.browserId,
1733 event: event,
1734 data: data,
1735 });
1736 };
1737
1738 /**
1739 * Builds a `CLISocket` if we are within a CLI-run environment; short-circuits
1740 * otherwise.
1741 *
1742 * @param {function(*, CLISocket)} done Node-style callback.
1743 */
1744 CLISocket.init = function init(done) {
1745 var browserId = getParam('cli_browser_id');
1746 if (!browserId) return done();
1747 // Only fire up the socket for root runners.
1748 if (ChildRunner.current()) return done();
1749
1750 loadScript(SOCKETIO_LIBRARY, function(error) {
1751 if (error) return done(error);
1752
1753 var socket = io(SOCKETIO_ENDPOINT);
1754 socket.on('error', function(error) {
1755 socket.off();
1756 done(error);
1757 });
1758
1759 socket.on('connect', function() {
1760 socket.off();
1761 done(null, new CLISocket(browserId, socket));
1762 });
1763 });
1764 };
1765
1766 // Misc Utility
1767
1768 /**
1769 * @param {!Mocha.Runnable} runnable The test or suite to extract titles from.
1770 * @return {!Array.<string>} The titles of the runnable and its parents.
1771 */
1772 function getTitles(runnable) {
1773 var titles = [];
1774 while (runnable && !runnable.root && runnable.title) {
1775 titles.unshift(runnable.title);
1776 runnable = runnable.parent;
1777 }
1778 return titles;
1779 }
1780
1781 /**
1782 * @param {!Mocha.Runnable} runnable
1783 * @return {string}
1784 */
1785 function getState(runnable) {
1786 if (runnable.state === 'passed') {
1787 return 'passing';
1788 } else if (runnable.state == 'failed') {
1789 return 'failing';
1790 } else if (runnable.pending) {
1791 return 'pending';
1792 } else {
1793 return 'unknown';
1794 }
1795 }
1796
1797 /**
1798 * @license
1799 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
1800 * This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
1801 * The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
1802 * The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
1803 * Code distributed by Google as part of the polymer project is also
1804 * subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
1805 */
1806
1807 // Make sure that we use native timers, in case they're being stubbed out.
1808 var setInterval = window.setInterval; // jshint ignore:line
1809 var setTimeout$1 = window.setTimeout; // jshint ignore:lin e
1810 var requestAnimationFrame = window.requestAnimationFrame; // jshint ignore:line
1811
1812 /**
1813 * Runs `stepFn`, catching any error and passing it to `callback` (Node-style).
1814 * Otherwise, calls `callback` with no arguments on success.
1815 *
1816 * @param {function()} callback
1817 * @param {function()} stepFn
1818 */
1819 window.safeStep = function safeStep(callback, stepFn) {
1820 var err;
1821 try {
1822 stepFn();
1823 } catch (error) {
1824 err = error;
1825 }
1826 callback(err);
1827 };
1828
1829 /**
1830 * Runs your test at declaration time (before Mocha has begun tests). Handy for
1831 * when you need to test document initialization.
1832 *
1833 * Be aware that any errors thrown asynchronously cannot be tied to your test.
1834 * You may want to catch them and pass them to the done event, instead. See
1835 * `safeStep`.
1836 *
1837 * @param {string} name The name of the test.
1838 * @param {function(?function())} testFn The test function. If an argument is
1839 * accepted, the test will be treated as async, just like Mocha tests.
1840 */
1841 window.testImmediate = function testImmediate(name, testFn) {
1842 if (testFn.length > 0) {
1843 return testImmediateAsync(name, testFn);
1844 }
1845
1846 var err;
1847 try {
1848 testFn();
1849 } catch (error) {
1850 err = error;
1851 }
1852
1853 test(name, function(done) {
1854 done(err);
1855 });
1856 };
1857
1858 /**
1859 * An async-only variant of `testImmediate`.
1860 *
1861 * @param {string} name
1862 * @param {function(?function())} testFn
1863 */
1864 window.testImmediateAsync = function testImmediateAsync(name, testFn) {
1865 var testComplete = false;
1866 var err;
1867
1868 test(name, function(done) {
1869 var intervalId = setInterval(function() {
1870 if (!testComplete) return;
1871 clearInterval(intervalId);
1872 done(err);
1873 }, 10);
1874 });
1875
1876 try {
1877 testFn(function(error) {
1878 if (error) err = error;
1879 testComplete = true;
1880 });
1881 } catch (error) {
1882 err = error;
1883 testComplete = true;
1884 }
1885 };
1886
1887 /**
1888 * Triggers a flush of any pending events, observations, etc and calls you back
1889 * after they have been processed.
1890 *
1891 * @param {function()} callback
1892 */
1893 window.flush = function flush(callback) {
1894 // Ideally, this function would be a call to Polymer.dom.flush, but that doesn 't
1895 // support a callback yet (https://github.com/Polymer/polymer-dev/issues/851),
1896 // ...and there's cross-browser flakiness to deal with.
1897
1898 // Make sure that we're invoking the callback with no arguments so that the
1899 // caller can pass Mocha callbacks, etc.
1900 var done = function done() { callback(); };
1901
1902 // Because endOfMicrotask is flaky for IE, we perform microtask checkpoints
1903 // ourselves (https://github.com/Polymer/polymer-dev/issues/114):
1904 var isIE = navigator.appName == 'Microsoft Internet Explorer';
1905 if (isIE && window.Platform && window.Platform.performMicrotaskCheckpoint) {
1906 var reallyDone = done;
1907 done = function doneIE() {
1908 Platform.performMicrotaskCheckpoint();
1909 setTimeout$1(reallyDone, 0);
1910 };
1911 }
1912
1913 // Everyone else gets a regular flush.
1914 var scope;
1915 if (window.Polymer && window.Polymer.dom && window.Polymer.dom.flush) {
1916 scope = window.Polymer.dom;
1917 } else if (window.Polymer && window.Polymer.flush) {
1918 scope = window.Polymer;
1919 } else if (window.WebComponents && window.WebComponents.flush) {
1920 scope = window.WebComponents;
1921 }
1922 if (scope) {
1923 scope.flush();
1924 }
1925
1926 // Ensure that we are creating a new _task_ to allow all active microtasks to
1927 // finish (the code you're testing may be using endOfMicrotask, too).
1928 setTimeout$1(done, 0);
1929 };
1930
1931 /**
1932 * Advances a single animation frame.
1933 *
1934 * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially
1935 * @param {function()} callback
1936 */
1937 window.animationFrameFlush = function animationFrameFlush(callback) {
1938 flush(function() {
1939 requestAnimationFrame(function() {
1940 flush(callback);
1941 });
1942 });
1943 };
1944
1945 /**
1946 * DEPRECATED: Use `flush`.
1947 * @param {function} callback
1948 */
1949 window.asyncPlatformFlush = function asyncPlatformFlush(callback) {
1950 console.warn('asyncPlatformFlush is deprecated in favor of the more terse flus h()');
1951 return window.flush(callback);
1952 };
1953
1954 /**
1955 *
1956 */
1957 window.waitFor = function waitFor(fn, next, intervalOrMutationEl, timeout, timeo utTime) {
1958 timeoutTime = timeoutTime || Date.now() + (timeout || 1000);
1959 intervalOrMutationEl = intervalOrMutationEl || 32;
1960 try {
1961 fn();
1962 } catch (e) {
1963 if (Date.now() > timeoutTime) {
1964 throw e;
1965 } else {
1966 if (isNaN(intervalOrMutationEl)) {
1967 intervalOrMutationEl.onMutation(intervalOrMutationEl, function() {
1968 waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
1969 });
1970 } else {
1971 setTimeout$1(function() {
1972 waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
1973 }, intervalOrMutationEl);
1974 }
1975 return;
1976 }
1977 }
1978 next();
1979 };
1980
1981 // You can configure WCT before it has loaded by assigning your custom
1982 // configuration to the global `WCT`.
1983 setup(window.WCT);
1984
1985 // Maybe some day we'll expose WCT as a module to whatever module registry you
1986 // are using (aka the UMD approach), or as an es6 module.
1987 var WCT = window.WCT = {};
1988 // A generic place to hang data about the current suite. This object is reported
1989 // back via the `sub-suite-start` and `sub-suite-end` events.
1990 WCT.share = {};
1991 // Until then, we get to rely on it to expose parent runners to their children.
1992 WCT._ChildRunner = ChildRunner;
1993 WCT._config = _config;
1994
1995
1996 // Public Interface
1997
1998 /**
1999 * Loads suites of tests, supporting both `.js` and `.html` files.
2000 *
2001 * @param {!Array.<string>} files The files to load.
2002 */
2003 WCT.loadSuites = loadSuites;
2004
2005
2006 // Load Process
2007
2008 listenForErrors();
2009 stubInterfaces();
2010 loadSync();
2011
2012 // Give any scripts on the page a chance to declare tests and muck with things.
2013 document.addEventListener('DOMContentLoaded', function() {
2014 debug('DOMContentLoaded');
2015
2016 ensureDependenciesPresent();
2017
2018 // We need the socket built prior to building its reporter.
2019 CLISocket.init(function(error, socket) {
2020 if (error) throw error;
2021
2022 // Are we a child of another run?
2023 var current = ChildRunner.current();
2024 var parent = current && current.parentScope.WCT._reporter;
2025 debug('parentReporter:', parent);
2026
2027 var childSuites = activeChildSuites();
2028 var reportersToUse = determineReporters(socket, parent);
2029 // +1 for any local tests.
2030 var reporter = new MultiReporter(childSuites.length + 1, reportersToUse, par ent);
2031 WCT._reporter = reporter; // For environment/compatibility.js
2032
2033 // We need the reporter so that we can report errors during load.
2034 loadJsSuites(reporter, function(error) {
2035 // Let our parent know that we're about to start the tests.
2036 if (current) current.ready(error);
2037 if (error) throw error;
2038
2039 // Emit any errors we've encountered up til now
2040 globalErrors.forEach(function onError(error) {
2041 reporter.emitOutOfBandTest('Test Suite Initialization', error);
2042 });
2043
2044 runSuites(reporter, childSuites, function(error) {
2045 // Make sure to let our parent know that we're done.
2046 if (current) current.done();
2047 if (error) throw error;
2048 });
2049 });
2050 });
2051 });
2052
2053 }());
2054 //# sourceMappingURL=browser.js.map
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698