Repository: Polymer/web-component-tester Branch: master Commit: e68b2cb5842f Files: 167 Total size: 409.9 KB Directory structure: gitextract_nxebno92/ ├── .clang-format ├── .github/ │ └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin/ │ ├── wct │ └── wct-st ├── bower.json ├── browser/ │ ├── childrunner.ts │ ├── clisocket.ts │ ├── config.ts │ ├── declarations.ts │ ├── environment/ │ │ ├── compatability.ts │ │ ├── errors.ts │ │ └── helpers.ts │ ├── environment.ts │ ├── index.ts │ ├── mocha/ │ │ ├── extend.ts │ │ ├── fixture.ts │ │ ├── replace.ts │ │ └── stub.ts │ ├── mocha.ts │ ├── more-declarations.ts │ ├── reporters/ │ │ ├── console.ts │ │ ├── html.ts │ │ ├── multi.ts │ │ └── title.ts │ ├── reporters.ts │ ├── suites.ts │ ├── tsconfig.json │ └── util.ts ├── browser-js-header.txt ├── browser.js ├── custom_typings/ │ ├── bower-config.d.ts │ ├── findup-sync.d.ts │ ├── promisify-node.d.ts │ ├── send.d.ts │ ├── server-destroy.d.ts │ ├── stacky.d.ts │ └── wd.d.ts ├── data/ │ ├── a11ySuite-npm-header.txt │ ├── a11ySuite.js │ └── index.html ├── gulpfile.js ├── package.json ├── runner/ │ ├── browserrunner.ts │ ├── cli.ts │ ├── clireporter.ts │ ├── config.ts │ ├── context.ts │ ├── gulp.ts │ ├── httpbin.ts │ ├── paths.ts │ ├── plugin.ts │ ├── port-scanner.ts │ ├── steps.ts │ ├── test.ts │ └── webserver.ts ├── runner.js ├── tasks/ │ └── test.js ├── test/ │ ├── fixtures/ │ │ ├── cli/ │ │ │ ├── conf/ │ │ │ │ ├── branch/ │ │ │ │ │ └── leaf/ │ │ │ │ │ └── thing.js │ │ │ │ ├── json/ │ │ │ │ │ ├── wct.conf.js │ │ │ │ │ └── wct.conf.json │ │ │ │ ├── rooted/ │ │ │ │ │ └── wct.conf.js │ │ │ │ ├── test/ │ │ │ │ │ └── foo.js │ │ │ │ └── wct.conf.js │ │ │ └── standard/ │ │ │ ├── test/ │ │ │ │ ├── a.html │ │ │ │ └── b.js │ │ │ └── x-foo.html │ │ ├── early-failure/ │ │ │ ├── bower_components/ │ │ │ │ └── web-component-tester/ │ │ │ │ └── package.json │ │ │ └── test/ │ │ │ └── index.html │ │ ├── fake-packages/ │ │ │ ├── duplicated-dep/ │ │ │ │ └── package.json │ │ │ └── singleton-dep/ │ │ │ └── package.json │ │ ├── integration/ │ │ │ ├── compilation/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ └── index.html │ │ │ ├── components_dir/ │ │ │ │ ├── bower_components/ │ │ │ │ │ └── foo-element/ │ │ │ │ │ └── foo-element.js │ │ │ │ ├── golden.json │ │ │ │ ├── test/ │ │ │ │ │ └── index.html │ │ │ │ └── wct.conf.json │ │ │ ├── custom-components_dir/ │ │ │ │ ├── .bowerrc │ │ │ │ ├── golden.json │ │ │ │ ├── my_components/ │ │ │ │ │ └── bar-element/ │ │ │ │ │ └── bar-element.js │ │ │ │ └── test/ │ │ │ │ └── index.html │ │ │ ├── custom-multiple-component_dirs/ │ │ │ │ ├── .bowerrc │ │ │ │ ├── golden.json │ │ │ │ ├── my_components/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ ├── my_components-bar/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ ├── my_components-foo/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ └── test/ │ │ │ │ └── index.html │ │ │ ├── define-webserver-hook/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ ├── index.html │ │ │ │ └── tests.html │ │ │ ├── failing/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ ├── index.html │ │ │ │ ├── tests.html │ │ │ │ └── tests.js │ │ │ ├── missing/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ └── missing.html │ │ │ ├── mixed-suites/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ ├── index.html │ │ │ │ ├── one.html │ │ │ │ ├── one.js │ │ │ │ ├── two.html │ │ │ │ └── two.js │ │ │ ├── multiple-component_dirs/ │ │ │ │ ├── bower_components/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ ├── bower_components-bar/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ ├── bower_components-foo/ │ │ │ │ │ └── package/ │ │ │ │ │ └── index.js │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ └── index.html │ │ │ ├── multiple-replace/ │ │ │ │ ├── dom-if-element.html │ │ │ │ ├── dom-repeat-fixture.html │ │ │ │ ├── exception-element.html │ │ │ │ ├── exception-fixture.html │ │ │ │ ├── golden.json │ │ │ │ ├── normal-element.html │ │ │ │ ├── projection-element-2.html │ │ │ │ ├── projection-element-3.html │ │ │ │ ├── projection-element-4.html │ │ │ │ ├── projection-element.html │ │ │ │ └── test/ │ │ │ │ ├── index.html │ │ │ │ └── tests.html │ │ │ ├── nested/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ ├── index.html │ │ │ │ ├── leaf.html │ │ │ │ ├── leaf.js │ │ │ │ ├── one/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── tests.html │ │ │ │ └── two/ │ │ │ │ └── index.html │ │ │ ├── no-tests/ │ │ │ │ ├── golden.json │ │ │ │ └── test/ │ │ │ │ └── index.html │ │ │ └── query-string/ │ │ │ ├── golden.json │ │ │ └── test/ │ │ │ ├── index.html │ │ │ ├── tests.html │ │ │ └── tests.js │ │ └── paths/ │ │ ├── bar/ │ │ │ ├── a.js │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── baz/ │ │ │ ├── a/ │ │ │ │ └── fizz.html │ │ │ ├── a.html │ │ │ ├── b/ │ │ │ │ ├── deep/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── stuff.html │ │ │ │ │ └── stuff.js │ │ │ │ ├── index.html │ │ │ │ └── one.js │ │ │ └── b.js │ │ ├── foo/ │ │ │ ├── one.js │ │ │ ├── three.css │ │ │ └── two.html │ │ ├── foo.html │ │ └── foo.js │ ├── integration/ │ │ ├── browser.ts │ │ └── setup_test_dir.ts │ └── unit/ │ ├── cli.ts │ ├── config.ts │ ├── context.ts │ ├── grunt.ts │ ├── gulp.ts │ └── paths.ts ├── tsconfig.json ├── tslint.json └── wct-browser-legacy/ ├── a11ySuite.js ├── browser.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: Google AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE ================================================ - [ ] CHANGELOG.md has been updated ================================================ FILE: .gitignore ================================================ # Update .npmignore whenever you update this file! .todo /bower_components /node_modules package-lock.json npm-debug.log typings/ runner/*.js runner/*.d.ts runner/*.js.map test/unit/*.js test/unit/*.d.ts test/unit/*.js.map test/integration/*.js test/integration/*.d.ts test/integration/*.js.map test/fixtures/integration/temp browser/*.js browser/*.js.map browser/environment/*.js* browser/mocha/*.js* browser/reporters/*.js* ================================================ FILE: .npmignore ================================================ # Update .gitignore whenever you update this file! .todo bower_components node_modules npm-debug.log typings/ # Don't ignore runner/*.js ================================================ FILE: .travis.yml ================================================ sudo: required dist: trusty addons: firefox: latest apt: sources: - google-chrome packages: - google-chrome-stable sauce_connect: true language: node_js node_js: - '6' - '8' script: - xvfb-run npm test env: global: - MOCHA_TIMEOUT=300000 - secure: WqREPJCj0auiij7P86MfTpD5+buf7vlAuRiUILmcq0+rhVFvsgqG82NlKIjcQuCzM9WFMqBHxDyV0/iyfE5qiQsEXYd7g3SwMVFIAS3R8sKnMz/EMA0kCqFCGn14iwbakNduSuyjQhxHje72iczm/3S4nKFceOzDGFlnvGLxpTA= - secure: D5P88V2PQF6mq/bPHlpzW1DgPSz4rKaoXJ1SNIsdeqUnd8xN+tmVUBO0/skunwIPIdws0y7cLfZOThEJuRMoDMUal0Fh81UcWna+yfM5N5NYziyCsOcf60UCZLpWdvS8QNEL0zGff6apO/TEaUkgYx7IMdzi0Bu+56v/iybwM3Q= ================================================ FILE: .vscode/settings.json ================================================ // Place your settings in this file to overwrite default and user settings. { "clang-format.style": "file", "editor.formatOnSave": true, "editor.formatOnType": true, "files.exclude": { "runner/*.js": true, "runner/*.d.ts": true, "runner/*.js.map": true, "test/unit/*.js": true, "test/unit/*.d.ts": true, "test/unit/*.js.map": true, "test/integration/*.js": true, "test/integration/*.d.ts": true, "test/integration/*.js.map": true, "browser.js": true, "browser.js.map": true, "browser/**/*.js": true, "browser/**/*.js.map": true }, "typescript.tsdk": "./node_modules/typescript/lib" } ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## 6.6.0-pre.5 - 2018-04-12 * Restore missing dependency 'findup-sync'. ## 6.6.0-pre.4 - 2018-04-11 * Upgrade polyserve to pick up recent fixes. ## 6.6.0-pre.3 - 2018-03-21 * Upgrade polyserve to ^0.25.2 and add the --module-resolution flag * Fix #488 - Support .bowerrc directory name override of bower_components, including variants * Injected libraries are now resolved to correct paths while using the runner. Note: simply serving the test file and running it will attempt to find the files, but they may load the wrong versions (e.g. lodash 4 might load instead of 3) * Not published: 6.6.0-pre.1 & 6.6.0-pre.2 ## 6.5.0 - 2018-01-17 * Upgrade wct-local to 2.1.0 to get support for headless Chrome. ## 6.4.3 - 2018-01-11 * web-component-tester: no longer injects `a11ySuite.js` script in `--npm` mode. * wct-browser-legacy: `a11ySuite.js` now exports a `a11ySuite` reference. Import this reference direction to use `a11ySuite()` in npm. ## 6.4.2 - 2018-01-09 * Upgrade wct-sauce to 2.0.0 to get updated browsers lists to include Safari 11 and Edge 15. * Fixed #523 WCT ignores the webserver hostname * Remove `overflow-y: auto` from test runner styling to increase performance. ## 6.4.1 - 2017-11-20 * Ensure that WCT is installed with compatible versions of wct-local and wct-sauce. This fixes a bug where – if incompatible versions are installed – they aren't able to coordinate shutdown, so WCT hangs after successfully completing a test run. ## 6.4.0 - 2017-10-31 🎃 * Updated package.json: * Upgraded dependencies async, chai, cleankill, findup-sync, sinon, and socket.io. * Upgraded devDependencies update-notifier * Added `define:webserver` hook to enable substitution of the generated express app for the webserver through a callback, to support use cases where a plugin might want to inject handlers or middleware in front of polyserve. * Added support for `proxy: {path: string, target: string}` config which is forwarded to `polyserve`. ## 6.3.0 - 2017-10-02 * Updated wct-browser-legacy to use a module version of a11ySuite to get access to Polymer.dom.flush. * Updated generated index for webserver to use a11ySuite as a module. * Updated polyserve to get support for on-the-fly module compilation and ``). * Browser options can be specified in `wct.conf.js` via the `clientOptions` key. * WCT now always generates an index when run via the command line. * `wct.conf.json` can be used as an alternative to `wct.conf.js`. ## 3.0.0-3.0.6 Yanked. See `3.0.7` for rolled up notes. # 2.x There were changes made, and @nevir failed to annotate them. What a slacker. # 1.x What are you, an archaeologist? ================================================ FILE: LICENSE ================================================ Copyright (c) 2015 The Polymer Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ ## 🚨 Moved to [`Polymer/tools/packages/web-component-tester`][1] 🚨 The [`Polymer/web-component-tester`][2] repo has been migrated to [`packages/web-component-tester`][1] folder of the [`Polymer/tools`][3] 🚝 *monorepo*. We are *actively* working on migrating open Issues and PRs to the new repo. New Issues and PRs should be filed at [`Polymer/tools`][3]. [1]: https://github.com/Polymer/tools/tree/master/packages/web-component-tester [2]: https://github.com/Polymer/web-component-tester [3]: https://github.com/Polymer/tools ================================================ FILE: bin/wct ================================================ #!/usr/bin/env node /** * @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 */ var resolve = require('resolve'); process.title = 'wct'; resolve('web-component-tester', {basedir: process.cwd()}, function(error, path) { var wct = path ? require(path) : require('..'); var promise = wct.cli.run(process.env, process.argv.slice(2), process.stdout, function (error) { process.exit(error ? 1 : 0); }); if (promise) { promise.then(() => process.exit(0), () => process.exit(1)); } }); ================================================ FILE: bin/wct-st ================================================ #!/usr/bin/env node /** * @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 */ var resolve = require('resolve'); process.title = 'wct-st'; resolve('web-component-tester', {basedir: process.cwd()}, function(error, path) { var wct = path ? require(path) : require('..'); var promise = wct.cli.runSauceTunnel(process.env, process.argv.slice(2), process.stdout, function (error) { process.exit(error ? 1 : 0); }); if (promise) { promise.then(() => process.exit(0), () => process.exit(1)); } }); ================================================ FILE: bower.json ================================================ { "name": "web-component-tester", "description": "web-component-tester makes testing your web components a breeze!", "version": "6.0.0", "main": [ "browser.js" ], "license": "http://polymer.github.io/LICENSE.txt", "ignore": [ "*", "!/data/*", "!/browser.js", "!/browser.js.map", "!/package.json", "!/bower.json" ], "keywords": [ "browser", "grunt", "gruntplugin", "gulp", "polymer", "test", "testing", "web component", "web" ], "dependencies": { "accessibility-developer-tools": "^2.10.0", "async": "^1.5.0", "chai": "^3.2.0", "lodash": "^3.7.0", "mocha": "^3.1.2", "sinon-chai": "^2.7.0", "sinonjs": "^1.14.1", "stacky": "^1.3.0", "test-fixture": "^3.0.0" }, "devDependencies": { "polymer": "Polymer/polymer#^1.5.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.22" } } ================================================ FILE: browser/childrunner.ts ================================================ /** * @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 */ import * as util from './util.js'; // 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. export interface SharedState {} /** * A Mocha suite (or suites) run within a child iframe, but reported as if they * are part of the current context. */ export default class ChildRunner { private url: string; parentScope: Window; private state: 'initializing'|'loading'|'complete'; private iframe?: HTMLIFrameElement; private onRunComplete: (error?: any) => void; private timeoutId: undefined|number; private share: SharedState; constructor(url: string, parentScope: Window) { const urlBits = util.parseUrl(url); util.mergeParams( urlBits.params, util.getParams(parentScope.location.search)); delete urlBits.params.cli_browser_id; this.url = urlBits.base + util.paramsToQuery(urlBits.params); this.parentScope = parentScope; this.state = 'initializing'; } // ChildRunners get a pretty generous load timeout by default. static loadTimeout = 60000; // We can't maintain properties on iframe elements in Firefox/Safari/???, so // we track childRunners by URL. static _byUrl: {[url: string]: undefined|ChildRunner} = {}; /** * @return {ChildRunner} The `ChildRunner` that was registered for this * window. */ static current(): ChildRunner { 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`. */ static get(target: Window, traversal?: boolean): ChildRunner { const 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. */ run(done: (error?: any) => void) { util.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'); let 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. */ loaded(error: any) { util.debug('ChildRunner#loaded', this.url, error); if (this.iframe.contentWindow == null && error) { this.signalRunComplete(error); this.done(); return; } // 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. */ ready(error?: any) { util.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. */ done() { util.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; this.share = null; }.bind(this), 1); } signalRunComplete(error?: any) { if (!this.onRunComplete) return; this.state = 'complete'; this.onRunComplete(error); this.onRunComplete = null; } } ================================================ FILE: browser/clisocket.ts ================================================ /** * @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 */ import ChildRunner from './childrunner.js'; import * as util from './util.js'; const SOCKETIO_ENDPOINT = window.location.protocol + '//' + window.location.host; const 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. */ export default class CLISocket { private readonly socket: SocketIO.Socket; private readonly browserId: string; constructor(browserId: string, socket: SocketIO.Socket) { this.browserId = browserId; this.socket = socket; } /** * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting * interesting events back to the CLI runner. */ observe(runner: Mocha.IRunner) { 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', (test: Mocha.IRunnable) => { this.emitEvent('test-start', {test: getTitles(test)}); }); runner.on('test end', (test: Mocha.IRunnable) => { this.emitEvent('test-end', { state: getState(test), test: getTitles(test), duration: (test as any).duration, error: (test as any).err, }); }); runner.on('fail', (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); } }); runner.on('childRunner start', (childRunner) => { this.emitEvent('sub-suite-start', childRunner.share); }); runner.on('childRunner end', (childRunner) => { this.emitEvent('sub-suite-end', childRunner.share); }); runner.on('end', () => { this.emitEvent('browser-end'); }); } /** * @param {string} event The name of the event to fire. * @param {*} data Additional data to pass with the event. */ emitEvent(event: string, data?: any) { 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. */ static init(done: (error?: any, socket?: CLISocket) => void) { const browserId = util.getParam('cli_browser_id'); if (!browserId) return done(); // Only fire up the socket for root runners. if (ChildRunner.current()) return done(); util.loadScript(SOCKETIO_LIBRARY, function(error: any) { if (error) return done(error); const socket = io(SOCKETIO_ENDPOINT); socket.on('error', function(error?: any) { socket.off(); done(error); }); socket.on('connect', function() { socket.off(); done(null, new CLISocket(browserId, socket as any)); }); }); } } // Misc Utility /** * @param {!Mocha.Runnable} runnable The test or suite to extract titles from. * @return {!Array.} The titles of the runnable and its parents. */ function getTitles(runnable: Mocha.IRunnable) { const titles = []; while (runnable && !runnable.root && runnable.title) { titles.unshift(runnable.title); runnable = runnable.parent as any; } return titles; } /** * @param {!Mocha.Runnable} runnable * @return {string} */ function getState(runnable: Mocha.IRunnable) { if (runnable.state === 'passed') { return 'passing'; } else if (runnable.state === 'failed') { return 'failing'; } else if (runnable.pending) { return 'pending'; } else { return 'unknown'; } } ================================================ FILE: browser/config.ts ================================================ /** * @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 */ import ChildRunner from './childrunner.js'; import * as util from './util.js'; export interface Config { /** * `.js` scripts to be loaded (synchronously) before WCT starts in earnest. * * Paths are relative to `scriptPrefix`. */ environmentScripts: string[]; environmentImports: string[]; /** Absolute root for client scripts. Detected in `setup()` if not set. */ root: null|string; /** By default, we wait for any web component frameworks to load. */ waitForFrameworks: boolean; /** * Alternate callback for waiting for tests. * `this` for the callback will be the window currently running tests. */ waitFor: null|Function; /** How many `.html` suites that can be concurrently loaded & run. */ numConcurrentSuites: number; /** Whether `console.error` should be treated as a test failure. */ trackConsoleError: boolean; /** Configuration passed to mocha.setup. */ mochaOptions: MochaSetupOptions; /** Whether WCT should emit (extremely verbose) debugging log messages. */ verbose: boolean; } /** * The global configuration state for WCT's browser client. */ export let _config: Config = { environmentScripts: !!window.__wctUseNpm ? [ 'stacky/browser.js', 'async/lib/async.js', 'lodash/index.js', 'mocha/mocha.js', 'chai/chai.js', '@polymer/sinonjs/sinon.js', 'sinon-chai/lib/sinon-chai.js', 'accessibility-developer-tools/dist/js/axs_testing.js', '@polymer/test-fixture/test-fixture.js' ] : [ '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: !!window.__wctUseNpm ? [] : ['test-fixture/test-fixture.html'], root: null as null | string, waitForFrameworks: true, waitFor: null as null | Function, numConcurrentSuites: 1, trackConsoleError: true, mochaOptions: {timeout: 10 * 1000}, verbose: false, }; /** * Merges initial `options` into WCT's global configuration. * * @param {Object} options The options to merge. See `browser/config.js` for a * reference. */ export function setup(options: Config) { const 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. const root = util.scriptPrefix('browser.js'); _config.root = util.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. */ export function get(key: K): Config[K] { return _config[key]; } // Internal function _deepMerge(target: Partial, source: Config) { 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]; } }); } ================================================ FILE: browser/declarations.ts ================================================ import * as ChaiStatic from 'chai'; import * as SinonStatic from 'sinon'; import * as SocketIOStatic from 'socket.io'; import * as StackyStatic from 'stacky'; import {default as ChildRunner, SharedState} from './childrunner.js'; import {Config} from './config.js'; import MultiReporter from './reporters/multi.js'; import * as suites from './suites.js'; type loadSuitesType = (typeof suites.loadSuites); declare global { interface Window { __wctUseNpm?: boolean; WebComponents?: WebComponentsStatic; Platform?: PlatformStatic; Polymer?: PolymerStatic; WCT: { readonly _ChildRunner: typeof ChildRunner; // readonly share: SharedState; // readonly _config: Config; // readonly loadSuites: loadSuitesType; _reporter: MultiReporter; }; mocha: typeof mocha; Mocha: typeof Mocha; __generatedByWct?: boolean; chai: typeof ChaiStatic; assert: typeof ChaiStatic.assert; expect: typeof ChaiStatic.expect; } interface WebComponentsStatic { ready?(): void; flush?(): void; } interface PlatformStatic { performMicrotaskCheckpoint(): void; } interface PolymerElement { _stampTemplate?(): void; } interface PolymerElementConstructor { prototype: PolymerElement; } interface PolymerStatic { flush(): void; dom: {flush(): void}; Element: PolymerElementConstructor; } interface Element { isConnected?: boolean; } interface Mocha { suite: Mocha.ISuite; } var Stacky: typeof StackyStatic; var io: typeof SocketIOStatic; var Platform: PlatformStatic; var sinon: typeof SinonStatic; } ================================================ FILE: browser/environment/compatability.ts ================================================ /** * @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 */ import ChildRunner from '../childrunner.js'; // polymer-test-tools (and Polymer/tools) support HTML tests where each file is // expected to call `done()`, which posts a message to the parent window. window.addEventListener('message', function(event) { if (!event.data || (event.data !== 'ok' && !event.data.error)) { return; } const childRunner = ChildRunner.get(event.source); if (!childRunner) { return; } childRunner.ready(); // The name of the suite as exposed to the user. const reporter = childRunner.parentScope.WCT._reporter; const title = reporter.suiteTitle(event.source.location); reporter.emitOutOfBandTest( 'page-wide tests via global done()', event.data.error, title, true); childRunner.done(); }); // Attempt to ensure that we complete a test suite if it is interrupted by a // document unload. window.addEventListener('unload', function(_event: BeforeUnloadEvent) { // Mocha's hook queue is asynchronous; but we want synchronous behavior if // we've gotten to the point of unloading the document. Mocha.Runner.immediately = function(callback: () => void) { callback(); }; }); ================================================ FILE: browser/environment/errors.ts ================================================ /** * @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 */ import * as config from '../config.js'; // 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. export let globalErrors: any[] = []; /** * Hook the environment to pick up on global errors. */ export function listenForErrors() { window.addEventListener('error', function(event) { globalErrors.push(event.error); }); // Also, we treat `console.error` as a test failure. Unless you prefer not. const origConsole = console; const origError = console.error; console.error = function wctShimmedError() { origError.apply(origConsole, arguments); if (config.get('trackConsoleError')) { throw 'console.error: ' + Array.prototype.join.call(arguments, ' '); } }; } ================================================ FILE: browser/environment/helpers.ts ================================================ /** * @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 */ export {}; // Make sure that we use native timers, in case they're being stubbed out. const nativeSetInterval = window.setInterval; const nativeSetTimeout = window.setTimeout; const nativeRequestAnimationFrame = window.requestAnimationFrame; /** * 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 */ function safeStep(callback: (error?: any) => void, stepFn: () => void) { let 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. */ function testImmediate(name: string, testFn: Function) { if (testFn.length > 0) { return testImmediateAsync(name, testFn); } let err: any; 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 */ function testImmediateAsync(name: string, testFn: Function) { let testComplete = false; let err: any; test(name, function(done) { const intervalId = nativeSetInterval(function() { if (!testComplete) return; clearInterval(intervalId); done(err); }, 10); }); try { testFn(function(error: any) { 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 */ function flush(callback: () => void) { // 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. let done = function done() { callback(); }; // Because endOfMicrotask is flaky for IE, we perform microtask checkpoints // ourselves (https://github.com/Polymer/polymer-dev/issues/114): const isIE = navigator.appName === 'Microsoft Internet Explorer'; if (isIE && window.Platform && window.Platform.performMicrotaskCheckpoint) { const reallyDone = done; done = function doneIE() { Platform.performMicrotaskCheckpoint(); nativeSetTimeout(reallyDone, 0); }; } // Everyone else gets a regular flush. let 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). nativeSetTimeout(done, 0); } /** * Advances a single animation frame. * * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially * @param {function()} callback */ function animationFrameFlush(callback: () => void) { flush(function() { nativeRequestAnimationFrame(function() { flush(callback); }); }); } /** * DEPRECATED: Use `flush`. * @param {function} callback */ function asyncPlatformFlush(callback: () => void) { console.warn( 'asyncPlatformFlush is deprecated in favor of the more terse flush()'); return window.flush(callback); } export interface MutationEl { onMutation(mutationEl: this, cb: () => void): void; } /** * */ function waitFor( fn: () => void, next: () => void, intervalOrMutationEl: number|MutationEl, timeout: number, timeoutTime: number) { timeoutTime = timeoutTime || Date.now() + (timeout || 1000); intervalOrMutationEl = intervalOrMutationEl || 32; try { fn(); } catch (e) { if (Date.now() > timeoutTime) { throw e; } else { if (typeof intervalOrMutationEl !== 'number') { intervalOrMutationEl.onMutation(intervalOrMutationEl, function() { waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime); }); } else { nativeSetTimeout(function() { waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime); }, intervalOrMutationEl); } return; } } next(); } declare global { interface Window { safeStep: typeof safeStep; testImmediate: typeof testImmediate; testImmediateAsync: typeof testImmediateAsync; flush: typeof flush; animationFrameFlush: typeof animationFrameFlush; asyncPlatformFlush: typeof asyncPlatformFlush; waitFor: typeof waitFor; } } window.safeStep = safeStep; window.testImmediate = testImmediate; window.testImmediateAsync = testImmediateAsync; window.flush = flush; window.animationFrameFlush = animationFrameFlush; window.asyncPlatformFlush = asyncPlatformFlush; window.waitFor = waitFor; ================================================ FILE: browser/environment.ts ================================================ /** * @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 */ import * as config from './config.js'; import * as reporters from './reporters.js'; import * as util from './util.js'; /** * Loads all environment scripts ...synchronously ...after us. */ export function loadSync() { util.debug('Loading environment scripts:'); const a11ySuiteScriptPath = 'web-component-tester/data/a11ySuite.js'; const scripts = config.get('environmentScripts'); const a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySuiteScriptPath) > -1; // We can't inject a11ySuite when running the npm version because it is a // module-based script that needs `'); // jshint ignore:line }); util.debug('Environment scripts loaded'); const imports = config.get('environmentImports'); imports.forEach(function(path) { const url = util.expandUrl(path, config.get('root')); util.debug('Loading environment import:', url); // Synchronous load. document.write( ''); // jshint ignore:line }); util.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. */ export function ensureDependenciesPresent() { _ensureMocha(); _checkChai(); } function _ensureMocha() { const 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'); } reporters.injectMocha(Mocha); // Magic loading of mocha's stylesheet const mochaPrefix = util.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) { util.loadStyle(mochaPrefix + 'mocha.css'); } } function _checkChai() { if (!window.chai) { util.debug('Chai not present; not registering shorthands'); return; } window.assert = window.chai.assert; window.expect = window.chai.expect; } ================================================ FILE: browser/index.ts ================================================ /** * @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 the entry point into `web-component-tester`'s browser client. */ // Registers a bunch of globals: import './environment/helpers.js'; import ChildRunner from './childrunner.js'; import CLISocket from './clisocket.js'; import * as config from './config.js'; import * as environment from './environment.js'; import * as errors from './environment/errors.js'; import * as mocha from './mocha.js'; import * as reporters from './reporters.js'; import MultiReporter from './reporters/multi.js'; import * as suites from './suites.js'; import * as util from './util.js'; // You can configure WCT before it has loaded by assigning your custom // configuration to the global `WCT`. config.setup(window.WCT as any as config.Config); // 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. const 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. share: {}, // Until then, we get to rely on it to expose parent runners to their // children. _ChildRunner: ChildRunner, _reporter: undefined as any, // assigned below _config: config._config, // Public API /** * Loads suites of tests, supporting both `.js` and `.html` files. * * @param {!Array.} files The files to load. */ loadSuites: suites.loadSuites, }; // Load Process errors.listenForErrors(); mocha.stubInterfaces(); environment.loadSync(); // Give any scripts on the page a chance to declare tests and muck with things. document.addEventListener('DOMContentLoaded', function() { util.debug('DOMContentLoaded'); environment.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? const current = ChildRunner.current(); const parent = current && current.parentScope.WCT._reporter; util.debug('parentReporter:', parent); const childSuites = suites.activeChildSuites(); const reportersToUse = reporters.determineReporters(socket, parent); // +1 for any local tests. const 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. suites.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 errors.globalErrors.forEach(function onError(error) { reporter.emitOutOfBandTest('Test Suite Initialization', error); }); suites.runSuites(reporter, childSuites, function(error) { // Make sure to let our parent know that we're done. if (current) current.done(); if (error) throw error; }); }); }); }); ================================================ FILE: browser/mocha/extend.ts ================================================ const interfaceExtensions: Array<() => void> = []; /** * 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. */ export function extendInterfaces( helperName: string, helperFactory: ( context: any, teardown: (cb: () => void) => void, interfaceName: 'tdd'|'bdd') => void) { interfaceExtensions.push(function() { const Mocha = window.Mocha; // For all Mocha interfaces (probably just TDD and BDD): Object.keys((Mocha as any).interfaces) .forEach(function(interfaceName: 'tdd'|'bdd') { // This is the original callback that defines the interface (TDD or // BDD): const originalInterface = (Mocha as any).interfaces[interfaceName]; // This is the name of the "teardown" or "afterEach" property for the // current interface: const 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 as any).interfaces[interfaceName] = function(suite: any) { // 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: any, _file: string, _mocha: any) { // Capture a bound reference to the teardown function as a // convenience: const 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. */ export function applyExtensions() { interfaceExtensions.forEach(function(applyExtension) { applyExtension(); }); } ================================================ FILE: browser/mocha/fixture.ts ================================================ import {extendInterfaces} from './extend.js'; interface TestFixture extends HTMLElement { create(model: object): HTMLElement; restore(): void; } 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: string, model: object) { // Automatically register a teardown callback that will restore the // test-fixture: teardown(function() { (document.getElementById(fixtureId) as TestFixture).restore(); }); // Find the test-fixture with the provided ID and create it, returning // the results: return (document.getElementById(fixtureId) as TestFixture).create(model); }; }); ================================================ FILE: browser/mocha/replace.ts ================================================ import {extendInterfaces} from './extend.js'; // replacement map stores what should be let replacements = {}; let 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: string) { return { with: function(tagName: string) { // 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 as any).isSinonProxy) { return; } if (!window.Polymer.Element) { window.Polymer.Element = function() {}; window.Polymer.Element.prototype._stampTemplate = function() {}; } // Keep a reference to the original `document.importNode` // implementation for later: const originalImportNode = document.importNode; // Use Sinon to stub `document.ImportNode`: sinon.stub( document, 'importNode', function(origContent: any, deep: boolean) { const templateClone = document.createElement('template'); const content = templateClone.content; const 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. const nodeIterator = document.createNodeIterator( content, NodeFilter.SHOW_ELEMENT, null, true); let 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() as Element) { let 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: const replacement = document.createElement(currentTagName); // For all attributes in the original node.. for (let 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`: const documentImportNode = document.importNode as any; if (documentImportNode.isSinonProxy) { documentImportNode.restore(); } // Empty the replacement map replacements = {}; }); } } }; }; }); ================================================ FILE: browser/mocha/stub.ts ================================================ import {extendInterfaces} from './extend.js'; /** * 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: string, implementation: object) { // Find the prototype of the element being stubbed: const proto = document.createElement(tagName).constructor.prototype; // For all keys in the implementation to stub with.. const 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(); }); }); }; }); ================================================ FILE: browser/mocha.ts ================================================ /** * @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 */ import './mocha/fixture.js'; import './mocha/stub.js'; import './mocha/replace.js'; import * as config from './config.js'; import {applyExtensions} from './mocha/extend.js'; // Mocha global helpers, broken out by testing method. // // Keys are the method for a particular interface; values are their analog in // the opposite interface. const 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. */ export function stubInterfaces() { const keys = Object.keys(MOCHA_EXPORTS) as Array; keys.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` const _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: 'tdd'|'bdd', key: string, alternate: 'string') { const mochaOptions = config.get('mochaOptions'); if (mochaOptions.ui && mochaOptions.ui !== ui) { const 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. } ================================================ FILE: browser/more-declarations.ts ================================================ declare namespace Mocha { interface UtilsStatic { highlightTags(somethingSomething: string): void; } let utils: UtilsStatic; interface IRunner extends NodeJS.EventEmitter { name?: string; total: number; } interface IRunnable { parent: ISuite; root: boolean; state: 'passed'|'failed'|undefined; pending: boolean; } interface ISuite { root: boolean; } let Runner: {prototype: IRunner; immediately(callback: () => void): void}; } declare namespace SocketIO { interface Server { off(): void; } } ================================================ FILE: browser/reporters/console.ts ================================================ /** * @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 */ import * as util from '../util.js'; // We capture console events when running tests; so make sure we have a // reference to the original one. const console = window.console; const FONT = ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-serif;'; const 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... const userAgent = navigator.userAgent.toLowerCase(); const CAN_STYLE_LOG = userAgent.match('firefox') || userAgent.match('webkit'); const CAN_STYLE_GROUP = userAgent.match('webkit'); // Track the indent for faked `console.group` let logIndent = ''; function log(text: string, style?: keyof typeof STYLES) { text = text.split('\n') .map(function(l) { return logIndent + l; }) .join('\n'); if (CAN_STYLE_LOG) { console.log('%c' + text, STYLES[style] || STYLES.plain); } else { console.log(text); } } function logGroup(text: string, style?: keyof typeof STYLES) { if (CAN_STYLE_GROUP) { console.group('%c' + text, STYLES[style] || STYLES.plain); } else if (console.group) { console.group(text); } else { logIndent = logIndent + ' '; log(text, style); } } function logGroupEnd() { if (console.groupEnd) { console.groupEnd(); } else { logIndent = logIndent.substr(0, logIndent.length - 2); } } function logException(error: Error) { log(error.stack || error.message || (error + ''), 'stack'); } /** * A Mocha reporter that logs results out to the web `console`. */ export default class Console { /** * @param runner The runner that is being reported on. */ constructor(runner: Mocha.IRunner) { Mocha.reporters.Base.call(this, runner); runner.on('suite', function(suite: Mocha.ISuite) { if (suite.root) { return; } logGroup(suite.title, 'suite'); }.bind(this)); runner.on('suite end', function(suite: Mocha.ISuite) { if (suite.root) { return; } logGroupEnd(); }.bind(this)); runner.on('test', function(test: Mocha.ITest) { logGroup(test.title, 'test'); }.bind(this)); runner.on('pending', function(test: Mocha.ITest) { logGroup(test.title, 'pending'); }.bind(this)); runner.on('fail', function(_test: Mocha.ITest, error: any) { logException(error); }.bind(this)); runner.on('test end', function(_test: Mocha.ITest) { logGroupEnd(); }.bind(this)); runner.on('end', this.logSummary.bind(this)); } /** Prints out a final summary of test results. */ logSummary() { logGroup('Test Results', 'results'); if (this.stats.failures > 0) { log(util.pluralizedStat(this.stats.failures, 'failing'), 'failing'); } if (this.stats.pending > 0) { log(util.pluralizedStat(this.stats.pending, 'pending'), 'pending'); } log(util.pluralizedStat(this.stats.passes, 'passing')); if (!this.stats.failures) { log('test suite passed', 'passing'); } log('Evaluated ' + this.stats.tests + ' tests in ' + (this.stats as any).duration + 'ms.'); logGroupEnd(); } } export default interface Console extends Mocha.reporters.Base {} ================================================ FILE: browser/reporters/html.ts ================================================ /** * @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. */ export default function HTML(runner: Mocha.IRunner) { const output = document.createElement('div'); output.id = 'mocha'; document.body.appendChild(output); runner.on('suite', function(_test: any) { 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. const 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; 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); ================================================ FILE: browser/reporters/multi.ts ================================================ /** * @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 */ import * as util from '../util.js'; const STACKY_CONFIG = { indent: ' ', locationStrip: [ /^https?:\/\/[^\/]+/, /\?.*$/, ], filter(line: {location: string}) { return !!line.location.match(/\/web-component-tester\/[^\/]+(\?.*)?$/); }, }; // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36-46 const 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. const ESTIMATED_TESTS_PER_SUITE = 3; export interface Reporter {} export interface ReporterFactory { new(parent: MultiReporter): Reporter; } interface ExtendedTest extends Mocha.ITest { err: any; } /** * A Mocha-like reporter that combines the output of multiple Mocha suites. */ export default class MultiReporter implements Reporter { private readonly reporters: ReadonlyArray; private readonly parent: MultiReporter|undefined; private readonly basePath: string; total: number; private currentRunner: null|Mocha.IRunner; /** Arguments that would be called on emit(). */ private pendingEvents: Array; private complete: boolean|undefined; /** * @param numSuites The number of suites that will be run, in order to * estimate the total number of tests that will be performed. * @param reporters The set of reporters that * should receive the unified event stream. * @param parent The parent reporter, if present. */ constructor( numSuites: number, reporters: ReporterFactory[], parent: MultiReporter|undefined) { this.reporters = reporters.map((reporter) => { return new reporter(this); }); this.parent = parent; this.basePath = parent && parent.basePath || util.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 The location this reporter represents. * @return A reporter-like "class" for each child suite * that should be passed to `mocha.run`. */ childReporter(location: Location|string): ReporterFactory { const name = this.suiteTitle(location); // The reporter is used as a constructor, so we can't depend on `this` being // properly bound. const self = this; return class ChildReporter { constructor(runner: Mocha.IRunner) { runner.name = window.name; self.bindChildRunner(runner); } static title = window.name; }; } /** Must be called once all runners have finished. */ 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 title The title of the test. * @param error An error associated with this test. If falsy, test is * considered to be passing. * @param suiteTitle Title for the suite that's wrapping the test. * @param estimated If this test was included in the original * estimate of `numSuites`. */ emitOutOfBandTest( title: string, error?: any, suiteTitle?: string, estimated?: boolean) { util.debug('MultiReporter#emitOutOfBandTest(', arguments, ')'); const root: Mocha.ISuite = new (Mocha as any).Suite(suiteTitle || ''); const test: ExtendedTest = new (Mocha as any).Test(title, function() {}); test.parent = root; test.state = error ? 'failed' : 'passed'; test.err = error; if (!estimated) { this.total = this.total + ESTIMATED_TESTS_PER_SUITE; } const runner = {total: 1} as Mocha.IRunner; this.proxyEvent('start', runner); this.proxyEvent('suite', runner, root); this.proxyEvent('test', runner, test); if (error) { this.proxyEvent('fail', runner, test, 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} */ suiteTitle(location: Location|string) { let path = util.relativeLocation(location, this.basePath); path = util.cleanLocation(path); return path; } // Internal Interface /** @param {!Mocha.runners.Base} runner The runner to listen to events for. */ private bindChildRunner(runner: Mocha.IRunner) { MOCHA_EVENTS.forEach((eventName) => { runner.on(eventName, this.proxyEvent.bind(this, eventName, runner)); }); } /** * 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. */ private proxyEvent( eventName: string, runner: Mocha.IRunner, ..._args: any[]) { const 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(Array.prototype.slice.call(arguments)); return; } util.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 eventName * @param runner The runner that emitted this event. * @param extraArgs */ private cleanEvent( eventName: string, _runner: Mocha.IRunner, extraArgs: any[]) { // 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 */ private showRootSuite(node: Mocha.IRunnable) { const leaf = node = Object.create(node); while (node && node.parent) { const wrappedParent = Object.create(node.parent); node.parent = wrappedParent; node = wrappedParent; } node.root = false; return leaf; } /** @param {!Mocha.runners.Base} runner */ private onRunnerStart(runner: Mocha.IRunner) { util.debug('MultiReporter#onRunnerStart:', runner.name); this.total = this.total - ESTIMATED_TESTS_PER_SUITE + runner.total; this.currentRunner = runner; } /** @param {!Mocha.runners.Base} runner */ private onRunnerEnd(runner: Mocha.IRunner) { util.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. */ private flushPendingEvents() { const events = this.pendingEvents; this.pendingEvents = []; events.forEach((eventArgs) => { this.proxyEvent.apply(this, eventArgs); }); } } export default interface MultiReporter extends Mocha.IRunner, NodeJS.EventEmitter {} ================================================ FILE: browser/reporters/title.ts ================================================ /** * @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 */ import * as util from '../util.js'; const ARC_OFFSET = 0; // start at the right. const 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. */ export default class Title { runner: Mocha.IRunner; constructor(runner: Mocha.IRunner) { Mocha.reporters.Base.call(this, runner); runner.on('test end', this.report.bind(this)); } /** Reports current stats via the page title and favicon. */ report() { this.updateTitle(); this.updateFavicon(); } /** Updates the document title with a summary of current stats. */ updateTitle() { if (this.stats.failures > 0) { document.title = util.pluralizedStat(this.stats.failures, 'failing'); } else { document.title = util.pluralizedStat(this.stats.passes, 'passing'); } } /** Updates the document's favicon w/ a summary of current stats. */ updateFavicon() { const canvas = document.createElement('canvas'); canvas.height = canvas.width = 32; const context = canvas.getContext('2d'); const passing = this.stats.passes; const pending = this.stats.pending; const failing = this.stats.failures; const 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. */ setFavicon(url: string) { const current = document.head.querySelector('link[rel="icon"]'); if (current) { document.head.removeChild(current); } const link = document.createElement('link'); link.rel = 'icon'; link.type = 'image/x-icon'; link.href = url; link.setAttribute('sizes', '32x32'); document.head.appendChild(link); } } /** * Draws an arc for the favicon status, relative to the total number of tests. */ function drawFaviconArc( context: CanvasRenderingContext2D, total: number, start: number, length: number, color: string) { const arcStart = ARC_OFFSET + Math.PI * 2 * (start / total); const 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(); } export default interface Title extends Mocha.reporters.Base {} ================================================ FILE: browser/reporters.ts ================================================ /** * @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 */ import CLISocket from './clisocket.js'; import ConsoleReporter from './reporters/console.js'; import HTMLReporter from './reporters/html.js'; import MultiReporter, {ReporterFactory} from './reporters/multi.js'; import TitleReporter from './reporters/title.js'; import * as suites from './suites.js'; export let htmlSuites: Array = []; export let jsSuites: Array = []; /** * @param {CLISocket} socket The CLI socket, if present. * @param {MultiReporter} parent The parent reporter, if present. * @return {!Array. = [TitleReporter, ConsoleReporter]; if (socket) { reporters.push(function(runner: MultiReporter) { socket.observe(runner); } as any); } if (suites.htmlSuites.length > 0 || suites.jsSuites.length > 0) { reporters.push(HTMLReporter as any); } return reporters; } export type MochaStatic = typeof Mocha; /** * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy. */ export function injectMocha(Mocha: MochaStatic) { _injectPrototype(ConsoleReporter, Mocha.reporters.Base.prototype); _injectPrototype(HTMLReporter, Mocha.reporters.HTML.prototype); // Mocha doesn't expose its `EventEmitter` shim directly, so: _injectPrototype( MultiReporter, Object.getPrototypeOf(Mocha.Runner.prototype)); } function _injectPrototype(klass: any, prototype: any) { const newPrototype = Object.create(prototype); // Only support Object.keys(klass.prototype).forEach(function(key) { newPrototype[key] = klass.prototype[key]; }); klass.prototype = newPrototype; } ================================================ FILE: browser/suites.ts ================================================ /** * @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 */ import ChildRunner from './childrunner.js'; import * as config from './config.js'; import MultiReporter from './reporters/multi.js'; import * as util from './util.js'; export let htmlSuites: string[] = []; export let jsSuites: string[] = []; // We process grep ourselves to avoid loading suites that will be filtered. let GREP = util.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 files The files to load. */ export function loadSuites(files: string[]) { files.forEach(function(file) { if (/\.js(\?.*)?$/.test(file)) { jsSuites.push(file); } else if (/\.html(\?.*)?$/.test(file)) { htmlSuites.push(file); } else { throw new Error('Unknown resource type: ' + file); } }); } /** * @return The child suites that should be loaded, ignoring * those that would not match `GREP`. */ export function activeChildSuites(): string[] { let subsuites = htmlSuites; if (GREP) { const cleanSubsuites = []; for (let i = 0, subsuite; subsuite = subsuites[i]; i++) { if (GREP.indexOf(util.cleanLocation(subsuite)) !== -1) { cleanSubsuites.push(subsuite); } } subsuites = cleanSubsuites; } return subsuites; } /** * Loads all `.js` sources requested by the current suite. */ export function loadJsSuites( _reporter: MultiReporter, done: (error: Error) => void) { util.debug('loadJsSuites', jsSuites); const loaders = jsSuites.map(function(file) { // We only support `.js` dependencies for now. return util.loadScript.bind(util, file); }); util.parallel(loaders, done); } export function runSuites( reporter: MultiReporter, childSuites: string[], done: (error?: any) => void) { util.debug('runSuites'); const suiteRunners: Array<(next: () => void) => void> = [ // 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) { const 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(); }); }); }); util.parallel( suiteRunners, config.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: MultiReporter, done: () => void, waited: boolean) { if (config.get('waitForFrameworks') && !waited) { const waitFor = (config.get('waitFor') || util.whenFrameworksReady).bind(window); waitFor(_runMocha.bind(null, reporter, done, true)); return; } util.debug('_runMocha'); const mocha = window.mocha; const 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 const runner = Mocha.prototype.run.call(mocha, function(_error: any) { 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); }); } } ================================================ FILE: browser/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "es6", "moduleResolution": "node", "isolatedModules": false, "noImplicitAny": true, "noUnusedLocals": false, "noUnusedParameters": true, "noImplicitThis": false, "strictNullChecks": false, "removeComments": false, "preserveConstEnums": true, "suppressImplicitAnyIndexErrors": true, "lib": [ "es2017", "dom" ], "sourceMap": true, "pretty": true }, "exclude": [ "node_modules" ], "include": [ "*.ts", "**/*.ts", "../custom_typings/*.d.ts" ] } ================================================ FILE: browser/util.ts ================================================ /** * @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 */ import * as config from './config.js'; /** * @param {function()} callback A function to call when the active web component * frameworks have loaded. */ export function whenFrameworksReady(callback: () => void) { debug('whenFrameworksReady'); const 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(); } } /** * @return {string} ' tests' or ' test'. */ export function pluralizedStat(count: number, kind: string): string { 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 */ export function loadScript(path: string, done: (error?: any) => void) { const 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 */ export function loadStyle(path: string, done?: () => void) { const 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. */ export function debug(...var_args: any[]) { if (!config.get('verbose')) { return; } const args = [window.location.pathname, ...var_args]; (console.debug || console.log).apply(console, args); } // URL Processing /** * @param {string} url * @return {{base: string, params: string}} */ export function parseUrl(url: string) { const 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} */ export function expandUrl(url: string, base: string) { if (!base) return url; if (url.match(/^(\/|https?:\/\/)/)) return url; if (base.substr(base.length - 1) !== '/') { base = base + '/'; } return base + url; } export interface Params { [param: string]: string[]; } /** * @param {string=} opt_query A query string to parse. * @return {!Object>} All params on the URL's query. */ export function getParams(query?: string): Params { query = typeof query === 'string' ? 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 {}; const result: {[param: string]: string[]} = {}; query.split('&').forEach(function(part) { const pair = part.split('='); if (pair.length !== 2) { console.warn('Invalid URL query part:', part); return; } const key = decodeURIComponent(pair[0]); const 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>} target * @param {!Object>} source */ export function mergeParams(target: Params, source: Params) { 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. */ export function getParam(param: string): string|null { const params = getParams(); return params[param] ? params[param][0] : null; } /** * @param {!Object>} params * @return {string} `params` encoded as a URI query. */ export function paramsToQuery(params: Params): string { const pairs: string[] = []; Object.keys(params).forEach(function(key) { params[key].forEach(function(value) { pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); }); }); return (pairs.length > 0) ? ('?' + pairs.join('&')) : ''; } function getPathName(location: Location|string): string { return typeof location === 'string' ? location : location.pathname; } export function basePath(location: Location|string) { return getPathName(location).match(/^.*\//)[0]; } export function relativeLocation(location: Location|string, basePath: string) { let path = getPathName(location); if (path.indexOf(basePath) === 0) { path = path.substring(basePath.length); } return path; } export function cleanLocation(location: Location|string) { let path = getPathName(location); if (path.slice(-11) === '/index.html') { path = path.slice(0, path.length - 10); } return path; } export type Runner = (f: Function) => void; /** * Like `async.parallelLimit`, but our own so that we don't force a dependency * on downstream code. * * @param 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. */ export function parallel(runners: Runner[], done: (error?: any) => void): void; export function parallel( runners: Runner[], limit: number, done: (error?: any) => void): void; export function parallel( runners: Runner[], maybeLimit: number|((error?: any) => void), done?: (error?: any) => void) { let limit: number; if (typeof maybeLimit !== 'number') { done = maybeLimit; limit = 0; } else { limit = maybeLimit; } if (!runners.length) { return done(); } let called = false; const total = runners.length; let numActive = 0; let numDone = 0; function runnerDone(error: any) { 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?} */ export function scriptPrefix(filename: string): string|null { const scripts = document.querySelectorAll('script[src*="' + filename + '"]') as NodeListOf; if (scripts.length !== 1) { return null; } const script = scripts[0].src; return script.substring(0, script.indexOf(filename)); } ================================================ FILE: browser-js-header.txt ================================================ /** * @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! */ ================================================ FILE: browser.js ================================================ /** * @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'; window.__wctUseNpm = false; /** * @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 nativeSetInterval = window.setInterval; var nativeSetTimeout = window.setTimeout; var nativeRequestAnimationFrame = window.requestAnimationFrame; /** * 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 */ 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. */ 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 */ function testImmediateAsync(name, testFn) { var testComplete = false; var err; test(name, function (done) { var intervalId = nativeSetInterval(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 */ 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_1 = done; done = function doneIE() { Platform.performMicrotaskCheckpoint(); nativeSetTimeout(reallyDone_1, 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). nativeSetTimeout(done, 0); } /** * Advances a single animation frame. * * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially * @param {function()} callback */ function animationFrameFlush(callback) { flush(function () { nativeRequestAnimationFrame(function () { flush(callback); }); }); } /** * DEPRECATED: Use `flush`. * @param {function} callback */ function asyncPlatformFlush(callback) { console.warn('asyncPlatformFlush is deprecated in favor of the more terse flush()'); return window.flush(callback); } /** * */ 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 (typeof intervalOrMutationEl !== 'number') { intervalOrMutationEl.onMutation(intervalOrMutationEl, function () { waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime); }); } else { nativeSetTimeout(function () { waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime); }, intervalOrMutationEl); } return; } } next(); } window.safeStep = safeStep; window.testImmediate = testImmediate; window.testImmediateAsync = testImmediateAsync; window.flush = flush; window.animationFrameFlush = animationFrameFlush; window.asyncPlatformFlush = asyncPlatformFlush; window.waitFor = waitFor; /** * The global configuration state for WCT's browser client. */ var _config = { environmentScripts: !!window.__wctUseNpm ? [ 'stacky/browser.js', 'async/lib/async.js', 'lodash/index.js', 'mocha/mocha.js', 'chai/chai.js', '@polymer/sinonjs/sinon.js', 'sinon-chai/lib/sinon-chai.js', 'accessibility-developer-tools/dist/js/axs_testing.js', '@polymer/test-fixture/test-fixture.js' ] : [ '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: !!window.__wctUseNpm ? [] : ['test-fixture/test-fixture.html'], root: null, waitForFrameworks: true, waitFor: null, numConcurrentSuites: 1, trackConsoleError: true, mochaOptions: { timeout: 10 * 1000 }, 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. */ 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]; } }); } /** * @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(); } } /** * @return {string} ' tests' or ' 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 var_args = []; for (var _i = 0; _i < arguments.length; _i++) { var_args[_i] = arguments[_i]; } if (!get('verbose')) { return; } var args = [window.location.pathname].concat(var_args); (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>} All params on the URL's query. */ function getParams(query) { query = typeof query === 'string' ? 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>} target * @param {!Object>} 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>} 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('&')) : ''; } function getPathName(location) { return typeof location === 'string' ? location : location.pathname; } function basePath(location) { return getPathName(location).match(/^.*\//)[0]; } function relativeLocation(location, basePath) { var path = getPathName(location); if (path.indexOf(basePath) === 0) { path = path.substring(basePath.length); } return path; } function cleanLocation(location) { var path = getPathName(location); if (path.slice(-11) === '/index.html') { path = path.slice(0, path.length - 10); } return path; } function parallel(runners, maybeLimit, done) { var limit; if (typeof maybeLimit !== 'number') { done = maybeLimit; limit = 0; } else { limit = maybeLimit; } 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 }); /** * A Mocha suite (or suites) run within a child iframe, but reported as if they * are part of the current context. */ var ChildRunner = /** @class */ (function () { 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'; } /** * @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); if (this.iframe.contentWindow == null && error) { this.signalRunComplete(error); this.done(); return; } // 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 () { 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; this.share = null; }.bind(this), 1); }; ChildRunner.prototype.signalRunComplete = function (error) { if (!this.onRunComplete) return; this.state = 'complete'; this.onRunComplete(error); this.onRunComplete = null; }; // 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; }()); 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. */ var CLISocket = /** @class */ (function () { 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 (runner) { var _this = this; 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) }); }); runner.on('test end', function (test) { _this.emitEvent('test-end', { state: getState(test), test: getTitles(test), duration: test.duration, error: test.err, }); }); 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); } }); runner.on('childRunner start', function (childRunner) { _this.emitEvent('sub-suite-start', childRunner.share); }); runner.on('childRunner end', function (childRunner) { _this.emitEvent('sub-suite-end', childRunner.share); }); runner.on('end', function () { _this.emitEvent('browser-end'); }); }; /** * @param {string} event The name of the event to fire. * @param {*} data Additional data to pass with the event. */ CLISocket.prototype.emitEvent = function (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 (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)); }); }); }; return CLISocket; }()); // Misc Utility /** * @param {!Mocha.Runnable} runnable The test or suite to extract titles from. * @return {!Array.} 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'; } } // 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`. */ var Console = /** @class */ (function () { /** * @param 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 () { 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(); }; return Console; }()); /** * @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 = "\n html, body {\n position: relative;\n height: 100%;\n width: 100%;\n min-width: 900px;\n }\n #mocha, #subsuites {\n height: 100%;\n position: absolute;\n top: 0;\n }\n #mocha {\n box-sizing: border-box;\n margin: 0 !important;\n padding: 60px 20px;\n right: 0;\n left: 500px;\n }\n #subsuites {\n -ms-flex-direction: column;\n -webkit-flex-direction: column;\n display: -ms-flexbox;\n display: -webkit-flex;\n display: flex;\n flex-direction: column;\n left: 0;\n width: 500px;\n }\n #subsuites .subsuite {\n border: 0;\n width: 100%;\n height: 100%;\n }\n #mocha .test.pass .duration {\n color: #555 !important;\n }\n"; 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. */ var MultiReporter = /** @class */ (function () { /** * @param numSuites The number of suites that will be run, in order to * estimate the total number of tests that will be performed. * @param reporters The set of reporters that * should receive the unified event stream. * @param parent The parent reporter, if present. */ function MultiReporter(numSuites, reporters, parent) { var _this = this; this.reporters = reporters.map(function (reporter) { return new reporter(_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 The location this reporter represents. * @return A reporter-like "class" for each child suite * that should be passed to `mocha.run`. */ MultiReporter.prototype.childReporter = function (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; return _a = /** @class */ (function () { function ChildReporter(runner) { runner.name = window.name; self.bindChildRunner(runner); } return ChildReporter; }()), _a.title = window.name, _a; var _a; }; /** Must be called once all runners have finished. */ MultiReporter.prototype.done = function () { 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 title The title of the test. * @param error An error associated with this test. If falsy, test is * considered to be passing. * @param suiteTitle Title for the suite that's wrapping the test. * @param estimated If this test was included in the original * estimate of `numSuites`. */ MultiReporter.prototype.emitOutOfBandTest = function (title, error, suiteTitle, estimated) { debug('MultiReporter#emitOutOfBandTest(', arguments, ')'); var root = new Mocha.Suite(suiteTitle || ''); var test = new Mocha.Test(title, function () { }); test.parent = root; test.state = error ? 'failed' : 'passed'; test.err = error; if (!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 (error) { this.proxyEvent('fail', runner, test, 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 (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 (runner) { var _this = this; MOCHA_EVENTS.forEach(function (eventName) { runner.on(eventName, _this.proxyEvent.bind(_this, eventName, runner)); }); }; /** * 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 (eventName, runner) { var _args = []; for (var _i = 2; _i < arguments.length; _i++) { _args[_i - 2] = arguments[_i]; } 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(Array.prototype.slice.call(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 eventName * @param runner The runner that emitted this event. * @param extraArgs */ MultiReporter.prototype.cleanEvent = function (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 (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 (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 (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 () { var _this = this; var events = this.pendingEvents; this.pendingEvents = []; events.forEach(function (eventArgs) { _this.proxyEvent.apply(_this, eventArgs); }); }; return MultiReporter; }()); 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. */ var Title = /** @class */ (function () { 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 () { this.updateTitle(); this.updateFavicon(); }; /** Updates the document title with a summary of current stats. */ Title.prototype.updateTitle = function () { if (this.stats.failures > 0) { document.title = pluralizedStat(this.stats.failures, 'failing'); } else { document.title = pluralizedStat(this.stats.passes, 'passing'); } }; /** Updates the document's favicon w/ a summary of current stats. */ Title.prototype.updateFavicon = function () { 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 (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); }; return Title; }()); /** * Draws an arc for the favicon status, relative to the total number of tests. */ 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(); } 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 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 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 = void 0; 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. */ 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); } 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(_runMocha.bind(null, 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); }); } } /** * @param {CLISocket} socket The CLI socket, if present. * @param {MultiReporter} parent The parent reporter, if present. * @return {!Array. 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 a11ySuiteScriptPath = 'web-component-tester/data/a11ySuite.js'; var scripts = get('environmentScripts'); var a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySuiteScriptPath) > -1; // We can't inject a11ySuite when running the npm version because it is a // module-based script that needs `'); // 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(''); // 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 (!window.Polymer.Element) { window.Polymer.Element = function () { }; window.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`: var documentImportNode = document.importNode; if (documentImportNode.isSinonProxy) { documentImportNode.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() { var keys = Object.keys(MOCHA_EXPORTS); keys.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. } // 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. share: {}, // Until then, we get to rely on it to expose parent runners to their // children. _ChildRunner: ChildRunner, _reporter: undefined, _config: _config, // Public API /** * Loads suites of tests, supporting both `.js` and `.html` files. * * @param {!Array.} files The files to load. */ 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 ================================================ FILE: custom_typings/bower-config.d.ts ================================================ declare module 'bower-config' { interface Config { directory: string; } function read(cwd: string, overrides?: boolean): Config; } ================================================ FILE: custom_typings/findup-sync.d.ts ================================================ declare module 'findup-sync' { import * as minimatch from 'minimatch'; interface IOptions extends minimatch.IOptions { cwd?: string; } function mod(pattern: string[]|string, opts?: IOptions): string; namespace mod {} export = mod; } ================================================ FILE: custom_typings/promisify-node.d.ts ================================================ declare module 'promisify-node' { interface NodeCallback { (err: any, value: T): void; } function promisify(f: (cb: NodeCallback) => void): () => Promise; function promisify(f: (a: A1, cb: NodeCallback) => void): (a: A1) => Promise; function promisify( f: (a: A1, a2: A2, cb: NodeCallback) => void): (a: A1, a2: A2) => Promise; function promisify( f: (a: A1, a2: A2, a3: A3, cb: NodeCallback) => void): (a: A1, a2: A2, a3: A3) => Promise; namespace promisify {} export = promisify; } ================================================ FILE: custom_typings/send.d.ts ================================================ declare module 'send' { import * as http from 'http'; import * as events from 'events'; function send(req: http.IncomingMessage, path: string, options?: send.Options): send.SendStream; namespace send { export interface SendStream extends events.EventEmitter { pipe(res: http.ServerResponse): void; } export interface Options { dotfiles?: 'allow' | 'deny' | 'ignore'; end?: number; etag?: boolean; extensions?: string[]; index?: boolean|string|string[]; lastModified?: boolean; maxAge?: number; root?: string; start?: number; } } export = send; } ================================================ FILE: custom_typings/server-destroy.d.ts ================================================ declare module 'server-destroy' { import * as http from 'http'; /** * Monkey-patches the destroy() method onto the given server. * * It only accepts DestroyableServers as parameters to remind the user * to update their type annotations elsewhere, as we can't express the * mutation in the type system directly. */ function enableDestroy(server: enableDestroy.DestroyableServer): void; namespace enableDestroy { interface DestroyableServer extends http.Server { destroy(): void; } } export = enableDestroy; } ================================================ FILE: custom_typings/stacky.d.ts ================================================ declare module 'stacky' { interface ParsedStackFrame { method: string; location: string; line: number; column: number; } type StyleFunction = (part: string) => string; interface Options { maxMethodPadding?: number; indent?: string; methodPlaceholder?: string; locationStrip?: (string|RegExp)[]; unimportantLocation?: (string|RegExp)[]; filter?: (line: ParsedStackFrame) => boolean; styles?: { method?: StyleFunction; location?: StyleFunction; line?: StyleFunction; column?: StyleFunction; unimportant?: StyleFunction; }; } export function clean(lines: ParsedStackFrame[], options: Options): void; export function pretty( errorStack: string|ParsedStackFrame[], options: Options): string; export function normalize(error: Error, config: Options): Error; } ================================================ FILE: custom_typings/wd.d.ts ================================================ declare module 'wd' { interface NodeCB { (err: any, value: T): void; } export interface Browser { configureHttp(options: { retries: number }): void; attach(sessionId: string, callback: NodeCB): void; init(capabilities: Capabilities, callback: NodeCB): void; get(url: string, callback: NodeCB): void; quit(callback: NodeCB): void; on(eventName: string, handler: Function): void; } export interface Capabilities { /** The name of the browser being used */ browserName: 'android'|'chrome'|'firefox'|'htmlunit'|'internet explorer'|'iPhone'|'iPad'|'opera'|'safari'; /** The browser version, or the empty string if unknown. */ version: string; /** A key specifying which platform the browser should be running on. */ platform: 'WINDOWS'|'XP'|'VISTA'|'MAC'|'LINUX'|'UNIX'|'ANDROID'|'ANY'; /** Whether the session can interact with modal popups, * such as window.alert and window.confirm. */ handlesAlerts: boolean; /** Whether the session supports CSS selectors when searching for elements. */ cssSelectorsEnabled: boolean; webdriver: { remote: { quietExceptions: boolean; } }; selenium: { server: { url: string; } }; } export function remote( hostnameOrUrl: string, port?: number, username?: string, password?: string): Browser; export function remote( options: {hostname: string, port?: number, auth?: string, path?: string, } ): Browser; export function remote( options: {host: string, port?: number, username?: string, accesskey?: string, path?: string, } ): Browser; } ================================================ FILE: data/a11ySuite-npm-header.txt ================================================ import * as polymerDom from '../@polymer/polymer/lib/legacy/polymer.dom.js'; const Polymer = { dom: polymerDom }; export {a11ySuiteExport as a11ySuite}; // wct-browser-legacy/a11ySuite.js is a generated file. Source is in web-component-tester/data/a11ySuite.js ================================================ FILE: data/a11ySuite.js ================================================ /** * @license * Copyright (c) 2015 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 */ var a11ySuiteExport; (function(Mocha, chai, axs) { Object.keys(Mocha.interfaces).forEach(function(iface) { var orig = Mocha.interfaces[iface]; Mocha.interfaces[iface] = function(suite) { orig.apply(this, arguments); var Suite = Mocha.Suite; var Test = Mocha.Test; suite.on('pre-require', function(context, file, mocha) { /** * Runs the Chrome Accessibility Developer Tools Audit against a test-fixture * * @param {String} fixtureId ID of the fixture element in the document to use * @param {Array?} ignoredRules Array of rules to ignore for this suite * @param {Function?} beforeEach Function to be called before each test to ensure proper setup */ a11ySuiteExport = context.a11ySuite = function(fixtureId, ignoredRules, beforeEach) { // capture a reference to the fixture element early var fixtureElement = document.getElementById(fixtureId); if (!fixtureElement) { return; } // build an audit config to disable certain ignorable tests var axsConfig = new axs.AuditConfiguration(); axsConfig.scope = document.body; axsConfig.showUnsupportedRulesWarning = false; axsConfig.auditRulesToIgnore = ignoredRules; // build mocha suite var a11ySuite = Suite.create(suite, 'A11y Audit - Fixture: ' + fixtureId); // override the `eachTest` function to hackily create the tests // // eachTest is called right before test runs to calculate the total // number of tests a11ySuite.eachTest = function() { // instantiate fixture fixtureElement.create(); // Make sure lazy-loaded dom is ready (eg