UNPKG

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