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

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

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698