| Index: appengine/config_service/ui/bower_components/web-component-tester/browser.js
|
| diff --git a/appengine/config_service/ui/bower_components/web-component-tester/browser.js b/appengine/config_service/ui/bower_components/web-component-tester/browser.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0999f4ea4b70eb41fa982661f67cb2f774d2257a
|
| --- /dev/null
|
| +++ b/appengine/config_service/ui/bower_components/web-component-tester/browser.js
|
| @@ -0,0 +1,2054 @@
|
| +/**
|
| + * @license
|
| + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| + * Code distributed by Google as part of the polymer project is also
|
| + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| + */
|
| +
|
| +/**
|
| + * THIS FILE IS AUTOMATICALLY GENERATED!
|
| + * To make changes to browser.js, please edit the source files in the repo's `browser/` directory!
|
| + */
|
| +
|
| +(function () {
|
| +'use strict';
|
| +
|
| +/**
|
| + * @param {function()} callback A function to call when the active web component
|
| + * frameworks have loaded.
|
| + */
|
| +function whenFrameworksReady(callback) {
|
| + debug('whenFrameworksReady');
|
| + var done = function() {
|
| + debug('whenFrameworksReady done');
|
| + callback();
|
| + };
|
| +
|
| + // If webcomponents script is in the document, wait for WebComponentsReady.
|
| + if (window.WebComponents && !window.WebComponents.ready) {
|
| + debug('WebComponentsReady?');
|
| + window.addEventListener('WebComponentsReady', function wcReady() {
|
| + window.removeEventListener('WebComponentsReady', wcReady);
|
| + debug('WebComponentsReady');
|
| + done();
|
| + });
|
| + } else {
|
| + done();
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * @param {number} count
|
| + * @param {string} kind
|
| + * @return {string} '<count> <kind> tests' or '<count> <kind> test'.
|
| + */
|
| +function pluralizedStat(count, kind) {
|
| + if (count === 1) {
|
| + return count + ' ' + kind + ' test';
|
| + } else {
|
| + return count + ' ' + kind + ' tests';
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * @param {string} path The URI of the script to load.
|
| + * @param {function} done
|
| + */
|
| +function loadScript(path, done) {
|
| + var script = document.createElement('script');
|
| + script.src = path;
|
| + if (done) {
|
| + script.onload = done.bind(null, null);
|
| + script.onerror = done.bind(null, 'Failed to load script ' + script.src);
|
| + }
|
| + document.head.appendChild(script);
|
| +}
|
| +
|
| +/**
|
| + * @param {string} path The URI of the stylesheet to load.
|
| + * @param {function} done
|
| + */
|
| +function loadStyle(path, done) {
|
| + var link = document.createElement('link');
|
| + link.rel = 'stylesheet';
|
| + link.href = path;
|
| + if (done) {
|
| + link.onload = done.bind(null, null);
|
| + link.onerror = done.bind(null, 'Failed to load stylesheet ' + link.href);
|
| + }
|
| + document.head.appendChild(link);
|
| +}
|
| +
|
| +/**
|
| + * @param {...*} var_args Logs values to the console when the `debug`
|
| + * configuration option is true.
|
| + */
|
| +function debug(var_args) {
|
| + if (!get('verbose')) return;
|
| + var args = [window.location.pathname];
|
| + args.push.apply(args, arguments);
|
| + (console.debug || console.log).apply(console, args);
|
| +}
|
| +
|
| +// URL Processing
|
| +
|
| +/**
|
| + * @param {string} url
|
| + * @return {{base: string, params: string}}
|
| + */
|
| +function parseUrl(url) {
|
| + var parts = url.match(/^(.*?)(?:\?(.*))?$/);
|
| + return {
|
| + base: parts[1],
|
| + params: getParams(parts[2] || ''),
|
| + };
|
| +}
|
| +
|
| +/**
|
| + * Expands a URL that may or may not be relative to `base`.
|
| + *
|
| + * @param {string} url
|
| + * @param {string} base
|
| + * @return {string}
|
| + */
|
| +function expandUrl(url, base) {
|
| + if (!base) return url;
|
| + if (url.match(/^(\/|https?:\/\/)/)) return url;
|
| + if (base.substr(base.length - 1) !== '/') {
|
| + base = base + '/';
|
| + }
|
| + return base + url;
|
| +}
|
| +
|
| +/**
|
| + * @param {string=} opt_query A query string to parse.
|
| + * @return {!Object<string, !Array<string>>} All params on the URL's query.
|
| + */
|
| +function getParams(opt_query) {
|
| + var query = typeof opt_query === 'string' ? opt_query : window.location.search;
|
| + if (query.substring(0, 1) === '?') {
|
| + query = query.substring(1);
|
| + }
|
| + // python's SimpleHTTPServer tacks a `/` on the end of query strings :(
|
| + if (query.slice(-1) === '/') {
|
| + query = query.substring(0, query.length - 1);
|
| + }
|
| + if (query === '') return {};
|
| +
|
| + var result = {};
|
| + query.split('&').forEach(function(part) {
|
| + var pair = part.split('=');
|
| + if (pair.length !== 2) {
|
| + console.warn('Invalid URL query part:', part);
|
| + return;
|
| + }
|
| + var key = decodeURIComponent(pair[0]);
|
| + var value = decodeURIComponent(pair[1]);
|
| +
|
| + if (!result[key]) {
|
| + result[key] = [];
|
| + }
|
| + result[key].push(value);
|
| + });
|
| +
|
| + return result;
|
| +}
|
| +
|
| +/**
|
| + * Merges params from `source` into `target` (mutating `target`).
|
| + *
|
| + * @param {!Object<string, !Array<string>>} target
|
| + * @param {!Object<string, !Array<string>>} source
|
| + */
|
| +function mergeParams(target, source) {
|
| + Object.keys(source).forEach(function(key) {
|
| + if (!(key in target)) {
|
| + target[key] = [];
|
| + }
|
| + target[key] = target[key].concat(source[key]);
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * @param {string} param The param to return a value for.
|
| + * @return {?string} The first value for `param`, if found.
|
| + */
|
| +function getParam(param) {
|
| + var params = getParams();
|
| + return params[param] ? params[param][0] : null;
|
| +}
|
| +
|
| +/**
|
| + * @param {!Object<string, !Array<string>>} params
|
| + * @return {string} `params` encoded as a URI query.
|
| + */
|
| +function paramsToQuery(params) {
|
| + var pairs = [];
|
| + Object.keys(params).forEach(function(key) {
|
| + params[key].forEach(function(value) {
|
| + pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
| + });
|
| + });
|
| + return (pairs.length > 0) ? ('?' + pairs.join('&')) : '';
|
| +}
|
| +
|
| +/**
|
| + * @param {!Location|string} location
|
| + * @return {string}
|
| + */
|
| +function basePath(location) {
|
| + return (location.pathname || location).match(/^.*\//)[0];
|
| +}
|
| +
|
| +/**
|
| + * @param {!Location|string} location
|
| + * @param {string} basePath
|
| + * @return {string}
|
| + */
|
| +function relativeLocation(location, basePath) {
|
| + var path = location.pathname || location;
|
| + if (path.indexOf(basePath) === 0) {
|
| + path = path.substring(basePath.length);
|
| + }
|
| + return path;
|
| +}
|
| +
|
| +/**
|
| + * @param {!Location|string} location
|
| + * @return {string}
|
| + */
|
| +function cleanLocation(location) {
|
| + var path = location.pathname || location;
|
| + if (path.slice(-11) === '/index.html') {
|
| + path = path.slice(0, path.length - 10);
|
| + }
|
| + return path;
|
| +}
|
| +
|
| +/**
|
| + * Like `async.parallelLimit`, but our own so that we don't force a dependency
|
| + * on downstream code.
|
| + *
|
| + * @param {!Array<function(function(*))>} runners Runners that call their given
|
| + * Node-style callback when done.
|
| + * @param {number|function(*)} limit Maximum number of concurrent runners.
|
| + * (optional).
|
| + * @param {?function(*)} done Callback that should be triggered once all runners
|
| + * have completed, or encountered an error.
|
| + */
|
| +function parallel(runners, limit, done) {
|
| + if (typeof limit !== 'number') {
|
| + done = limit;
|
| + limit = 0;
|
| + }
|
| + if (!runners.length) return done();
|
| +
|
| + var called = false;
|
| + var total = runners.length;
|
| + var numActive = 0;
|
| + var numDone = 0;
|
| +
|
| + function runnerDone(error) {
|
| + if (called) return;
|
| + numDone = numDone + 1;
|
| + numActive = numActive - 1;
|
| +
|
| + if (error || numDone >= total) {
|
| + called = true;
|
| + done(error);
|
| + } else {
|
| + runOne();
|
| + }
|
| + }
|
| +
|
| + function runOne() {
|
| + if (limit && numActive >= limit) return;
|
| + if (!runners.length) return;
|
| + numActive = numActive + 1;
|
| + runners.shift()(runnerDone);
|
| + }
|
| + runners.forEach(runOne);
|
| +}
|
| +
|
| +/**
|
| + * Finds the directory that a loaded script is hosted on.
|
| + *
|
| + * @param {string} filename
|
| + * @return {string?}
|
| + */
|
| +function scriptPrefix(filename) {
|
| + var scripts = document.querySelectorAll('script[src*="' + filename + '"]');
|
| + if (scripts.length !== 1) return null;
|
| + var script = scripts[0].src;
|
| + return script.substring(0, script.indexOf(filename));
|
| +}
|
| +
|
| +var util = Object.freeze({
|
| + whenFrameworksReady: whenFrameworksReady,
|
| + pluralizedStat: pluralizedStat,
|
| + loadScript: loadScript,
|
| + loadStyle: loadStyle,
|
| + debug: debug,
|
| + parseUrl: parseUrl,
|
| + expandUrl: expandUrl,
|
| + getParams: getParams,
|
| + mergeParams: mergeParams,
|
| + getParam: getParam,
|
| + paramsToQuery: paramsToQuery,
|
| + basePath: basePath,
|
| + relativeLocation: relativeLocation,
|
| + cleanLocation: cleanLocation,
|
| + parallel: parallel,
|
| + scriptPrefix: scriptPrefix
|
| +});
|
| +
|
| +// TODO(thedeeno): Consider renaming subsuite. IIRC, childRunner is entirely
|
| +// distinct from mocha suite, which tripped me up badly when trying to add
|
| +// plugin support. Perhaps something like 'batch', or 'bundle'. Something that
|
| +// has no mocha correlate. This may also eliminate the need for root/non-root
|
| +// suite distinctions.
|
| +
|
| +/**
|
| + * A Mocha suite (or suites) run within a child iframe, but reported as if they
|
| + * are part of the current context.
|
| + */
|
| +function ChildRunner(url, parentScope) {
|
| + var urlBits = parseUrl(url);
|
| + mergeParams(
|
| + urlBits.params, getParams(parentScope.location.search));
|
| + delete urlBits.params.cli_browser_id;
|
| +
|
| + this.url = urlBits.base + paramsToQuery(urlBits.params);
|
| + this.parentScope = parentScope;
|
| +
|
| + this.state = 'initializing';
|
| +}
|
| +
|
| +// ChildRunners get a pretty generous load timeout by default.
|
| +ChildRunner.loadTimeout = 60000;
|
| +
|
| +// We can't maintain properties on iframe elements in Firefox/Safari/???, so we
|
| +// track childRunners by URL.
|
| +ChildRunner._byUrl = {};
|
| +
|
| +/**
|
| + * @return {ChildRunner} The `ChildRunner` that was registered for this window.
|
| + */
|
| +ChildRunner.current = function() {
|
| + return ChildRunner.get(window);
|
| +};
|
| +
|
| +/**
|
| + * @param {!Window} target A window to find the ChildRunner of.
|
| + * @param {boolean} traversal Whether this is a traversal from a child window.
|
| + * @return {ChildRunner} The `ChildRunner` that was registered for `target`.
|
| + */
|
| +ChildRunner.get = function(target, traversal) {
|
| + var childRunner = ChildRunner._byUrl[target.location.href];
|
| + if (childRunner) return childRunner;
|
| + if (window.parent === window) { // Top window.
|
| + if (traversal) {
|
| + console.warn('Subsuite loaded but was never registered. This most likely is due to wonky history behavior. Reloading...');
|
| + window.location.reload();
|
| + }
|
| + return null;
|
| + }
|
| + // Otherwise, traverse.
|
| + return window.parent.WCT._ChildRunner.get(target, true);
|
| +};
|
| +
|
| +/**
|
| + * Loads and runs the subsuite.
|
| + *
|
| + * @param {function} done Node-style callback.
|
| + */
|
| +ChildRunner.prototype.run = function(done) {
|
| + debug('ChildRunner#run', this.url);
|
| + this.state = 'loading';
|
| + this.onRunComplete = done;
|
| +
|
| + this.iframe = document.createElement('iframe');
|
| + this.iframe.src = this.url;
|
| + this.iframe.classList.add('subsuite');
|
| +
|
| + var container = document.getElementById('subsuites');
|
| + if (!container) {
|
| + container = document.createElement('div');
|
| + container.id = 'subsuites';
|
| + document.body.appendChild(container);
|
| + }
|
| + container.appendChild(this.iframe);
|
| +
|
| + // let the iframe expand the URL for us.
|
| + this.url = this.iframe.src;
|
| + ChildRunner._byUrl[this.url] = this;
|
| +
|
| + this.timeoutId = setTimeout(
|
| + this.loaded.bind(this, new Error('Timed out loading ' + this.url)), ChildRunner.loadTimeout);
|
| +
|
| + this.iframe.addEventListener('error',
|
| + this.loaded.bind(this, new Error('Failed to load document ' + this.url)));
|
| +
|
| + this.iframe.contentWindow.addEventListener('DOMContentLoaded', this.loaded.bind(this, null));
|
| +};
|
| +
|
| +/**
|
| + * Called when the sub suite's iframe has loaded (or errored during load).
|
| + *
|
| + * @param {*} error The error that occured, if any.
|
| + */
|
| +ChildRunner.prototype.loaded = function(error) {
|
| + debug('ChildRunner#loaded', this.url, error);
|
| +
|
| + // Not all targets have WCT loaded (compatiblity mode)
|
| + if (this.iframe.contentWindow.WCT) {
|
| + this.share = this.iframe.contentWindow.WCT.share;
|
| + }
|
| +
|
| + if (error) {
|
| + this.signalRunComplete(error);
|
| + this.done();
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Called in mocha/run.js when all dependencies have loaded, and the child is
|
| + * ready to start running tests
|
| + *
|
| + * @param {*} error The error that occured, if any.
|
| + */
|
| +ChildRunner.prototype.ready = function(error) {
|
| + debug('ChildRunner#ready', this.url, error);
|
| + if (this.timeoutId) {
|
| + clearTimeout(this.timeoutId);
|
| + }
|
| + if (error) {
|
| + this.signalRunComplete(error);
|
| + this.done();
|
| + }
|
| +};
|
| +
|
| +/** Called when the sub suite's tests are complete, so that it can clean up. */
|
| +ChildRunner.prototype.done = function done() {
|
| + debug('ChildRunner#done', this.url, arguments);
|
| +
|
| + // make sure to clear that timeout
|
| + this.ready();
|
| + this.signalRunComplete();
|
| +
|
| + if (!this.iframe) return;
|
| + // Be safe and avoid potential browser crashes when logic attempts to interact
|
| + // with the removed iframe.
|
| + setTimeout(function() {
|
| + this.iframe.parentNode.removeChild(this.iframe);
|
| + this.iframe = null;
|
| + }.bind(this), 1);
|
| +};
|
| +
|
| +ChildRunner.prototype.signalRunComplete = function signalRunComplete(error) {
|
| + if (!this.onRunComplete) return;
|
| + this.state = 'complete';
|
| + this.onRunComplete(error);
|
| + this.onRunComplete = null;
|
| +};
|
| +
|
| +/**
|
| + * The global configuration state for WCT's browser client.
|
| + */
|
| +var _config = {
|
| + /**
|
| + * `.js` scripts to be loaded (synchronously) before WCT starts in earnest.
|
| + *
|
| + * Paths are relative to `scriptPrefix`.
|
| + */
|
| + environmentScripts: [
|
| + 'stacky/browser.js',
|
| + 'async/lib/async.js',
|
| + 'lodash/lodash.js',
|
| + 'mocha/mocha.js',
|
| + 'chai/chai.js',
|
| + 'sinonjs/sinon.js',
|
| + 'sinon-chai/lib/sinon-chai.js',
|
| + 'accessibility-developer-tools/dist/js/axs_testing.js'
|
| + ],
|
| +
|
| + environmentImports: [
|
| + 'test-fixture/test-fixture.html'
|
| + ],
|
| +
|
| + /** Absolute root for client scripts. Detected in `setup()` if not set. */
|
| + root: null,
|
| +
|
| + /** By default, we wait for any web component frameworks to load. */
|
| + waitForFrameworks: true,
|
| +
|
| + /** Alternate callback for waiting for tests.
|
| + * `this` for the callback will be the window currently running tests.
|
| + */
|
| + waitFor: null,
|
| +
|
| + /** How many `.html` suites that can be concurrently loaded & run. */
|
| + numConcurrentSuites: 1,
|
| +
|
| + /** Whether `console.error` should be treated as a test failure. */
|
| + trackConsoleError: true,
|
| +
|
| + /** Configuration passed to mocha.setup. */
|
| + mochaOptions: {
|
| + timeout: 10 * 1000
|
| + },
|
| +
|
| + /** Whether WCT should emit (extremely verbose) debugging log messages. */
|
| + verbose: false,
|
| +};
|
| +
|
| +/**
|
| + * Merges initial `options` into WCT's global configuration.
|
| + *
|
| + * @param {Object} options The options to merge. See `browser/config.js` for a
|
| + * reference.
|
| + */
|
| +function setup(options) {
|
| + var childRunner = ChildRunner.current();
|
| + if (childRunner) {
|
| + _deepMerge(_config, childRunner.parentScope.WCT._config);
|
| + // But do not force the mocha UI
|
| + delete _config.mochaOptions.ui;
|
| + }
|
| +
|
| + if (options && typeof options === 'object') {
|
| + _deepMerge(_config, options);
|
| + }
|
| +
|
| + if (!_config.root) {
|
| + // Sibling dependencies.
|
| + var root = scriptPrefix('browser.js');
|
| + _config.root = basePath(root.substr(0, root.length - 1));
|
| + if (!_config.root) {
|
| + throw new Error('Unable to detect root URL for WCT sources. Please set WCT.root before including browser.js');
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Retrieves a configuration value.
|
| + *
|
| + * @param {string} key
|
| + * @return {*}
|
| + */
|
| +function get(key) {
|
| + return _config[key];
|
| +}
|
| +
|
| +// Internal
|
| +
|
| +function _deepMerge(target, source) {
|
| + Object.keys(source).forEach(function (key) {
|
| + if (target[key] !== null && typeof target[key] === 'object' && !Array.isArray(target[key])) {
|
| + _deepMerge(target[key], source[key]);
|
| + } else {
|
| + target[key] = source[key];
|
| + }
|
| + });
|
| +}
|
| +
|
| +var htmlSuites$1 = [];
|
| +var jsSuites$1 = [];
|
| +
|
| +// We process grep ourselves to avoid loading suites that will be filtered.
|
| +var GREP = getParam('grep');
|
| +// work around mocha bug (https://github.com/mochajs/mocha/issues/2070)
|
| +if (GREP) {
|
| + GREP = GREP.replace(/\\\./g, '.');
|
| +}
|
| +
|
| +/**
|
| + * Loads suites of tests, supporting both `.js` and `.html` files.
|
| + *
|
| + * @param {!Array.<string>} files The files to load.
|
| + */
|
| +function loadSuites(files) {
|
| + files.forEach(function(file) {
|
| + if (/\.js(\?.*)?$/.test(file)) {
|
| + jsSuites$1.push(file);
|
| + } else if (/\.html(\?.*)?$/.test(file)) {
|
| + htmlSuites$1.push(file);
|
| + } else {
|
| + throw new Error('Unknown resource type: ' + file);
|
| + }
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * @return {!Array.<string>} The child suites that should be loaded, ignoring
|
| + * those that would not match `GREP`.
|
| + */
|
| +function activeChildSuites() {
|
| + var subsuites = htmlSuites$1;
|
| + if (GREP) {
|
| + var cleanSubsuites = [];
|
| + for (var i = 0, subsuite; subsuite = subsuites[i]; i++) {
|
| + if (GREP.indexOf(cleanLocation(subsuite)) !== -1) {
|
| + cleanSubsuites.push(subsuite);
|
| + }
|
| + }
|
| + subsuites = cleanSubsuites;
|
| + }
|
| + return subsuites;
|
| +}
|
| +
|
| +/**
|
| + * Loads all `.js` sources requested by the current suite.
|
| + *
|
| + * @param {!MultiReporter} reporter
|
| + * @param {function} done
|
| + */
|
| +function loadJsSuites(reporter, done) {
|
| + debug('loadJsSuites', jsSuites$1);
|
| +
|
| + var loaders = jsSuites$1.map(function(file) {
|
| + // We only support `.js` dependencies for now.
|
| + return loadScript.bind(util, file);
|
| + });
|
| +
|
| + parallel(loaders, done);
|
| +}
|
| +
|
| +/**
|
| + * @param {!MultiReporter} reporter
|
| + * @param {!Array.<string>} childSuites
|
| + * @param {function} done
|
| + */
|
| +function runSuites(reporter, childSuites, done) {
|
| + debug('runSuites');
|
| +
|
| + var suiteRunners = [
|
| + // Run the local tests (if any) first, not stopping on error;
|
| + _runMocha.bind(null, reporter),
|
| + ];
|
| +
|
| + // As well as any sub suites. Again, don't stop on error.
|
| + childSuites.forEach(function(file) {
|
| + suiteRunners.push(function(next) {
|
| + var childRunner = new ChildRunner(file, window);
|
| + reporter.emit('childRunner start', childRunner);
|
| + childRunner.run(function(error) {
|
| + reporter.emit('childRunner end', childRunner);
|
| + if (error) reporter.emitOutOfBandTest(file, error);
|
| + next();
|
| + });
|
| + });
|
| + });
|
| +
|
| + parallel(suiteRunners, get('numConcurrentSuites'), function(error) {
|
| + reporter.done();
|
| + done(error);
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * Kicks off a mocha run, waiting for frameworks to load if necessary.
|
| + *
|
| + * @param {!MultiReporter} reporter Where to send Mocha's events.
|
| + * @param {function} done A callback fired, _no error is passed_.
|
| + */
|
| +function _runMocha(reporter, done, waited) {
|
| + if (get('waitForFrameworks') && !waited) {
|
| + var waitFor = (get('waitFor') || whenFrameworksReady).bind(window);
|
| + waitFor(function() {
|
| + _fixCustomElements();
|
| + _runMocha(reporter, done, true);
|
| + });
|
| + return;
|
| + }
|
| + debug('_runMocha');
|
| + var mocha = window.mocha;
|
| + var Mocha = window.Mocha;
|
| +
|
| + mocha.reporter(reporter.childReporter(window.location));
|
| + mocha.suite.title = reporter.suiteTitle(window.location);
|
| + mocha.grep(GREP);
|
| +
|
| + // We can't use `mocha.run` because it bashes over grep, invert, and friends.
|
| + // See https://github.com/visionmedia/mocha/blob/master/support/tail.js#L137
|
| + var runner = Mocha.prototype.run.call(mocha, function(error) {
|
| + if (document.getElementById('mocha')) {
|
| + Mocha.utils.highlightTags('code');
|
| + }
|
| + done(); // We ignore the Mocha failure count.
|
| + });
|
| +
|
| + // Mocha's default `onerror` handling strips the stack (to support really old
|
| + // browsers). We upgrade this to get better stacks for async errors.
|
| + //
|
| + // TODO(nevir): Can we expand support to other browsers?
|
| + if (navigator.userAgent.match(/chrome/i)) {
|
| + window.onerror = null;
|
| + window.addEventListener('error', function(event) {
|
| + if (!event.error) return;
|
| + if (event.error.ignore) return;
|
| + runner.uncaught(event.error);
|
| + });
|
| + }
|
| +}
|
| +/**
|
| + * In Chrome57 custom elements in the document might not get upgraded when
|
| + * there is a high GC https://bugs.chromium.org/p/chromium/issues/detail?id=701601
|
| + * We clone and replace the ones that weren't upgraded.
|
| + */
|
| +function _fixCustomElements() {
|
| + // Bail out if it is not Chrome 57.
|
| + var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
| + var isM57 = raw && raw[2] === '57';
|
| + if (!isM57) return;
|
| +
|
| + var elements = document.body.querySelectorAll('*:not(script):not(style)');
|
| + var constructors = {};
|
| + for (var i = 0; i < elements.length; i++) {
|
| + var el = elements[i];
|
| + // This child has already been cloned and replaced by its parent, skip it!
|
| + if (!el.isConnected) continue;
|
| +
|
| + var tag = el.localName;
|
| + // Not a custom element!
|
| + if (tag.indexOf('-') === -1) continue;
|
| +
|
| + // Memoize correct constructors.
|
| + constructors[tag] = constructors[tag] || document.createElement(tag).constructor;
|
| + // This one was correctly upgraded.
|
| + if (el instanceof constructors[tag]) continue;
|
| +
|
| + debug('_fixCustomElements: found non-upgraded custom element ' + el);
|
| + var clone = document.importNode(el, true);
|
| + el.parentNode.replaceChild(clone, el);
|
| + }
|
| +}
|
| +
|
| +// We capture console events when running tests; so make sure we have a
|
| +// reference to the original one.
|
| +var console$1 = window.console;
|
| +
|
| +var FONT = ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-serif;';
|
| +var STYLES = {
|
| + plain: FONT,
|
| + suite: 'color: #5c6bc0' + FONT,
|
| + test: FONT,
|
| + passing: 'color: #259b24' + FONT,
|
| + pending: 'color: #e65100' + FONT,
|
| + failing: 'color: #c41411' + FONT,
|
| + stack: 'color: #c41411',
|
| + results: FONT + 'font-size: 16px',
|
| +};
|
| +
|
| +// I don't think we can feature detect this one...
|
| +var userAgent = navigator.userAgent.toLowerCase();
|
| +var CAN_STYLE_LOG = userAgent.match('firefox') || userAgent.match('webkit');
|
| +var CAN_STYLE_GROUP = userAgent.match('webkit');
|
| +// Track the indent for faked `console.group`
|
| +var logIndent = '';
|
| +
|
| +function log(text, style) {
|
| + text = text.split('\n').map(function(l) { return logIndent + l; }).join('\n');
|
| + if (CAN_STYLE_LOG) {
|
| + console$1.log('%c' + text, STYLES[style] || STYLES.plain);
|
| + } else {
|
| + console$1.log(text);
|
| + }
|
| +}
|
| +
|
| +function logGroup(text, style) {
|
| + if (CAN_STYLE_GROUP) {
|
| + console$1.group('%c' + text, STYLES[style] || STYLES.plain);
|
| + } else if (console$1.group) {
|
| + console$1.group(text);
|
| + } else {
|
| + logIndent = logIndent + ' ';
|
| + log(text, style);
|
| + }
|
| +}
|
| +
|
| +function logGroupEnd() {
|
| + if (console$1.groupEnd) {
|
| + console$1.groupEnd();
|
| + } else {
|
| + logIndent = logIndent.substr(0, logIndent.length - 2);
|
| + }
|
| +}
|
| +
|
| +function logException(error) {
|
| + log(error.stack || error.message || error, 'stack');
|
| +}
|
| +
|
| +/**
|
| + * A Mocha reporter that logs results out to the web `console`.
|
| + *
|
| + * @param {!Mocha.Runner} runner The runner that is being reported on.
|
| + */
|
| +function Console(runner) {
|
| + Mocha.reporters.Base.call(this, runner);
|
| +
|
| + runner.on('suite', function(suite) {
|
| + if (suite.root) return;
|
| + logGroup(suite.title, 'suite');
|
| + }.bind(this));
|
| +
|
| + runner.on('suite end', function(suite) {
|
| + if (suite.root) return;
|
| + logGroupEnd();
|
| + }.bind(this));
|
| +
|
| + runner.on('test', function(test) {
|
| + logGroup(test.title, 'test');
|
| + }.bind(this));
|
| +
|
| + runner.on('pending', function(test) {
|
| + logGroup(test.title, 'pending');
|
| + }.bind(this));
|
| +
|
| + runner.on('fail', function(test, error) {
|
| + logException(error);
|
| + }.bind(this));
|
| +
|
| + runner.on('test end', function(test) {
|
| + logGroupEnd();
|
| + }.bind(this));
|
| +
|
| + runner.on('end', this.logSummary.bind(this));
|
| +}
|
| +
|
| +/** Prints out a final summary of test results. */
|
| +Console.prototype.logSummary = function logSummary() {
|
| + logGroup('Test Results', 'results');
|
| +
|
| + if (this.stats.failures > 0) {
|
| + log(pluralizedStat(this.stats.failures, 'failing'), 'failing');
|
| + }
|
| + if (this.stats.pending > 0) {
|
| + log(pluralizedStat(this.stats.pending, 'pending'), 'pending');
|
| + }
|
| + log(pluralizedStat(this.stats.passes, 'passing'));
|
| +
|
| + if (!this.stats.failures) {
|
| + log('test suite passed', 'passing');
|
| + }
|
| + log('Evaluated ' + this.stats.tests + ' tests in ' + this.stats.duration + 'ms.');
|
| + logGroupEnd();
|
| +};
|
| +
|
| +/**
|
| + * @license
|
| + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| + * Code distributed by Google as part of the polymer project is also
|
| + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| + */
|
| +
|
| +/**
|
| + * WCT-specific behavior on top of Mocha's default HTML reporter.
|
| + *
|
| + * @param {!Mocha.Runner} runner The runner that is being reported on.
|
| + */
|
| +function HTML(runner) {
|
| + var output = document.createElement('div');
|
| + output.id = 'mocha';
|
| + document.body.appendChild(output);
|
| +
|
| + runner.on('suite', function(test) {
|
| + this.total = runner.total;
|
| + }.bind(this));
|
| +
|
| + Mocha.reporters.HTML.call(this, runner);
|
| +}
|
| +
|
| +// Woo! What a hack. This just saves us from adding a bunch of complexity around
|
| +// style loading.
|
| +var style = document.createElement('style');
|
| +style.textContent = 'html, body {' +
|
| + ' position: relative;' +
|
| + ' height: 100%;' +
|
| + ' width: 100%;' +
|
| + ' min-width: 900px;' +
|
| + '}' +
|
| + '#mocha, #subsuites {' +
|
| + ' height: 100%;' +
|
| + ' position: absolute;' +
|
| + ' top: 0;' +
|
| + '}' +
|
| + '#mocha {' +
|
| + ' box-sizing: border-box;' +
|
| + ' margin: 0 !important;' +
|
| + ' overflow-y: auto;' +
|
| + ' padding: 60px 20px;' +
|
| + ' right: 0;' +
|
| + ' left: 500px;' +
|
| + '}' +
|
| + '#subsuites {' +
|
| + ' -ms-flex-direction: column;' +
|
| + ' -webkit-flex-direction: column;' +
|
| + ' display: -ms-flexbox;' +
|
| + ' display: -webkit-flex;' +
|
| + ' display: flex;' +
|
| + ' flex-direction: column;' +
|
| + ' left: 0;' +
|
| + ' width: 500px;' +
|
| + '}' +
|
| + '#subsuites .subsuite {' +
|
| + ' border: 0;' +
|
| + ' width: 100%;' +
|
| + ' height: 100%;' +
|
| + '}' +
|
| + '#mocha .test.pass .duration {' +
|
| + ' color: #555 !important;' +
|
| + '}';
|
| +document.head.appendChild(style);
|
| +
|
| +var STACKY_CONFIG = {
|
| + indent: ' ',
|
| + locationStrip: [
|
| + /^https?:\/\/[^\/]+/,
|
| + /\?.*$/,
|
| + ],
|
| + filter: function(line) {
|
| + return line.location.match(/\/web-component-tester\/[^\/]+(\?.*)?$/);
|
| + },
|
| +};
|
| +
|
| +// https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36-46
|
| +var MOCHA_EVENTS = [
|
| + 'start',
|
| + 'end',
|
| + 'suite',
|
| + 'suite end',
|
| + 'test',
|
| + 'test end',
|
| + 'hook',
|
| + 'hook end',
|
| + 'pass',
|
| + 'fail',
|
| + 'pending',
|
| + 'childRunner end'
|
| +];
|
| +
|
| +// Until a suite has loaded, we assume this many tests in it.
|
| +var ESTIMATED_TESTS_PER_SUITE = 3;
|
| +
|
| +/**
|
| + * A Mocha-like reporter that combines the output of multiple Mocha suites.
|
| + *
|
| + * @param {number} numSuites The number of suites that will be run, in order to
|
| + * estimate the total number of tests that will be performed.
|
| + * @param {!Array.<!Mocha.reporters.Base>} reporters The set of reporters that
|
| + * should receive the unified event stream.
|
| + * @param {MultiReporter} parent The parent reporter, if present.
|
| + */
|
| +function MultiReporter(numSuites, reporters, parent) {
|
| + this.reporters = reporters.map(function(reporter) {
|
| + return new reporter(this);
|
| + }.bind(this));
|
| +
|
| + this.parent = parent;
|
| + this.basePath = parent && parent.basePath || basePath(window.location);
|
| +
|
| + this.total = numSuites * ESTIMATED_TESTS_PER_SUITE;
|
| + // Mocha reporters assume a stream of events, so we have to be careful to only
|
| + // report on one runner at a time...
|
| + this.currentRunner = null;
|
| + // ...while we buffer events for any other active runners.
|
| + this.pendingEvents = [];
|
| +
|
| + this.emit('start');
|
| +}
|
| +
|
| +/**
|
| + * @param {!Location|string} location The location this reporter represents.
|
| + * @return {!Mocha.reporters.Base} A reporter-like "class" for each child suite
|
| + * that should be passed to `mocha.run`.
|
| + */
|
| +MultiReporter.prototype.childReporter = function childReporter(location) {
|
| + var name = this.suiteTitle(location);
|
| + // The reporter is used as a constructor, so we can't depend on `this` being
|
| + // properly bound.
|
| + var self = this;
|
| + function reporter(runner) {
|
| + runner.name = name;
|
| + self.bindChildRunner(runner);
|
| + }
|
| + reporter.title = name;
|
| + return reporter;
|
| +};
|
| +
|
| +/** Must be called once all runners have finished. */
|
| +MultiReporter.prototype.done = function done() {
|
| + this.complete = true;
|
| + this.flushPendingEvents();
|
| + this.emit('end');
|
| +};
|
| +
|
| +/**
|
| + * Emit a top level test that is not part of any suite managed by this reporter.
|
| + *
|
| + * Helpful for reporting on global errors, loading issues, etc.
|
| + *
|
| + * @param {string} title The title of the test.
|
| + * @param {*} opt_error An error associated with this test. If falsy, test is
|
| + * considered to be passing.
|
| + * @param {string} opt_suiteTitle Title for the suite that's wrapping the test.
|
| + * @param {?boolean} opt_estimated If this test was included in the original
|
| + * estimate of `numSuites`.
|
| + */
|
| +MultiReporter.prototype.emitOutOfBandTest = function emitOutOfBandTest(title, opt_error, opt_suiteTitle, opt_estimated) {
|
| + debug('MultiReporter#emitOutOfBandTest(', arguments, ')');
|
| + var root = new Mocha.Suite();
|
| + root.title = opt_suiteTitle || '';
|
| + var test = new Mocha.Test(title, function() {
|
| + });
|
| + test.parent = root;
|
| + test.state = opt_error ? 'failed' : 'passed';
|
| + test.err = opt_error;
|
| +
|
| + if (!opt_estimated) {
|
| + this.total = this.total + ESTIMATED_TESTS_PER_SUITE;
|
| + }
|
| +
|
| + var runner = {total: 1};
|
| + this.proxyEvent('start', runner);
|
| + this.proxyEvent('suite', runner, root);
|
| + this.proxyEvent('test', runner, test);
|
| + if (opt_error) {
|
| + this.proxyEvent('fail', runner, test, opt_error);
|
| + } else {
|
| + this.proxyEvent('pass', runner, test);
|
| + }
|
| + this.proxyEvent('test end', runner, test);
|
| + this.proxyEvent('suite end', runner, root);
|
| + this.proxyEvent('end', runner);
|
| +};
|
| +
|
| +/**
|
| + * @param {!Location|string} location
|
| + * @return {string}
|
| + */
|
| +MultiReporter.prototype.suiteTitle = function suiteTitle(location) {
|
| + var path = relativeLocation(location, this.basePath);
|
| + path = cleanLocation(path);
|
| + return path;
|
| +};
|
| +
|
| +// Internal Interface
|
| +
|
| +/** @param {!Mocha.runners.Base} runner The runner to listen to events for. */
|
| +MultiReporter.prototype.bindChildRunner = function bindChildRunner(runner) {
|
| + MOCHA_EVENTS.forEach(function(eventName) {
|
| + runner.on(eventName, this.proxyEvent.bind(this, eventName, runner));
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Evaluates an event fired by `runner`, proxying it forward or buffering it.
|
| + *
|
| + * @param {string} eventName
|
| + * @param {!Mocha.runners.Base} runner The runner that emitted this event.
|
| + * @param {...*} var_args Any additional data passed as part of the event.
|
| + */
|
| +MultiReporter.prototype.proxyEvent = function proxyEvent(eventName, runner, var_args) {
|
| + var extraArgs = Array.prototype.slice.call(arguments, 2);
|
| + if (this.complete) {
|
| + console.warn('out of order Mocha event for ' + runner.name + ':', eventName, extraArgs);
|
| + return;
|
| + }
|
| +
|
| + if (this.currentRunner && runner !== this.currentRunner) {
|
| + this.pendingEvents.push(arguments);
|
| + return;
|
| + }
|
| + debug('MultiReporter#proxyEvent(', arguments, ')');
|
| +
|
| + // This appears to be a Mocha bug: Tests failed by passing an error to their
|
| + // done function don't set `err` properly.
|
| + //
|
| + // TODO(nevir): Track down.
|
| + if (eventName === 'fail' && !extraArgs[0].err) {
|
| + extraArgs[0].err = extraArgs[1];
|
| + }
|
| +
|
| + if (eventName === 'start') {
|
| + this.onRunnerStart(runner);
|
| + } else if (eventName === 'end') {
|
| + this.onRunnerEnd(runner);
|
| + } else {
|
| + this.cleanEvent(eventName, runner, extraArgs);
|
| + this.emit.apply(this, [eventName].concat(extraArgs));
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Cleans or modifies an event if needed.
|
| + *
|
| + * @param {string} eventName
|
| + * @param {!Mocha.runners.Base} runner The runner that emitted this event.
|
| + * @param {!Array.<*>} extraArgs
|
| + */
|
| +MultiReporter.prototype.cleanEvent = function cleanEvent(eventName, runner, extraArgs) {
|
| + // Suite hierarchy
|
| + if (extraArgs[0]) {
|
| + extraArgs[0] = this.showRootSuite(extraArgs[0]);
|
| + }
|
| +
|
| + // Normalize errors
|
| + if (eventName === 'fail') {
|
| + extraArgs[1] = Stacky.normalize(extraArgs[1], STACKY_CONFIG);
|
| + }
|
| + if (extraArgs[0] && extraArgs[0].err) {
|
| + extraArgs[0].err = Stacky.normalize(extraArgs[0].err, STACKY_CONFIG);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * We like to show the root suite's title, which requires a little bit of
|
| + * trickery in the suite hierarchy.
|
| + *
|
| + * @param {!Mocha.Runnable} node
|
| + */
|
| +MultiReporter.prototype.showRootSuite = function showRootSuite(node) {
|
| + var leaf = node = Object.create(node);
|
| + while (node && node.parent) {
|
| + var wrappedParent = Object.create(node.parent);
|
| + node.parent = wrappedParent;
|
| + node = wrappedParent;
|
| + }
|
| + node.root = false;
|
| +
|
| + return leaf;
|
| +};
|
| +
|
| +/** @param {!Mocha.runners.Base} runner */
|
| +MultiReporter.prototype.onRunnerStart = function onRunnerStart(runner) {
|
| + debug('MultiReporter#onRunnerStart:', runner.name);
|
| + this.total = this.total - ESTIMATED_TESTS_PER_SUITE + runner.total;
|
| + this.currentRunner = runner;
|
| +};
|
| +
|
| +/** @param {!Mocha.runners.Base} runner */
|
| +MultiReporter.prototype.onRunnerEnd = function onRunnerEnd(runner) {
|
| + debug('MultiReporter#onRunnerEnd:', runner.name);
|
| + this.currentRunner = null;
|
| + this.flushPendingEvents();
|
| +};
|
| +
|
| +/**
|
| + * Flushes any buffered events and runs them through `proxyEvent`. This will
|
| + * loop until all buffered runners are complete, or we have run out of buffered
|
| + * events.
|
| + */
|
| +MultiReporter.prototype.flushPendingEvents = function flushPendingEvents() {
|
| + var events = this.pendingEvents;
|
| + this.pendingEvents = [];
|
| + events.forEach(function(eventArgs) {
|
| + this.proxyEvent.apply(this, eventArgs);
|
| + }.bind(this));
|
| +};
|
| +
|
| +var ARC_OFFSET = 0; // start at the right.
|
| +var ARC_WIDTH = 6;
|
| +
|
| +/**
|
| + * A Mocha reporter that updates the document's title and favicon with
|
| + * at-a-glance stats.
|
| + *
|
| + * @param {!Mocha.Runner} runner The runner that is being reported on.
|
| + */
|
| +function Title(runner) {
|
| + Mocha.reporters.Base.call(this, runner);
|
| +
|
| + runner.on('test end', this.report.bind(this));
|
| +}
|
| +
|
| +/** Reports current stats via the page title and favicon. */
|
| +Title.prototype.report = function report() {
|
| + this.updateTitle();
|
| + this.updateFavicon();
|
| +};
|
| +
|
| +/** Updates the document title with a summary of current stats. */
|
| +Title.prototype.updateTitle = function updateTitle() {
|
| + if (this.stats.failures > 0) {
|
| + document.title = pluralizedStat(this.stats.failures, 'failing');
|
| + } else {
|
| + document.title = pluralizedStat(this.stats.passes, 'passing');
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Draws an arc for the favicon status, relative to the total number of tests.
|
| + *
|
| + * @param {!CanvasRenderingContext2D} context
|
| + * @param {number} total
|
| + * @param {number} start
|
| + * @param {number} length
|
| + * @param {string} color
|
| + */
|
| +function drawFaviconArc(context, total, start, length, color) {
|
| + var arcStart = ARC_OFFSET + Math.PI * 2 * (start / total);
|
| + var arcEnd = ARC_OFFSET + Math.PI * 2 * ((start + length) / total);
|
| +
|
| + context.beginPath();
|
| + context.strokeStyle = color;
|
| + context.lineWidth = ARC_WIDTH;
|
| + context.arc(16, 16, 16 - ARC_WIDTH / 2, arcStart, arcEnd);
|
| + context.stroke();
|
| +}
|
| +
|
| +/** Updates the document's favicon w/ a summary of current stats. */
|
| +Title.prototype.updateFavicon = function updateFavicon() {
|
| + var canvas = document.createElement('canvas');
|
| + canvas.height = canvas.width = 32;
|
| + var context = canvas.getContext('2d');
|
| +
|
| + var passing = this.stats.passes;
|
| + var pending = this.stats.pending;
|
| + var failing = this.stats.failures;
|
| + var total = Math.max(this.runner.total, passing + pending + failing);
|
| + drawFaviconArc(context, total, 0, passing, '#0e9c57');
|
| + drawFaviconArc(context, total, passing, pending, '#f3b300');
|
| + drawFaviconArc(context, total, pending + passing, failing, '#ff5621');
|
| +
|
| + this.setFavicon(canvas.toDataURL());
|
| +};
|
| +
|
| +/** Sets the current favicon by URL. */
|
| +Title.prototype.setFavicon = function setFavicon(url) {
|
| + var current = document.head.querySelector('link[rel="icon"]');
|
| + if (current) {
|
| + document.head.removeChild(current);
|
| + }
|
| +
|
| + var link = document.createElement('link');
|
| + link.rel = 'icon';
|
| + link.type = 'image/x-icon';
|
| + link.href = url;
|
| + link.setAttribute('sizes', '32x32');
|
| + document.head.appendChild(link);
|
| +};
|
| +
|
| +/**
|
| + * @param {CLISocket} socket The CLI socket, if present.
|
| + * @param {MultiReporter} parent The parent reporter, if present.
|
| + * @return {!Array.<!Mocha.reporters.Base} The reporters that should be used.
|
| + */
|
| +function determineReporters(socket, parent) {
|
| + // Parents are greedy.
|
| + if (parent) {
|
| + return [parent.childReporter(window.location)];
|
| + }
|
| +
|
| + // Otherwise, we get to run wild without any parental supervision!
|
| + var reporters = [Title, Console];
|
| +
|
| + if (socket) {
|
| + reporters.push(function(runner) {
|
| + socket.observe(runner);
|
| + });
|
| + }
|
| +
|
| + if (htmlSuites$1.length > 0 || jsSuites$1.length > 0) {
|
| + reporters.push(HTML);
|
| + }
|
| +
|
| + return reporters;
|
| +}
|
| +
|
| +/**
|
| + * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy.
|
| + */
|
| +function injectMocha(Mocha) {
|
| + _injectPrototype(Console, Mocha.reporters.Base.prototype);
|
| + _injectPrototype(HTML, Mocha.reporters.HTML.prototype);
|
| + // Mocha doesn't expose its `EventEmitter` shim directly, so:
|
| + _injectPrototype(MultiReporter, Object.getPrototypeOf(Mocha.Runner.prototype));
|
| +}
|
| +
|
| +function _injectPrototype(klass, prototype) {
|
| + var newPrototype = Object.create(prototype);
|
| + // Only support
|
| + Object.keys(klass.prototype).forEach(function(key) {
|
| + newPrototype[key] = klass.prototype[key];
|
| + });
|
| +
|
| + klass.prototype = newPrototype;
|
| +}
|
| +
|
| +/**
|
| + * Loads all environment scripts ...synchronously ...after us.
|
| + */
|
| +function loadSync() {
|
| + debug('Loading environment scripts:');
|
| + var a11ySuite = 'web-component-tester/data/a11ySuite.js';
|
| + var scripts = get('environmentScripts');
|
| + var a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySuite) > -1;
|
| + if (!a11ySuiteWillBeLoaded) {
|
| + // wct is running as a bower dependency, load a11ySuite from data/
|
| + scripts.push(a11ySuite);
|
| + }
|
| + scripts.forEach(function(path) {
|
| + var url = expandUrl(path, get('root'));
|
| + debug('Loading environment script:', url);
|
| + // Synchronous load.
|
| + document.write('<script src="' + encodeURI(url) + '"></script>'); // jshint ignore:line
|
| + });
|
| + debug('Environment scripts loaded');
|
| +
|
| + var imports = get('environmentImports');
|
| + imports.forEach(function(path) {
|
| + var url = expandUrl(path, get('root'));
|
| + debug('Loading environment import:', url);
|
| + // Synchronous load.
|
| + document.write('<link rel="import" href="' + encodeURI(url) + '">'); // jshint ignore:line
|
| + });
|
| + debug('Environment imports loaded');
|
| +}
|
| +
|
| +/**
|
| + * We have some hard dependencies on things that should be loaded via
|
| + * `environmentScripts`, so we assert that they're present here; and do any
|
| + * post-facto setup.
|
| + */
|
| +function ensureDependenciesPresent() {
|
| + _ensureMocha();
|
| + _checkChai();
|
| +}
|
| +
|
| +function _ensureMocha() {
|
| + var Mocha = window.Mocha;
|
| + if (!Mocha) {
|
| + 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');
|
| + }
|
| + injectMocha(Mocha);
|
| + // Magic loading of mocha's stylesheet
|
| + var mochaPrefix = scriptPrefix('mocha.js');
|
| + // only load mocha stylesheet for the test runner output
|
| + // Not the end of the world, if it doesn't load.
|
| + if (mochaPrefix && window.top === window.self) {
|
| + loadStyle(mochaPrefix + 'mocha.css');
|
| + }
|
| +}
|
| +
|
| +function _checkChai() {
|
| + if (!window.chai) {
|
| + debug('Chai not present; not registering shorthands');
|
| + return;
|
| + }
|
| +
|
| + window.assert = window.chai.assert;
|
| + window.expect = window.chai.expect;
|
| +}
|
| +
|
| +// We may encounter errors during initialization (for example, syntax errors in
|
| +// a test file). Hang onto those (and more) until we are ready to report them.
|
| +var globalErrors = [];
|
| +
|
| +/**
|
| + * Hook the environment to pick up on global errors.
|
| + */
|
| +function listenForErrors() {
|
| + window.addEventListener('error', function(event) {
|
| + globalErrors.push(event.error);
|
| + });
|
| +
|
| + // Also, we treat `console.error` as a test failure. Unless you prefer not.
|
| + var origConsole = console;
|
| + var origError = console.error;
|
| + console.error = function wctShimmedError() {
|
| + origError.apply(origConsole, arguments);
|
| + if (get('trackConsoleError')) {
|
| + throw 'console.error: ' + Array.prototype.join.call(arguments, ' ');
|
| + }
|
| + };
|
| +}
|
| +
|
| +var interfaceExtensions = [];
|
| +
|
| +/**
|
| + * Registers an extension that extends the global `Mocha` implementation
|
| + * with new helper methods. These helper methods will be added to the `window`
|
| + * when tests run for both BDD and TDD interfaces.
|
| + */
|
| +function extendInterfaces(helperName, helperFactory) {
|
| + interfaceExtensions.push(function() {
|
| + var Mocha = window.Mocha;
|
| + // For all Mocha interfaces (probably just TDD and BDD):
|
| + Object.keys(Mocha.interfaces).forEach(function(interfaceName) {
|
| + // This is the original callback that defines the interface (TDD or BDD):
|
| + var originalInterface = Mocha.interfaces[interfaceName];
|
| + // This is the name of the "teardown" or "afterEach" property for the
|
| + // current interface:
|
| + var teardownProperty = interfaceName === 'tdd' ? 'teardown' : 'afterEach';
|
| + // The original callback is monkey patched with a new one that appends to
|
| + // the global context however we want it to:
|
| + Mocha.interfaces[interfaceName] = function(suite) {
|
| + // Call back to the original callback so that we get the base interface:
|
| + originalInterface.apply(this, arguments);
|
| + // Register a listener so that we can further extend the base interface:
|
| + suite.on('pre-require', function(context, file, mocha) {
|
| + // Capture a bound reference to the teardown function as a convenience:
|
| + var teardown = context[teardownProperty].bind(context);
|
| + // Add our new helper to the testing context. The helper is generated
|
| + // by a factory method that receives the context, the teardown function
|
| + // and the interface name and returns the new method to be added to
|
| + // that context:
|
| + context[helperName] = helperFactory(context, teardown, interfaceName);
|
| + });
|
| + };
|
| + });
|
| + });
|
| +}
|
| +
|
| +/**
|
| + * Applies any registered interface extensions. The extensions will be applied
|
| + * as many times as this function is called, so don't call it more than once.
|
| + */
|
| +function applyExtensions() {
|
| + interfaceExtensions.forEach(function(applyExtension) {
|
| + applyExtension();
|
| + });
|
| +}
|
| +
|
| +extendInterfaces('fixture', function (context, teardown) {
|
| +
|
| + // Return context.fixture if it is already a thing, for backwards
|
| + // compatibility with `test-fixture-mocha.js`:
|
| + return context.fixture || function fixture(fixtureId, model) {
|
| +
|
| + // Automatically register a teardown callback that will restore the
|
| + // test-fixture:
|
| + teardown(function () {
|
| + document.getElementById(fixtureId).restore();
|
| + });
|
| +
|
| + // Find the test-fixture with the provided ID and create it, returning
|
| + // the results:
|
| + return document.getElementById(fixtureId).create(model);
|
| + };
|
| +});
|
| +
|
| +/**
|
| + * stub
|
| + *
|
| + * The stub addon allows the tester to partially replace the implementation of
|
| + * an element with some custom implementation. Usage example:
|
| + *
|
| + * beforeEach(function() {
|
| + * stub('x-foo', {
|
| + * attached: function() {
|
| + * // Custom implementation of the `attached` method of element `x-foo`..
|
| + * },
|
| + * otherMethod: function() {
|
| + * // More custom implementation..
|
| + * },
|
| + * getterSetterProperty: {
|
| + * get: function() {
|
| + * // Custom getter implementation..
|
| + * },
|
| + * set: function() {
|
| + * // Custom setter implementation..
|
| + * }
|
| + * },
|
| + * // etc..
|
| + * });
|
| + * });
|
| + */
|
| +extendInterfaces('stub', function(context, teardown) {
|
| +
|
| + return function stub(tagName, implementation) {
|
| + // Find the prototype of the element being stubbed:
|
| + var proto = document.createElement(tagName).constructor.prototype;
|
| +
|
| + // For all keys in the implementation to stub with..
|
| + var stubs = Object.keys(implementation).map(function(key) {
|
| + // Stub the method on the element prototype with Sinon:
|
| + return sinon.stub(proto, key, implementation[key]);
|
| + });
|
| +
|
| + // After all tests..
|
| + teardown(function() {
|
| + stubs.forEach(function(stub) {
|
| + stub.restore();
|
| + });
|
| + });
|
| + };
|
| +});
|
| +
|
| +// replacement map stores what should be
|
| +var replacements = {};
|
| +var replaceTeardownAttached = false;
|
| +
|
| +/**
|
| + * replace
|
| + *
|
| + * The replace addon allows the tester to replace all usages of one element with
|
| + * another element within all Polymer elements created within the time span of
|
| + * the test. Usage example:
|
| + *
|
| + * beforeEach(function() {
|
| + * replace('x-foo').with('x-fake-foo');
|
| + * });
|
| + *
|
| + * All annotations and attributes will be set on the placement element the way
|
| + * they were set for the original element.
|
| + */
|
| +extendInterfaces('replace', function (context, teardown) {
|
| + return function replace(oldTagName) {
|
| + return {
|
| + with: function (tagName) {
|
| + // Standardizes our replacements map
|
| + oldTagName = oldTagName.toLowerCase();
|
| + tagName = tagName.toLowerCase();
|
| +
|
| + replacements[oldTagName] = tagName;
|
| +
|
| + // If the function is already a stub, restore it to original
|
| + if (document.importNode.isSinonProxy) {
|
| + return;
|
| + }
|
| +
|
| + if (!Polymer.Element) {
|
| + Polymer.Element = function () { };
|
| + Polymer.Element.prototype._stampTemplate = function () { };
|
| + }
|
| +
|
| + // Keep a reference to the original `document.importNode`
|
| + // implementation for later:
|
| + var originalImportNode = document.importNode;
|
| +
|
| + // Use Sinon to stub `document.ImportNode`:
|
| + sinon.stub(document, 'importNode', function (origContent, deep) {
|
| + var templateClone = document.createElement('template');
|
| + var content = templateClone.content;
|
| + var inertDoc = content.ownerDocument;
|
| +
|
| + // imports node from inertDoc which holds inert nodes.
|
| + templateClone.content.appendChild(inertDoc.importNode(origContent, true));
|
| +
|
| + // optional arguments are not optional on IE.
|
| + var nodeIterator = document.createNodeIterator(content,
|
| + NodeFilter.SHOW_ELEMENT, null, true);
|
| + var node;
|
| +
|
| + // Traverses the tree. A recently-replaced node will be put next, so
|
| + // if a node is replaced, it will be checked if it needs to be
|
| + // replaced again.
|
| + while (node = nodeIterator.nextNode()) {
|
| + var currentTagName = node.tagName.toLowerCase();
|
| +
|
| + if (replacements.hasOwnProperty(currentTagName)) {
|
| + currentTagName = replacements[currentTagName];
|
| +
|
| + // find the final tag name.
|
| + while (replacements[currentTagName]) {
|
| + currentTagName = replacements[currentTagName];
|
| + }
|
| +
|
| + // Create a replacement:
|
| + var replacement = document.createElement(currentTagName);
|
| +
|
| + // For all attributes in the original node..
|
| + for (var index = 0; index < node.attributes.length; ++index) {
|
| + // Set that attribute on the replacement:
|
| + replacement.setAttribute(
|
| + node.attributes[index].name, node.attributes[index].value);
|
| + }
|
| +
|
| + // Replace the original node with the replacement node:
|
| + node.parentNode.replaceChild(replacement, node);
|
| + }
|
| + }
|
| +
|
| + return originalImportNode.call(this, content, deep);
|
| + });
|
| +
|
| + if (!replaceTeardownAttached) {
|
| + // After each test...
|
| + teardown(function () {
|
| + replaceTeardownAttached = true;
|
| + // Restore the stubbed version of `document.importNode`:
|
| + if (document.importNode.isSinonProxy) {
|
| + document.importNode.restore();
|
| + }
|
| +
|
| + // Empty the replacement map
|
| + replacements = {};
|
| + });
|
| + }
|
| + }
|
| + };
|
| + };
|
| +});
|
| +
|
| +// Mocha global helpers, broken out by testing method.
|
| +//
|
| +// Keys are the method for a particular interface; values are their analog in
|
| +// the opposite interface.
|
| +var MOCHA_EXPORTS = {
|
| + // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/tdd.js
|
| + tdd: {
|
| + 'setup': '"before"',
|
| + 'teardown': '"after"',
|
| + 'suiteSetup': '"beforeEach"',
|
| + 'suiteTeardown': '"afterEach"',
|
| + 'suite': '"describe" or "context"',
|
| + 'test': '"it" or "specify"',
|
| + },
|
| + // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/bdd.js
|
| + bdd: {
|
| + 'before': '"setup"',
|
| + 'after': '"teardown"',
|
| + 'beforeEach': '"suiteSetup"',
|
| + 'afterEach': '"suiteTeardown"',
|
| + 'describe': '"suite"',
|
| + 'context': '"suite"',
|
| + 'xdescribe': '"suite.skip"',
|
| + 'xcontext': '"suite.skip"',
|
| + 'it': '"test"',
|
| + 'xit': '"test.skip"',
|
| + 'specify': '"test"',
|
| + 'xspecify': '"test.skip"',
|
| + },
|
| +};
|
| +
|
| +/**
|
| + * Exposes all Mocha methods up front, configuring and running mocha
|
| + * automatically when you call them.
|
| + *
|
| + * The assumption is that it is a one-off (sub-)suite of tests being run.
|
| + */
|
| +function stubInterfaces() {
|
| + Object.keys(MOCHA_EXPORTS).forEach(function (ui) {
|
| + Object.keys(MOCHA_EXPORTS[ui]).forEach(function (key) {
|
| + window[key] = function wrappedMochaFunction() {
|
| + _setupMocha(ui, key, MOCHA_EXPORTS[ui][key]);
|
| + if (!window[key] || window[key] === wrappedMochaFunction) {
|
| + throw new Error('Expected mocha.setup to define ' + key);
|
| + }
|
| + window[key].apply(window, arguments);
|
| + };
|
| + });
|
| + });
|
| +}
|
| +
|
| +// Whether we've called `mocha.setup`
|
| +var _mochaIsSetup = false;
|
| +
|
| +/**
|
| + * @param {string} ui Sets up mocha to run `ui`-style tests.
|
| + * @param {string} key The method called that triggered this.
|
| + * @param {string} alternate The matching method in the opposite interface.
|
| + */
|
| +function _setupMocha(ui, key, alternate) {
|
| + var mochaOptions = get('mochaOptions');
|
| + if (mochaOptions.ui && mochaOptions.ui !== ui) {
|
| + var message = 'Mixing ' + mochaOptions.ui + ' and ' + ui + ' Mocha styles is not supported. ' +
|
| + 'You called "' + key + '". Did you mean ' + alternate + '?';
|
| + throw new Error(message);
|
| + }
|
| + if (_mochaIsSetup) return;
|
| +
|
| + applyExtensions();
|
| + mochaOptions.ui = ui;
|
| + mocha.setup(mochaOptions); // Note that the reporter is configured in run.js.
|
| +}
|
| +
|
| +var SOCKETIO_ENDPOINT = window.location.protocol + '//' + window.location.host;
|
| +var SOCKETIO_LIBRARY = SOCKETIO_ENDPOINT + '/socket.io/socket.io.js';
|
| +
|
| +/**
|
| + * A socket for communication between the CLI and browser runners.
|
| + *
|
| + * @param {string} browserId An ID generated by the CLI runner.
|
| + * @param {!io.Socket} socket The socket.io `Socket` to communicate over.
|
| + */
|
| +function CLISocket(browserId, socket) {
|
| + this.browserId = browserId;
|
| + this.socket = socket;
|
| +}
|
| +
|
| +/**
|
| + * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting
|
| + * interesting events back to the CLI runner.
|
| + */
|
| +CLISocket.prototype.observe = function observe(runner) {
|
| + this.emitEvent('browser-start', {
|
| + url: window.location.toString(),
|
| + });
|
| +
|
| + // We only emit a subset of events that we care about, and follow a more
|
| + // general event format that is hopefully applicable to test runners beyond
|
| + // mocha.
|
| + //
|
| + // For all possible mocha events, see:
|
| + // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36
|
| + runner.on('test', function(test) {
|
| + this.emitEvent('test-start', {test: getTitles(test)});
|
| + }.bind(this));
|
| +
|
| + runner.on('test end', function(test) {
|
| + this.emitEvent('test-end', {
|
| + state: getState(test),
|
| + test: getTitles(test),
|
| + duration: test.duration,
|
| + error: test.err,
|
| + });
|
| + }.bind(this));
|
| +
|
| + runner.on('fail', function(test, err) {
|
| + // fail the test run if we catch errors outside of a test function
|
| + if (test.type !== 'test') {
|
| + this.emitEvent('browser-fail', 'Error thrown outside of test function: ' + err.stack);
|
| + }
|
| + }.bind(this));
|
| +
|
| + runner.on('childRunner start', function(childRunner) {
|
| + this.emitEvent('sub-suite-start', childRunner.share);
|
| + }.bind(this));
|
| +
|
| + runner.on('childRunner end', function(childRunner) {
|
| + this.emitEvent('sub-suite-end', childRunner.share);
|
| + }.bind(this));
|
| +
|
| + runner.on('end', function() {
|
| + this.emitEvent('browser-end');
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * @param {string} event The name of the event to fire.
|
| + * @param {*} data Additional data to pass with the event.
|
| + */
|
| +CLISocket.prototype.emitEvent = function emitEvent(event, data) {
|
| + this.socket.emit('client-event', {
|
| + browserId: this.browserId,
|
| + event: event,
|
| + data: data,
|
| + });
|
| +};
|
| +
|
| +/**
|
| + * Builds a `CLISocket` if we are within a CLI-run environment; short-circuits
|
| + * otherwise.
|
| + *
|
| + * @param {function(*, CLISocket)} done Node-style callback.
|
| + */
|
| +CLISocket.init = function init(done) {
|
| + var browserId = getParam('cli_browser_id');
|
| + if (!browserId) return done();
|
| + // Only fire up the socket for root runners.
|
| + if (ChildRunner.current()) return done();
|
| +
|
| + loadScript(SOCKETIO_LIBRARY, function(error) {
|
| + if (error) return done(error);
|
| +
|
| + var socket = io(SOCKETIO_ENDPOINT);
|
| + socket.on('error', function(error) {
|
| + socket.off();
|
| + done(error);
|
| + });
|
| +
|
| + socket.on('connect', function() {
|
| + socket.off();
|
| + done(null, new CLISocket(browserId, socket));
|
| + });
|
| + });
|
| +};
|
| +
|
| +// Misc Utility
|
| +
|
| +/**
|
| + * @param {!Mocha.Runnable} runnable The test or suite to extract titles from.
|
| + * @return {!Array.<string>} The titles of the runnable and its parents.
|
| + */
|
| +function getTitles(runnable) {
|
| + var titles = [];
|
| + while (runnable && !runnable.root && runnable.title) {
|
| + titles.unshift(runnable.title);
|
| + runnable = runnable.parent;
|
| + }
|
| + return titles;
|
| +}
|
| +
|
| +/**
|
| + * @param {!Mocha.Runnable} runnable
|
| + * @return {string}
|
| + */
|
| +function getState(runnable) {
|
| + if (runnable.state === 'passed') {
|
| + return 'passing';
|
| + } else if (runnable.state == 'failed') {
|
| + return 'failing';
|
| + } else if (runnable.pending) {
|
| + return 'pending';
|
| + } else {
|
| + return 'unknown';
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * @license
|
| + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| + * Code distributed by Google as part of the polymer project is also
|
| + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| + */
|
| +
|
| +// Make sure that we use native timers, in case they're being stubbed out.
|
| +var setInterval = window.setInterval; // jshint ignore:line
|
| +var setTimeout$1 = window.setTimeout; // jshint ignore:line
|
| +var requestAnimationFrame = window.requestAnimationFrame; // jshint ignore:line
|
| +
|
| +/**
|
| + * Runs `stepFn`, catching any error and passing it to `callback` (Node-style).
|
| + * Otherwise, calls `callback` with no arguments on success.
|
| + *
|
| + * @param {function()} callback
|
| + * @param {function()} stepFn
|
| + */
|
| +window.safeStep = function safeStep(callback, stepFn) {
|
| + var err;
|
| + try {
|
| + stepFn();
|
| + } catch (error) {
|
| + err = error;
|
| + }
|
| + callback(err);
|
| +};
|
| +
|
| +/**
|
| + * Runs your test at declaration time (before Mocha has begun tests). Handy for
|
| + * when you need to test document initialization.
|
| + *
|
| + * Be aware that any errors thrown asynchronously cannot be tied to your test.
|
| + * You may want to catch them and pass them to the done event, instead. See
|
| + * `safeStep`.
|
| + *
|
| + * @param {string} name The name of the test.
|
| + * @param {function(?function())} testFn The test function. If an argument is
|
| + * accepted, the test will be treated as async, just like Mocha tests.
|
| + */
|
| +window.testImmediate = function testImmediate(name, testFn) {
|
| + if (testFn.length > 0) {
|
| + return testImmediateAsync(name, testFn);
|
| + }
|
| +
|
| + var err;
|
| + try {
|
| + testFn();
|
| + } catch (error) {
|
| + err = error;
|
| + }
|
| +
|
| + test(name, function(done) {
|
| + done(err);
|
| + });
|
| +};
|
| +
|
| +/**
|
| + * An async-only variant of `testImmediate`.
|
| + *
|
| + * @param {string} name
|
| + * @param {function(?function())} testFn
|
| + */
|
| +window.testImmediateAsync = function testImmediateAsync(name, testFn) {
|
| + var testComplete = false;
|
| + var err;
|
| +
|
| + test(name, function(done) {
|
| + var intervalId = setInterval(function() {
|
| + if (!testComplete) return;
|
| + clearInterval(intervalId);
|
| + done(err);
|
| + }, 10);
|
| + });
|
| +
|
| + try {
|
| + testFn(function(error) {
|
| + if (error) err = error;
|
| + testComplete = true;
|
| + });
|
| + } catch (error) {
|
| + err = error;
|
| + testComplete = true;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Triggers a flush of any pending events, observations, etc and calls you back
|
| + * after they have been processed.
|
| + *
|
| + * @param {function()} callback
|
| + */
|
| +window.flush = function flush(callback) {
|
| + // Ideally, this function would be a call to Polymer.dom.flush, but that doesn't
|
| + // support a callback yet (https://github.com/Polymer/polymer-dev/issues/851),
|
| + // ...and there's cross-browser flakiness to deal with.
|
| +
|
| + // Make sure that we're invoking the callback with no arguments so that the
|
| + // caller can pass Mocha callbacks, etc.
|
| + var done = function done() { callback(); };
|
| +
|
| + // Because endOfMicrotask is flaky for IE, we perform microtask checkpoints
|
| + // ourselves (https://github.com/Polymer/polymer-dev/issues/114):
|
| + var isIE = navigator.appName == 'Microsoft Internet Explorer';
|
| + if (isIE && window.Platform && window.Platform.performMicrotaskCheckpoint) {
|
| + var reallyDone = done;
|
| + done = function doneIE() {
|
| + Platform.performMicrotaskCheckpoint();
|
| + setTimeout$1(reallyDone, 0);
|
| + };
|
| + }
|
| +
|
| + // Everyone else gets a regular flush.
|
| + var scope;
|
| + if (window.Polymer && window.Polymer.dom && window.Polymer.dom.flush) {
|
| + scope = window.Polymer.dom;
|
| + } else if (window.Polymer && window.Polymer.flush) {
|
| + scope = window.Polymer;
|
| + } else if (window.WebComponents && window.WebComponents.flush) {
|
| + scope = window.WebComponents;
|
| + }
|
| + if (scope) {
|
| + scope.flush();
|
| + }
|
| +
|
| + // Ensure that we are creating a new _task_ to allow all active microtasks to
|
| + // finish (the code you're testing may be using endOfMicrotask, too).
|
| + setTimeout$1(done, 0);
|
| +};
|
| +
|
| +/**
|
| + * Advances a single animation frame.
|
| + *
|
| + * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially
|
| + * @param {function()} callback
|
| + */
|
| +window.animationFrameFlush = function animationFrameFlush(callback) {
|
| + flush(function() {
|
| + requestAnimationFrame(function() {
|
| + flush(callback);
|
| + });
|
| + });
|
| +};
|
| +
|
| +/**
|
| + * DEPRECATED: Use `flush`.
|
| + * @param {function} callback
|
| + */
|
| +window.asyncPlatformFlush = function asyncPlatformFlush(callback) {
|
| + console.warn('asyncPlatformFlush is deprecated in favor of the more terse flush()');
|
| + return window.flush(callback);
|
| +};
|
| +
|
| +/**
|
| + *
|
| + */
|
| +window.waitFor = function waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime) {
|
| + timeoutTime = timeoutTime || Date.now() + (timeout || 1000);
|
| + intervalOrMutationEl = intervalOrMutationEl || 32;
|
| + try {
|
| + fn();
|
| + } catch (e) {
|
| + if (Date.now() > timeoutTime) {
|
| + throw e;
|
| + } else {
|
| + if (isNaN(intervalOrMutationEl)) {
|
| + intervalOrMutationEl.onMutation(intervalOrMutationEl, function() {
|
| + waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
|
| + });
|
| + } else {
|
| + setTimeout$1(function() {
|
| + waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
|
| + }, intervalOrMutationEl);
|
| + }
|
| + return;
|
| + }
|
| + }
|
| + next();
|
| +};
|
| +
|
| +// You can configure WCT before it has loaded by assigning your custom
|
| +// configuration to the global `WCT`.
|
| +setup(window.WCT);
|
| +
|
| +// Maybe some day we'll expose WCT as a module to whatever module registry you
|
| +// are using (aka the UMD approach), or as an es6 module.
|
| +var WCT = window.WCT = {};
|
| +// A generic place to hang data about the current suite. This object is reported
|
| +// back via the `sub-suite-start` and `sub-suite-end` events.
|
| +WCT.share = {};
|
| +// Until then, we get to rely on it to expose parent runners to their children.
|
| +WCT._ChildRunner = ChildRunner;
|
| +WCT._config = _config;
|
| +
|
| +
|
| +// Public Interface
|
| +
|
| +/**
|
| + * Loads suites of tests, supporting both `.js` and `.html` files.
|
| + *
|
| + * @param {!Array.<string>} files The files to load.
|
| + */
|
| +WCT.loadSuites = loadSuites;
|
| +
|
| +
|
| +// Load Process
|
| +
|
| +listenForErrors();
|
| +stubInterfaces();
|
| +loadSync();
|
| +
|
| +// Give any scripts on the page a chance to declare tests and muck with things.
|
| +document.addEventListener('DOMContentLoaded', function() {
|
| + debug('DOMContentLoaded');
|
| +
|
| + ensureDependenciesPresent();
|
| +
|
| + // We need the socket built prior to building its reporter.
|
| + CLISocket.init(function(error, socket) {
|
| + if (error) throw error;
|
| +
|
| + // Are we a child of another run?
|
| + var current = ChildRunner.current();
|
| + var parent = current && current.parentScope.WCT._reporter;
|
| + debug('parentReporter:', parent);
|
| +
|
| + var childSuites = activeChildSuites();
|
| + var reportersToUse = determineReporters(socket, parent);
|
| + // +1 for any local tests.
|
| + var reporter = new MultiReporter(childSuites.length + 1, reportersToUse, parent);
|
| + WCT._reporter = reporter; // For environment/compatibility.js
|
| +
|
| + // We need the reporter so that we can report errors during load.
|
| + loadJsSuites(reporter, function(error) {
|
| + // Let our parent know that we're about to start the tests.
|
| + if (current) current.ready(error);
|
| + if (error) throw error;
|
| +
|
| + // Emit any errors we've encountered up til now
|
| + globalErrors.forEach(function onError(error) {
|
| + reporter.emitOutOfBandTest('Test Suite Initialization', error);
|
| + });
|
| +
|
| + runSuites(reporter, childSuites, function(error) {
|
| + // Make sure to let our parent know that we're done.
|
| + if (current) current.done();
|
| + if (error) throw error;
|
| + });
|
| + });
|
| + });
|
| +});
|
| +
|
| +}());
|
| +//# sourceMappingURL=browser.js.map
|
|
|