PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Test04/platforms/ios/www/bower_components/web-component-tester/browser.js

https://gitlab.com/hemantr/Test04
JavaScript | 1680 lines | 1007 code | 196 blank | 477 comment | 141 complexity | 5f34dfc9aef42dc01e85561457cbafb6 MD5 | raw file
  1. /**
  2. * @license
  3. * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
  4. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
  5. * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
  6. * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
  7. * Code distributed by Google as part of the polymer project is also
  8. * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
  9. */
  10. (function () { 'use strict';
  11. /**
  12. * @param {function()} callback A function to call when the active web component
  13. * frameworks have loaded.
  14. */
  15. function whenFrameworksReady(callback) {
  16. debug('whenFrameworksReady');
  17. var done = function() {
  18. debug('whenFrameworksReady done');
  19. callback();
  20. };
  21. function whenWebComponentsReady() {
  22. debug('WebComponentsReady?');
  23. if (window.WebComponents && WebComponents.whenReady) {
  24. WebComponents.whenReady(function() {
  25. debug('WebComponents Ready');
  26. done();
  27. });
  28. } else {
  29. var after = function after() {
  30. window.removeEventListener('WebComponentsReady', after);
  31. debug('WebComponentsReady');
  32. done();
  33. };
  34. window.addEventListener('WebComponentsReady', after);
  35. }
  36. }
  37. function importsReady() {
  38. // handle Polymer 0.5 readiness
  39. debug('Polymer ready?');
  40. if (window.Polymer && Polymer.whenReady) {
  41. Polymer.whenReady(function() {
  42. debug('Polymer ready');
  43. done();
  44. });
  45. } else {
  46. whenWebComponentsReady();
  47. }
  48. }
  49. // All our supported framework configurations depend on imports.
  50. if (!window.HTMLImports) {
  51. done();
  52. } else if (HTMLImports.ready) {
  53. debug('HTMLImports ready');
  54. importsReady();
  55. } else if (HTMLImports.whenReady) {
  56. HTMLImports.whenReady(function() {
  57. debug('HTMLImports.whenReady ready');
  58. importsReady();
  59. });
  60. } else {
  61. whenWebComponentsReady();
  62. }
  63. }
  64. /**
  65. * @param {number} count
  66. * @param {string} kind
  67. * @return {string} '<count> <kind> tests' or '<count> <kind> test'.
  68. */
  69. function pluralizedStat(count, kind) {
  70. if (count === 1) {
  71. return count + ' ' + kind + ' test';
  72. } else {
  73. return count + ' ' + kind + ' tests';
  74. }
  75. }
  76. /**
  77. * @param {string} path The URI of the script to load.
  78. * @param {function} done
  79. */
  80. function loadScript(path, done) {
  81. var script = document.createElement('script');
  82. script.src = path;
  83. if (done) {
  84. script.onload = done.bind(null, null);
  85. script.onerror = done.bind(null, 'Failed to load script ' + script.src);
  86. }
  87. document.head.appendChild(script);
  88. }
  89. /**
  90. * @param {string} path The URI of the stylesheet to load.
  91. * @param {function} done
  92. */
  93. function loadStyle(path, done) {
  94. var link = document.createElement('link');
  95. link.rel = 'stylesheet';
  96. link.href = path;
  97. if (done) {
  98. link.onload = done.bind(null, null);
  99. link.onerror = done.bind(null, 'Failed to load stylesheet ' + link.href);
  100. }
  101. document.head.appendChild(link);
  102. }
  103. /**
  104. * @param {...*} var_args Logs values to the console when the `debug`
  105. * configuration option is true.
  106. */
  107. function debug(var_args) {
  108. if (!get('verbose')) return;
  109. var args = [window.location.pathname];
  110. args.push.apply(args, arguments);
  111. (console.debug || console.log).apply(console, args);
  112. }
  113. // URL Processing
  114. /**
  115. * @param {string} url
  116. * @return {{base: string, params: string}}
  117. */
  118. function parseUrl(url) {
  119. var parts = url.match(/^(.*?)(?:\?(.*))?$/);
  120. return {
  121. base: parts[1],
  122. params: getParams(parts[2] || ''),
  123. };
  124. }
  125. /**
  126. * Expands a URL that may or may not be relative to `base`.
  127. *
  128. * @param {string} url
  129. * @param {string} base
  130. * @return {string}
  131. */
  132. function expandUrl(url, base) {
  133. if (!base) return url;
  134. if (url.match(/^(\/|https?:\/\/)/)) return url;
  135. if (base.substr(base.length - 1) !== '/') {
  136. base = base + '/';
  137. }
  138. return base + url;
  139. }
  140. /**
  141. * @param {string=} opt_query A query string to parse.
  142. * @return {!Object<string, !Array<string>>} All params on the URL's query.
  143. */
  144. function getParams(opt_query) {
  145. var query = typeof opt_query === 'string' ? opt_query : window.location.search;
  146. if (query.substring(0, 1) === '?') {
  147. query = query.substring(1);
  148. }
  149. // python's SimpleHTTPServer tacks a `/` on the end of query strings :(
  150. if (query.slice(-1) === '/') {
  151. query = query.substring(0, query.length - 1);
  152. }
  153. if (query === '') return {};
  154. var result = {};
  155. query.split('&').forEach(function(part) {
  156. var pair = part.split('=');
  157. if (pair.length !== 2) {
  158. console.warn('Invalid URL query part:', part);
  159. return;
  160. }
  161. var key = decodeURIComponent(pair[0]);
  162. var value = decodeURIComponent(pair[1]);
  163. if (!result[key]) {
  164. result[key] = [];
  165. }
  166. result[key].push(value);
  167. });
  168. return result;
  169. }
  170. /**
  171. * Merges params from `source` into `target` (mutating `target`).
  172. *
  173. * @param {!Object<string, !Array<string>>} target
  174. * @param {!Object<string, !Array<string>>} source
  175. */
  176. function mergeParams(target, source) {
  177. Object.keys(source).forEach(function(key) {
  178. if (!(key in target)) {
  179. target[key] = [];
  180. }
  181. target[key] = target[key].concat(source[key]);
  182. });
  183. }
  184. /**
  185. * @param {string} param The param to return a value for.
  186. * @return {?string} The first value for `param`, if found.
  187. */
  188. function getParam(param) {
  189. var params = getParams();
  190. return params[param] ? params[param][0] : null;
  191. }
  192. /**
  193. * @param {!Object<string, !Array<string>>} params
  194. * @return {string} `params` encoded as a URI query.
  195. */
  196. function paramsToQuery(params) {
  197. var pairs = [];
  198. Object.keys(params).forEach(function(key) {
  199. params[key].forEach(function(value) {
  200. pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
  201. });
  202. });
  203. return (pairs.length > 0) ? ('?' + pairs.join('&')) : '';
  204. }
  205. /**
  206. * @param {!Location|string} location
  207. * @return {string}
  208. */
  209. function basePath(location) {
  210. return (location.pathname || location).match(/^.*\//)[0];
  211. }
  212. /**
  213. * @param {!Location|string} location
  214. * @param {string} basePath
  215. * @return {string}
  216. */
  217. function relativeLocation(location, basePath) {
  218. var path = location.pathname || location;
  219. if (path.indexOf(basePath) === 0) {
  220. path = path.substring(basePath.length);
  221. }
  222. return path;
  223. }
  224. /**
  225. * @param {!Location|string} location
  226. * @return {string}
  227. */
  228. function cleanLocation(location) {
  229. var path = location.pathname || location;
  230. if (path.slice(-11) === '/index.html') {
  231. path = path.slice(0, path.length - 10);
  232. }
  233. return path;
  234. }
  235. /**
  236. * Like `async.parallelLimit`, but our own so that we don't force a dependency
  237. * on downstream code.
  238. *
  239. * @param {!Array<function(function(*))>} runners Runners that call their given
  240. * Node-style callback when done.
  241. * @param {number|function(*)} limit Maximum number of concurrent runners.
  242. * (optional).
  243. * @param {?function(*)} done Callback that should be triggered once all runners
  244. * have completed, or encountered an error.
  245. */
  246. function parallel(runners, limit, done) {
  247. if (typeof limit !== 'number') {
  248. done = limit;
  249. limit = 0;
  250. }
  251. if (!runners.length) return done();
  252. var called = false;
  253. var total = runners.length;
  254. var numActive = 0;
  255. var numDone = 0;
  256. function runnerDone(error) {
  257. if (called) return;
  258. numDone = numDone + 1;
  259. numActive = numActive - 1;
  260. if (error || numDone >= total) {
  261. called = true;
  262. done(error);
  263. } else {
  264. runOne();
  265. }
  266. }
  267. function runOne() {
  268. if (limit && numActive >= limit) return;
  269. if (!runners.length) return;
  270. numActive = numActive + 1;
  271. runners.shift()(runnerDone);
  272. }
  273. runners.forEach(runOne);
  274. }
  275. /**
  276. * Finds the directory that a loaded script is hosted on.
  277. *
  278. * @param {string} filename
  279. * @return {string?}
  280. */
  281. function scriptPrefix(filename) {
  282. var scripts = document.querySelectorAll('script[src*="' + filename + '"]');
  283. if (scripts.length !== 1) return null;
  284. var script = scripts[0].src;
  285. return script.substring(0, script.indexOf(filename));
  286. }
  287. var util = Object.freeze({
  288. whenFrameworksReady: whenFrameworksReady,
  289. pluralizedStat: pluralizedStat,
  290. loadScript: loadScript,
  291. loadStyle: loadStyle,
  292. debug: debug,
  293. parseUrl: parseUrl,
  294. expandUrl: expandUrl,
  295. getParams: getParams,
  296. mergeParams: mergeParams,
  297. getParam: getParam,
  298. paramsToQuery: paramsToQuery,
  299. basePath: basePath,
  300. relativeLocation: relativeLocation,
  301. cleanLocation: cleanLocation,
  302. parallel: parallel,
  303. scriptPrefix: scriptPrefix
  304. });
  305. // TODO(thedeeno): Consider renaming subsuite. IIRC, childRunner is entirely
  306. // distinct from mocha suite, which tripped me up badly when trying to add
  307. // plugin support. Perhaps something like 'batch', or 'bundle'. Something that
  308. // has no mocha correlate. This may also eliminate the need for root/non-root
  309. // suite distinctions.
  310. /**
  311. * A Mocha suite (or suites) run within a child iframe, but reported as if they
  312. * are part of the current context.
  313. */
  314. function ChildRunner(url, parentScope) {
  315. var urlBits = parseUrl(url);
  316. mergeParams(
  317. urlBits.params, getParams(parentScope.location.search));
  318. delete urlBits.params.cli_browser_id;
  319. this.url = urlBits.base + paramsToQuery(urlBits.params);
  320. this.parentScope = parentScope;
  321. this.state = 'initializing';
  322. }
  323. // ChildRunners get a pretty generous load timeout by default.
  324. ChildRunner.loadTimeout = 60000;
  325. // We can't maintain properties on iframe elements in Firefox/Safari/???, so we
  326. // track childRunners by URL.
  327. ChildRunner._byUrl = {};
  328. /**
  329. * @return {ChildRunner} The `ChildRunner` that was registered for this window.
  330. */
  331. ChildRunner.current = function() {
  332. return ChildRunner.get(window);
  333. };
  334. /**
  335. * @param {!Window} target A window to find the ChildRunner of.
  336. * @param {boolean} traversal Whether this is a traversal from a child window.
  337. * @return {ChildRunner} The `ChildRunner` that was registered for `target`.
  338. */
  339. ChildRunner.get = function(target, traversal) {
  340. var childRunner = ChildRunner._byUrl[target.location.href];
  341. if (childRunner) return childRunner;
  342. if (window.parent === window) { // Top window.
  343. if (traversal) {
  344. console.warn('Subsuite loaded but was never registered. This most likely is due to wonky history behavior. Reloading...');
  345. window.location.reload();
  346. }
  347. return null;
  348. }
  349. // Otherwise, traverse.
  350. return window.parent.WCT._ChildRunner.get(target, true);
  351. };
  352. /**
  353. * Loads and runs the subsuite.
  354. *
  355. * @param {function} done Node-style callback.
  356. */
  357. ChildRunner.prototype.run = function(done) {
  358. debug('ChildRunner#run', this.url);
  359. this.state = 'loading';
  360. this.onRunComplete = done;
  361. this.iframe = document.createElement('iframe');
  362. this.iframe.src = this.url;
  363. this.iframe.classList.add('subsuite');
  364. var container = document.getElementById('subsuites');
  365. if (!container) {
  366. container = document.createElement('div');
  367. container.id = 'subsuites';
  368. document.body.appendChild(container);
  369. }
  370. container.appendChild(this.iframe);
  371. // let the iframe expand the URL for us.
  372. this.url = this.iframe.src;
  373. ChildRunner._byUrl[this.url] = this;
  374. this.timeoutId = setTimeout(
  375. this.loaded.bind(this, new Error('Timed out loading ' + this.url)), ChildRunner.loadTimeout);
  376. this.iframe.addEventListener('error',
  377. this.loaded.bind(this, new Error('Failed to load document ' + this.url)));
  378. this.iframe.contentWindow.addEventListener('DOMContentLoaded', this.loaded.bind(this, null));
  379. };
  380. /**
  381. * Called when the sub suite's iframe has loaded (or errored during load).
  382. *
  383. * @param {*} error The error that occured, if any.
  384. */
  385. ChildRunner.prototype.loaded = function(error) {
  386. debug('ChildRunner#loaded', this.url, error);
  387. // Not all targets have WCT loaded (compatiblity mode)
  388. if (this.iframe.contentWindow.WCT) {
  389. this.share = this.iframe.contentWindow.WCT.share;
  390. }
  391. if (error) {
  392. this.signalRunComplete(error);
  393. this.done();
  394. }
  395. };
  396. /**
  397. * Called in mocha/run.js when all dependencies have loaded, and the child is
  398. * ready to start running tests
  399. *
  400. * @param {*} error The error that occured, if any.
  401. */
  402. ChildRunner.prototype.ready = function(error) {
  403. debug('ChildRunner#ready', this.url, error);
  404. if (this.timeoutId) {
  405. clearTimeout(this.timeoutId);
  406. }
  407. if (error) {
  408. this.signalRunComplete(error);
  409. this.done();
  410. }
  411. };
  412. /** Called when the sub suite's tests are complete, so that it can clean up. */
  413. ChildRunner.prototype.done = function done() {
  414. debug('ChildRunner#done', this.url, arguments);
  415. // make sure to clear that timeout
  416. this.ready();
  417. this.signalRunComplete();
  418. if (!this.iframe) return;
  419. // Be safe and avoid potential browser crashes when logic attempts to interact
  420. // with the removed iframe.
  421. setTimeout(function() {
  422. this.iframe.parentNode.removeChild(this.iframe);
  423. this.iframe = null;
  424. }.bind(this), 1);
  425. };
  426. ChildRunner.prototype.signalRunComplete = function signalRunComplete(error) {
  427. if (!this.onRunComplete) return;
  428. this.state = 'complete';
  429. this.onRunComplete(error);
  430. this.onRunComplete = null;
  431. };
  432. /**
  433. * The global configuration state for WCT's browser client.
  434. */
  435. var _config = {
  436. /**
  437. * `.js` scripts to be loaded (synchronously) before WCT starts in earnest.
  438. *
  439. * Paths are relative to `scriptPrefix`.
  440. */
  441. environmentScripts: [
  442. 'stacky/browser.js',
  443. 'async/lib/async.js',
  444. 'lodash/lodash.js',
  445. 'mocha/mocha.js',
  446. 'chai/chai.js',
  447. 'sinonjs/sinon.js',
  448. 'sinon-chai/lib/sinon-chai.js',
  449. 'accessibility-developer-tools/dist/js/axs_testing.js'
  450. ],
  451. environmentImports: [
  452. 'test-fixture/test-fixture.html'
  453. ],
  454. /** Absolute root for client scripts. Detected in `setup()` if not set. */
  455. root: null,
  456. /** By default, we wait for any web component frameworks to load. */
  457. waitForFrameworks: true,
  458. /** Alternate callback for waiting for tests.
  459. * `this` for the callback will be the window currently running tests.
  460. */
  461. waitFor: null,
  462. /** How many `.html` suites that can be concurrently loaded & run. */
  463. numConcurrentSuites: 1,
  464. /** Whether `console.error` should be treated as a test failure. */
  465. trackConsoleError: true,
  466. /** Configuration passed to mocha.setup. */
  467. mochaOptions: {
  468. timeout: 10 * 1000
  469. },
  470. /** Whether WCT should emit (extremely verbose) debugging log messages. */
  471. verbose: false,
  472. };
  473. /**
  474. * Merges initial `options` into WCT's global configuration.
  475. *
  476. * @param {Object} options The options to merge. See `browser/config.js` for a
  477. * reference.
  478. */
  479. function setup(options) {
  480. var childRunner = ChildRunner.current();
  481. if (childRunner) {
  482. _deepMerge(_config, childRunner.parentScope.WCT._config);
  483. // But do not force the mocha UI
  484. delete _config.mochaOptions.ui;
  485. }
  486. if (options && typeof options === 'object') {
  487. _deepMerge(_config, options);
  488. }
  489. if (!_config.root) {
  490. // Sibling dependencies.
  491. var root = scriptPrefix('browser.js');
  492. _config.root = basePath(root.substr(0, root.length - 1));
  493. if (!_config.root) {
  494. throw new Error('Unable to detect root URL for WCT sources. Please set WCT.root before including browser.js');
  495. }
  496. }
  497. }
  498. /**
  499. * Retrieves a configuration value.
  500. *
  501. * @param {string} key
  502. * @return {*}
  503. */
  504. function get(key) {
  505. return _config[key];
  506. }
  507. // Internal
  508. function _deepMerge(target, source) {
  509. Object.keys(source).forEach(function(key) {
  510. if (target[key] !== null && typeof target[key] === 'object' && !Array.isArray(target[key])) {
  511. _deepMerge(target[key], source[key]);
  512. } else {
  513. target[key] = source[key];
  514. }
  515. });
  516. }
  517. var htmlSuites$1 = [];
  518. var jsSuites$1 = [];
  519. // We process grep ourselves to avoid loading suites that will be filtered.
  520. var GREP = getParam('grep');
  521. /**
  522. * Loads suites of tests, supporting both `.js` and `.html` files.
  523. *
  524. * @param {!Array.<string>} files The files to load.
  525. */
  526. function loadSuites(files) {
  527. files.forEach(function(file) {
  528. if (/\.js(\?.*)?$/.test(file)) {
  529. jsSuites$1.push(file);
  530. } else if (/\.html(\?.*)?$/.test(file)) {
  531. htmlSuites$1.push(file);
  532. } else {
  533. throw new Error('Unknown resource type: ' + file);
  534. }
  535. });
  536. }
  537. /**
  538. * @return {!Array.<string>} The child suites that should be loaded, ignoring
  539. * those that would not match `GREP`.
  540. */
  541. function activeChildSuites() {
  542. var subsuites = htmlSuites$1;
  543. if (GREP) {
  544. var cleanSubsuites = [];
  545. for (var i = 0, subsuite; subsuite = subsuites[i]; i++) {
  546. if (GREP.indexOf(cleanLocation(subsuite)) !== -1) {
  547. cleanSubsuites.push(subsuite);
  548. }
  549. }
  550. subsuites = cleanSubsuites;
  551. }
  552. return subsuites;
  553. }
  554. /**
  555. * Loads all `.js` sources requested by the current suite.
  556. *
  557. * @param {!MultiReporter} reporter
  558. * @param {function} done
  559. */
  560. function loadJsSuites(reporter, done) {
  561. debug('loadJsSuites', jsSuites$1);
  562. var loaders = jsSuites$1.map(function(file) {
  563. // We only support `.js` dependencies for now.
  564. return loadScript.bind(util, file);
  565. });
  566. parallel(loaders, done);
  567. }
  568. /**
  569. * @param {!MultiReporter} reporter
  570. * @param {!Array.<string>} childSuites
  571. * @param {function} done
  572. */
  573. function runSuites(reporter, childSuites, done) {
  574. debug('runSuites');
  575. var suiteRunners = [
  576. // Run the local tests (if any) first, not stopping on error;
  577. _runMocha.bind(null, reporter),
  578. ];
  579. // As well as any sub suites. Again, don't stop on error.
  580. childSuites.forEach(function(file) {
  581. suiteRunners.push(function(next) {
  582. var childRunner = new ChildRunner(file, window);
  583. reporter.emit('childRunner start', childRunner);
  584. childRunner.run(function(error) {
  585. reporter.emit('childRunner end', childRunner);
  586. if (error) reporter.emitOutOfBandTest(file, error);
  587. next();
  588. });
  589. });
  590. });
  591. parallel(suiteRunners, get('numConcurrentSuites'), function(error) {
  592. reporter.done();
  593. done(error);
  594. });
  595. }
  596. /**
  597. * Kicks off a mocha run, waiting for frameworks to load if necessary.
  598. *
  599. * @param {!MultiReporter} reporter Where to send Mocha's events.
  600. * @param {function} done A callback fired, _no error is passed_.
  601. */
  602. function _runMocha(reporter, done, waited) {
  603. if (get('waitForFrameworks') && !waited) {
  604. var waitFor = (get('waitFor') || whenFrameworksReady).bind(window);
  605. waitFor(_runMocha.bind(null, reporter, done, true));
  606. return;
  607. }
  608. debug('_runMocha');
  609. var mocha = window.mocha;
  610. var Mocha = window.Mocha;
  611. mocha.reporter(reporter.childReporter(window.location));
  612. mocha.suite.title = reporter.suiteTitle(window.location);
  613. mocha.grep(GREP);
  614. // We can't use `mocha.run` because it bashes over grep, invert, and friends.
  615. // See https://github.com/visionmedia/mocha/blob/master/support/tail.js#L137
  616. var runner = Mocha.prototype.run.call(mocha, function(error) {
  617. if (document.getElementById('mocha')) {
  618. Mocha.utils.highlightTags('code');
  619. }
  620. done(); // We ignore the Mocha failure count.
  621. });
  622. // Mocha's default `onerror` handling strips the stack (to support really old
  623. // browsers). We upgrade this to get better stacks for async errors.
  624. //
  625. // TODO(nevir): Can we expand support to other browsers?
  626. if (navigator.userAgent.match(/chrome/i)) {
  627. window.onerror = null;
  628. window.addEventListener('error', function(event) {
  629. if (!event.error) return;
  630. if (event.error.ignore) return;
  631. runner.uncaught(event.error);
  632. });
  633. }
  634. }
  635. // We may encounter errors during initialization (for example, syntax errors in
  636. // a test file). Hang onto those (and more) until we are ready to report them.
  637. var globalErrors = [];
  638. /**
  639. * Hook the environment to pick up on global errors.
  640. */
  641. function listenForErrors() {
  642. window.addEventListener('error', function(event) {
  643. globalErrors.push(event.error);
  644. });
  645. // Also, we treat `console.error` as a test failure. Unless you prefer not.
  646. var origConsole = console;
  647. var origError = console.error;
  648. console.error = function wctShimmedError() {
  649. origError.apply(origConsole, arguments);
  650. if (get('trackConsoleError')) {
  651. throw 'console.error: ' + Array.prototype.join.call(arguments, ' ');
  652. }
  653. };
  654. }
  655. var interfaceExtensions = [];
  656. /**
  657. * Registers an extension that extends the global `Mocha` implementation
  658. * with new helper methods. These helper methods will be added to the `window`
  659. * when tests run for both BDD and TDD interfaces.
  660. */
  661. function extendInterfaces(helperName, helperFactory) {
  662. interfaceExtensions.push(function() {
  663. var Mocha = window.Mocha;
  664. // For all Mocha interfaces (probably just TDD and BDD):
  665. Object.keys(Mocha.interfaces).forEach(function(interfaceName) {
  666. // This is the original callback that defines the interface (TDD or BDD):
  667. var originalInterface = Mocha.interfaces[interfaceName];
  668. // This is the name of the "teardown" or "afterEach" property for the
  669. // current interface:
  670. var teardownProperty = interfaceName === 'tdd' ? 'teardown' : 'afterEach';
  671. // The original callback is monkey patched with a new one that appends to
  672. // the global context however we want it to:
  673. Mocha.interfaces[interfaceName] = function(suite) {
  674. // Call back to the original callback so that we get the base interface:
  675. originalInterface.apply(this, arguments);
  676. // Register a listener so that we can further extend the base interface:
  677. suite.on('pre-require', function(context, file, mocha) {
  678. // Capture a bound reference to the teardown function as a convenience:
  679. var teardown = context[teardownProperty].bind(context);
  680. // Add our new helper to the testing context. The helper is generated
  681. // by a factory method that receives the context, the teardown function
  682. // and the interface name and returns the new method to be added to
  683. // that context:
  684. context[helperName] = helperFactory(context, teardown, interfaceName);
  685. });
  686. };
  687. });
  688. });
  689. }
  690. /**
  691. * Applies any registered interface extensions. The extensions will be applied
  692. * as many times as this function is called, so don't call it more than once.
  693. */
  694. function applyExtensions() {
  695. interfaceExtensions.forEach(function(applyExtension) {
  696. applyExtension();
  697. });
  698. }
  699. extendInterfaces('fixture', function(context, teardown) {
  700. // Return context.fixture if it is already a thing, for backwards
  701. // compatibility with `test-fixture-mocha.js`:
  702. return context.fixture || function fixture(fixtureId, model) {
  703. // Automatically register a teardown callback that will restore the
  704. // test-fixture:
  705. teardown(function() {
  706. document.getElementById(fixtureId).restore();
  707. });
  708. // Find the test-fixture with the provided ID and create it, returning
  709. // the results:
  710. return document.getElementById(fixtureId).create(model);
  711. };
  712. });
  713. /**
  714. * stub
  715. *
  716. * The stub addon allows the tester to partially replace the implementation of
  717. * an element with some custom implementation. Usage example:
  718. *
  719. * beforeEach(function() {
  720. * stub('x-foo', {
  721. * attached: function() {
  722. * // Custom implementation of the `attached` method of element `x-foo`..
  723. * },
  724. * otherMethod: function() {
  725. * // More custom implementation..
  726. * },
  727. * // etc..
  728. * });
  729. * });
  730. */
  731. extendInterfaces('stub', function(context, teardown) {
  732. return function stub(tagName, implementation) {
  733. // Find the prototype of the element being stubbed:
  734. var proto = document.createElement(tagName).constructor.prototype;
  735. // For all keys in the implementation to stub with..
  736. var keys = Object.keys(implementation);
  737. keys.forEach(function(key) {
  738. // Stub the method on the element prototype with Sinon:
  739. sinon.stub(proto, key, implementation[key]);
  740. });
  741. // After all tests..
  742. teardown(function() {
  743. // For all of the keys in the implementation we stubbed..
  744. keys.forEach(function(key) {
  745. // Restore the stub:
  746. if (proto[key].isSinonProxy) {
  747. proto[key].restore();
  748. }
  749. });
  750. });
  751. };
  752. });
  753. /**
  754. * replace
  755. *
  756. * The replace addon allows the tester to replace all usages of one element with
  757. * another element within all Polymer elements created within the time span of
  758. * the test. Usage example:
  759. *
  760. * beforeEach(function() {
  761. * replace('x-foo').with('x-fake-foo');
  762. * });
  763. *
  764. * All annotations and attributes will be set on the placement element the way
  765. * they were set for the original element.
  766. */
  767. extendInterfaces('replace', function(context, teardown) {
  768. return function replace(oldTagName) {
  769. return {
  770. with: function(tagName) {
  771. // Keep a reference to the original `Polymer.Base.instanceTemplate`
  772. // implementation for later:
  773. var originalInstanceTemplate = Polymer.Base.instanceTemplate;
  774. // Use Sinon to stub `Polymer.Base.instanceTemplate`:
  775. sinon.stub(Polymer.Base, 'instanceTemplate', function(template) {
  776. // The DOM to replace is the result of calling the "original"
  777. // `instanceTemplate` implementation:
  778. var dom = originalInstanceTemplate.apply(this, arguments);
  779. // The nodes to replace are queried from the DOM chunk:
  780. var nodes = Array.prototype.slice.call(dom.querySelectorAll(oldTagName));
  781. // For all of the nodes we want to place...
  782. nodes.forEach(function(node) {
  783. // Create a replacement:
  784. var replacement = document.createElement(tagName);
  785. // For all attributes in the original node..
  786. for (var index = 0; index < node.attributes.length; ++index) {
  787. // Set that attribute on the replacement:
  788. replacement.setAttribute(
  789. node.attributes[index].name, node.attributes[index].value);
  790. }
  791. // Replace the original node with the replacement node:
  792. node.parentNode.replaceChild(replacement, node);
  793. });
  794. return dom;
  795. });
  796. // After each test...
  797. teardown(function() {
  798. // Restore the stubbed version of `Polymer.Base.instanceTemplate`:
  799. if (Polymer.Base.instanceTemplate.isSinonProxy) {
  800. Polymer.Base.instanceTemplate.restore();
  801. }
  802. });
  803. }
  804. };
  805. };
  806. });
  807. // Mocha global helpers, broken out by testing method.
  808. //
  809. // Keys are the method for a particular interface; values are their analog in
  810. // the opposite interface.
  811. var MOCHA_EXPORTS = {
  812. // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/tdd.js
  813. tdd: {
  814. 'setup': '"before"',
  815. 'teardown': '"after"',
  816. 'suiteSetup': '"beforeEach"',
  817. 'suiteTeardown': '"afterEach"',
  818. 'suite': '"describe" or "context"',
  819. 'test': '"it" or "specify"',
  820. },
  821. // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/bdd.js
  822. bdd: {
  823. 'before': '"setup"',
  824. 'after': '"teardown"',
  825. 'beforeEach': '"suiteSetup"',
  826. 'afterEach': '"suiteTeardown"',
  827. 'describe': '"suite"',
  828. 'context': '"suite"',
  829. 'xdescribe': '"suite.skip"',
  830. 'xcontext': '"suite.skip"',
  831. 'it': '"test"',
  832. 'xit': '"test.skip"',
  833. 'specify': '"test"',
  834. 'xspecify': '"test.skip"',
  835. },
  836. };
  837. /**
  838. * Exposes all Mocha methods up front, configuring and running mocha
  839. * automatically when you call them.
  840. *
  841. * The assumption is that it is a one-off (sub-)suite of tests being run.
  842. */
  843. function stubInterfaces() {
  844. Object.keys(MOCHA_EXPORTS).forEach(function(ui) {
  845. Object.keys(MOCHA_EXPORTS[ui]).forEach(function(key) {
  846. window[key] = function wrappedMochaFunction() {
  847. _setupMocha(ui, key, MOCHA_EXPORTS[ui][key]);
  848. if (!window[key] || window[key] === wrappedMochaFunction) {
  849. throw new Error('Expected mocha.setup to define ' + key);
  850. }
  851. window[key].apply(window, arguments);
  852. };
  853. });
  854. });
  855. }
  856. // Whether we've called `mocha.setup`
  857. var _mochaIsSetup = false;
  858. /**
  859. * @param {string} ui Sets up mocha to run `ui`-style tests.
  860. * @param {string} key The method called that triggered this.
  861. * @param {string} alternate The matching method in the opposite interface.
  862. */
  863. function _setupMocha(ui, key, alternate) {
  864. var mochaOptions = get('mochaOptions');
  865. if (mochaOptions.ui && mochaOptions.ui !== ui) {
  866. var message = 'Mixing ' + mochaOptions.ui + ' and ' + ui + ' Mocha styles is not supported. ' +
  867. 'You called "' + key + '". Did you mean ' + alternate + '?';
  868. throw new Error(message);
  869. }
  870. if (_mochaIsSetup) return;
  871. applyExtensions();
  872. mochaOptions.ui = ui;
  873. mocha.setup(mochaOptions); // Note that the reporter is configured in run.js.
  874. }
  875. // We capture console events when running tests; so make sure we have a
  876. // reference to the original one.
  877. var console$1 = window.console;
  878. var FONT = ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-serif;';
  879. var STYLES = {
  880. plain: FONT,
  881. suite: 'color: #5c6bc0' + FONT,
  882. test: FONT,
  883. passing: 'color: #259b24' + FONT,
  884. pending: 'color: #e65100' + FONT,
  885. failing: 'color: #c41411' + FONT,
  886. stack: 'color: #c41411',
  887. results: FONT + 'font-size: 16px',
  888. };
  889. // I don't think we can feature detect this one...
  890. var userAgent = navigator.userAgent.toLowerCase();
  891. var CAN_STYLE_LOG = userAgent.match('firefox') || userAgent.match('webkit');
  892. var CAN_STYLE_GROUP = userAgent.match('webkit');
  893. // Track the indent for faked `console.group`
  894. var logIndent = '';
  895. function log(text, style) {
  896. text = text.split('\n').map(function(l) { return logIndent + l; }).join('\n');
  897. if (CAN_STYLE_LOG) {
  898. console$1.log('%c' + text, STYLES[style] || STYLES.plain);
  899. } else {
  900. console$1.log(text);
  901. }
  902. }
  903. function logGroup(text, style) {
  904. if (CAN_STYLE_GROUP) {
  905. console$1.group('%c' + text, STYLES[style] || STYLES.plain);
  906. } else if (console$1.group) {
  907. console$1.group(text);
  908. } else {
  909. logIndent = logIndent + ' ';
  910. log(text, style);
  911. }
  912. }
  913. function logGroupEnd() {
  914. if (console$1.groupEnd) {
  915. console$1.groupEnd();
  916. } else {
  917. logIndent = logIndent.substr(0, logIndent.length - 2);
  918. }
  919. }
  920. function logException(error) {
  921. log(error.stack || error.message || error, 'stack');
  922. }
  923. /**
  924. * A Mocha reporter that logs results out to the web `console`.
  925. *
  926. * @param {!Mocha.Runner} runner The runner that is being reported on.
  927. */
  928. function Console(runner) {
  929. Mocha.reporters.Base.call(this, runner);
  930. runner.on('suite', function(suite) {
  931. if (suite.root) return;
  932. logGroup(suite.title, 'suite');
  933. }.bind(this));
  934. runner.on('suite end', function(suite) {
  935. if (suite.root) return;
  936. logGroupEnd();
  937. }.bind(this));
  938. runner.on('test', function(test) {
  939. logGroup(test.title, 'test');
  940. }.bind(this));
  941. runner.on('pending', function(test) {
  942. logGroup(test.title, 'pending');
  943. }.bind(this));
  944. runner.on('fail', function(test, error) {
  945. logException(error);
  946. }.bind(this));
  947. runner.on('test end', function(test) {
  948. logGroupEnd();
  949. }.bind(this));
  950. runner.on('end', this.logSummary.bind(this));
  951. }
  952. /** Prints out a final summary of test results. */
  953. Console.prototype.logSummary = function logSummary() {
  954. logGroup('Test Results', 'results');
  955. if (this.stats.failures > 0) {
  956. log(pluralizedStat(this.stats.failures, 'failing'), 'failing');
  957. }
  958. if (this.stats.pending > 0) {
  959. log(pluralizedStat(this.stats.pending, 'pending'), 'pending');
  960. }
  961. log(pluralizedStat(this.stats.passes, 'passing'));
  962. if (!this.stats.failures) {
  963. log('test suite passed', 'passing');
  964. }
  965. log('Evaluated ' + this.stats.tests + ' tests in ' + this.stats.duration + 'ms.');
  966. logGroupEnd();
  967. };
  968. /**
  969. * @license
  970. * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
  971. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
  972. * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
  973. * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
  974. * Code distributed by Google as part of the polymer project is also
  975. * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
  976. */
  977. /**
  978. * WCT-specific behavior on top of Mocha's default HTML reporter.
  979. *
  980. * @param {!Mocha.Runner} runner The runner that is being reported on.
  981. */
  982. function HTML(runner) {
  983. var output = document.createElement('div');
  984. output.id = 'mocha';
  985. document.body.appendChild(output);
  986. runner.on('suite', function(test) {
  987. this.total = runner.total;
  988. }.bind(this));
  989. Mocha.reporters.HTML.call(this, runner);
  990. }
  991. // Woo! What a hack. This just saves us from adding a bunch of complexity around
  992. // style loading.
  993. var style = document.createElement('style');
  994. style.textContent = 'html, body {' +
  995. ' position: relative;' +
  996. ' height: 100%;' +
  997. ' width: 100%;' +
  998. ' min-width: 900px;' +
  999. '}' +
  1000. '#mocha, #subsuites {' +
  1001. ' height: 100%;' +
  1002. ' position: absolute;' +
  1003. ' top: 0;' +
  1004. '}' +
  1005. '#mocha {' +
  1006. ' box-sizing: border-box;' +
  1007. ' margin: 0 !important;' +
  1008. ' overflow-y: auto;' +
  1009. ' padding: 60px 20px;' +
  1010. ' right: 0;' +
  1011. ' left: 500px;' +
  1012. '}' +
  1013. '#subsuites {' +
  1014. ' -ms-flex-direction: column;' +
  1015. ' -webkit-flex-direction: column;' +
  1016. ' display: -ms-flexbox;' +
  1017. ' display: -webkit-flex;' +
  1018. ' display: flex;' +
  1019. ' flex-direction: column;' +
  1020. ' left: 0;' +
  1021. ' width: 500px;' +
  1022. '}' +
  1023. '#subsuites .subsuite {' +
  1024. ' border: 0;' +
  1025. ' width: 100%;' +
  1026. ' height: 100%;' +
  1027. '}' +
  1028. '#mocha .test.pass .duration {' +
  1029. ' color: #555 !important;' +
  1030. '}';
  1031. document.head.appendChild(style);
  1032. var STACKY_CONFIG = {
  1033. indent: ' ',
  1034. locationStrip: [
  1035. /^https?:\/\/[^\/]+/,
  1036. /\?.*$/,
  1037. ],
  1038. filter: function(line) {
  1039. return line.location.match(/\/web-component-tester\/[^\/]+(\?.*)?$/);
  1040. },
  1041. };
  1042. // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36-46
  1043. var MOCHA_EVENTS = [
  1044. 'start',
  1045. 'end',
  1046. 'suite',
  1047. 'suite end',
  1048. 'test',
  1049. 'test end',
  1050. 'hook',
  1051. 'hook end',
  1052. 'pass',
  1053. 'fail',
  1054. 'pending',
  1055. 'childRunner end'
  1056. ];
  1057. // Until a suite has loaded, we assume this many tests in it.
  1058. var ESTIMATED_TESTS_PER_SUITE = 3;
  1059. /**
  1060. * A Mocha-like reporter that combines the output of multiple Mocha suites.
  1061. *
  1062. * @param {number} numSuites The number of suites that will be run, in order to
  1063. * estimate the total number of tests that will be performed.
  1064. * @param {!Array.<!Mocha.reporters.Base>} reporters The set of reporters that
  1065. * should receive the unified event stream.
  1066. * @param {MultiReporter} parent The parent reporter, if present.
  1067. */
  1068. function MultiReporter(numSuites, reporters, parent) {
  1069. this.reporters = reporters.map(function(reporter) {
  1070. return new reporter(this);
  1071. }.bind(this));
  1072. this.parent = parent;
  1073. this.basePath = parent && parent.basePath || basePath(window.location);
  1074. this.total = numSuites * ESTIMATED_TESTS_PER_SUITE;
  1075. // Mocha reporters assume a stream of events, so we have to be careful to only
  1076. // report on one runner at a time...
  1077. this.currentRunner = null;
  1078. // ...while we buffer events for any other active runners.
  1079. this.pendingEvents = [];
  1080. this.emit('start');
  1081. }
  1082. /**
  1083. * @param {!Location|string} location The location this reporter represents.
  1084. * @return {!Mocha.reporters.Base} A reporter-like "class" for each child suite
  1085. * that should be passed to `mocha.run`.
  1086. */
  1087. MultiReporter.prototype.childReporter = function childReporter(location) {
  1088. var name = this.suiteTitle(location);
  1089. // The reporter is used as a constructor, so we can't depend on `this` being
  1090. // properly bound.
  1091. var self = this;
  1092. function reporter(runner) {
  1093. runner.name = name;
  1094. self.bindChildRunner(runner);
  1095. }
  1096. reporter.title = name;
  1097. return reporter;
  1098. };
  1099. /** Must be called once all runners have finished. */
  1100. MultiReporter.prototype.done = function done() {
  1101. this.complete = true;
  1102. this.flushPendingEvents();
  1103. this.emit('end');
  1104. };
  1105. /**
  1106. * Emit a top level test that is not part of any suite managed by this reporter.
  1107. *
  1108. * Helpful for reporting on global errors, loading issues, etc.
  1109. *
  1110. * @param {string} title The title of the test.
  1111. * @param {*} opt_error An error associated with this test. If falsy, test is
  1112. * considered to be passing.
  1113. * @param {string} opt_suiteTitle Title for the suite that's wrapping the test.
  1114. * @param {?boolean} opt_estimated If this test was included in the original
  1115. * estimate of `numSuites`.
  1116. */
  1117. MultiReporter.prototype.emitOutOfBandTest = function emitOutOfBandTest(title, opt_error, opt_suiteTitle, opt_estimated) {
  1118. debug('MultiReporter#emitOutOfBandTest(', arguments, ')');
  1119. var root = new Mocha.Suite();
  1120. root.title = opt_suiteTitle || '';
  1121. var test = new Mocha.Test(title, function() {
  1122. });
  1123. test.parent = root;
  1124. test.state = opt_error ? 'failed' : 'passed';
  1125. test.err = opt_error;
  1126. if (!opt_estimated) {
  1127. this.total = this.total + ESTIMATED_TESTS_PER_SUITE;
  1128. }
  1129. var runner = {total: 1};
  1130. this.proxyEvent('start', runner);
  1131. this.proxyEvent('suite', runner, root);
  1132. this.proxyEvent('test', runner, test);
  1133. if (opt_error) {
  1134. this.proxyEvent('fail', runner, test, opt_error);
  1135. } else {
  1136. this.proxyEvent('pass', runner, test);
  1137. }
  1138. this.proxyEvent('test end', runner, test);
  1139. this.proxyEvent('suite end', runner, root);
  1140. this.proxyEvent('end', runner);
  1141. };
  1142. /**
  1143. * @param {!Location|string} location
  1144. * @return {string}
  1145. */
  1146. MultiReporter.prototype.suiteTitle = function suiteTitle(location) {
  1147. var path = relativeLocation(location, this.basePath);
  1148. path = cleanLocation(path);
  1149. return path;
  1150. };
  1151. // Internal Interface
  1152. /** @param {!Mocha.runners.Base} runner The runner to listen to events for. */
  1153. MultiReporter.prototype.bindChildRunner = function bindChildRunner(runner) {
  1154. MOCHA_EVENTS.forEach(function(eventName) {
  1155. runner.on(eventName, this.proxyEvent.bind(this, eventName, runner));
  1156. }.bind(this));
  1157. };
  1158. /**
  1159. * Evaluates an event fired by `runner`, proxying it forward or buffering it.
  1160. *
  1161. * @param {string} eventName
  1162. * @param {!Mocha.runners.Base} runner The runner that emitted this event.
  1163. * @param {...*} var_args Any additional data passed as part of the event.
  1164. */
  1165. MultiReporter.prototype.proxyEvent = function proxyEvent(eventName, runner, var_args) {
  1166. var extraArgs = Array.prototype.slice.call(arguments, 2);
  1167. if (this.complete) {
  1168. console.warn('out of order Mocha event for ' + runner.name + ':', eventName, extraArgs);
  1169. return;
  1170. }
  1171. if (this.currentRunner && runner !== this.currentRunner) {
  1172. this.pendingEvents.push(arguments);
  1173. return;
  1174. }
  1175. debug('MultiReporter#proxyEvent(', arguments, ')');
  1176. // This appears to be a Mocha bug: Tests failed by passing an error to their
  1177. // done function don't set `err` properly.
  1178. //
  1179. // TODO(nevir): Track down.
  1180. if (eventName === 'fail' && !extraArgs[0].err) {
  1181. extraArgs[0].err = extraArgs[1];
  1182. }
  1183. if (eventName === 'start') {
  1184. this.onRunnerStart(runner);
  1185. } else if (eventName === 'end') {
  1186. this.onRunnerEnd(runner);
  1187. } else {
  1188. this.cleanEvent(eventName, runner, extraArgs);
  1189. this.emit.apply(this, [eventName].concat(extraArgs));
  1190. }
  1191. };
  1192. /**
  1193. * Cleans or modifies an event if needed.
  1194. *
  1195. * @param {string} eventName
  1196. * @param {!Mocha.runners.Base} runner The runner that emitted this event.
  1197. * @param {!Array.<*>} extraArgs
  1198. */
  1199. MultiReporter.prototype.cleanEvent = function cleanEvent(eventName, runner, extraArgs) {
  1200. // Suite hierarchy
  1201. if (extraArgs[0]) {
  1202. extraArgs[0] = this.showRootSuite(extraArgs[0]);
  1203. }
  1204. // Normalize errors
  1205. if (eventName === 'fail') {
  1206. extraArgs[1] = Stacky.normalize(extraArgs[1], STACKY_CONFIG);
  1207. }
  1208. if (extraArgs[0] && extraArgs[0].err) {
  1209. extraArgs[0].err = Stacky.normalize(extraArgs[0].err, STACKY_CONFIG);
  1210. }
  1211. };
  1212. /**
  1213. * We like to show the root suite's title, which requires a little bit of
  1214. * trickery in the suite hierarchy.
  1215. *
  1216. * @param {!Mocha.Runnable} node
  1217. */
  1218. MultiReporter.prototype.showRootSuite = function showRootSuite(node) {
  1219. var leaf = node = Object.create(node);
  1220. while (node && node.parent) {
  1221. var wrappedParent = Object.create(node.parent);
  1222. node.parent = wrappedParent;
  1223. node = wrappedParent;
  1224. }
  1225. node.root = false;
  1226. return leaf;
  1227. };
  1228. /** @param {!Mocha.runners.Base} runner */
  1229. MultiReporter.prototype.onRunnerStart = function onRunnerStart(runner) {
  1230. debug('MultiReporter#onRunnerStart:', runner.name);
  1231. this.total = this.total - ESTIMATED_TESTS_PER_SUITE + runner.total;
  1232. this.currentRunner = runner;
  1233. };
  1234. /** @param {!Mocha.runners.Base} runner */
  1235. MultiReporter.prototype.onRunnerEnd = function onRunnerEnd(runner) {
  1236. debug('MultiReporter#onRunnerEnd:', runner.name);
  1237. this.currentRunner = null;
  1238. this.flushPendingEvents();
  1239. };
  1240. /**
  1241. * Flushes any buffered events and runs them through `proxyEvent`. This will
  1242. * loop until all buffered runners are complete, or we have run out of buffered
  1243. * events.
  1244. */
  1245. MultiReporter.prototype.flushPendingEvents = function flushPendingEvents() {
  1246. var events = this.pendingEvents;
  1247. this.pendingEvents = [];
  1248. events.forEach(function(eventArgs) {
  1249. this.proxyEvent.apply(this, eventArgs);
  1250. }.bind(this));
  1251. };
  1252. var ARC_OFFSET = 0; // start at the right.
  1253. var ARC_WIDTH = 6;
  1254. /**
  1255. * A Mocha reporter that updates the document's title and favicon with
  1256. * at-a-glance stats.
  1257. *
  1258. * @param {!Mocha.Runner} runner The runner that is being reported on.
  1259. */
  1260. function Title(runner) {
  1261. Mocha.reporters.Base.call(this, runner);
  1262. runner.on('test end', this.report.bind(this));
  1263. }
  1264. /** Reports current stats via the page title and favicon. */
  1265. Title.prototype.report = function report() {
  1266. this.updateTitle();
  1267. this.updateFavicon();
  1268. };
  1269. /** Updates the document title with a summary of current stats. */
  1270. Title.prototype.updateTitle = function updateTitle() {
  1271. if (this.stats.failures > 0) {
  1272. document.title = pluralizedStat(this.stats.failures, 'failing');
  1273. } else {
  1274. document.title = pluralizedStat(this.stats.passes, 'passing');
  1275. }
  1276. };
  1277. /**
  1278. * Draws an arc for the favicon status, relative to the total number of tests.
  1279. *
  1280. * @param {!CanvasRenderingContext2D} context
  1281. * @param {number} total
  1282. * @param {number} start
  1283. * @param {number} length
  1284. * @param {string} color
  1285. */
  1286. function drawFaviconArc(context, total, start, length, color) {
  1287. var arcStart = ARC_OFFSET + Math.PI * 2 * (start / total);
  1288. var arcEnd = ARC_OFFSET + Math.PI * 2 * ((start + length) / total);
  1289. context.beginPath();
  1290. context.strokeStyle = color;
  1291. context.lineWidth = ARC_WIDTH;
  1292. context.arc(16, 16, 16 - ARC_WIDTH / 2, arcStart, arcEnd);
  1293. context.stroke();
  1294. }
  1295. /** Updates the document's favicon w/ a summary of current stats. */
  1296. Title.prototype.updateFavicon = function updateFavicon() {
  1297. var canvas = document.createElement('canvas');
  1298. canvas.height = canvas.width = 32;
  1299. var context = canvas.getContext('2d');
  1300. var passing = this.stats.passes;
  1301. var pending = this.stats.pending;
  1302. var failing = this.stats.failures;
  1303. var total = Math.max(this.runner.total, passing + pending + failing);
  1304. drawFaviconArc(context, total, 0, passing, '#0e9c57');
  1305. drawFaviconArc(context, total, passing, pending, '#f3b300');
  1306. drawFaviconArc(context, total, pending + passing, failing, '#ff5621');
  1307. this.setFavicon(canvas.toDataURL());
  1308. };
  1309. /** Sets the current favicon by URL. */
  1310. Title.prototype.setFavicon = function setFavicon(url) {
  1311. var current = document.head.querySelector('link[rel="icon"]');
  1312. if (current) {
  1313. document.head.removeChild(current);
  1314. }
  1315. var link = document.createElement('link');
  1316. link.rel = 'icon';
  1317. link.type = 'image/x-icon';
  1318. link.href = url;
  1319. link.setAttribute('sizes', '32x32');
  1320. document.head.appendChild(link);
  1321. };
  1322. /**
  1323. * @param {CLISocket} socket The CLI socket, if present.
  1324. * @param {MultiReporter} parent The parent reporter, if present.
  1325. * @return {!Array.<!Mocha.reporters.Base} The reporters that should be used.
  1326. */
  1327. function determineReporters(socket, parent) {
  1328. // Parents are greedy.
  1329. if (parent) {
  1330. return [parent.childReporter(window.location)];
  1331. }
  1332. // Otherwise, we get to run wild without any parental supervision!
  1333. var reporters = [Title, Console];
  1334. if (socket) {
  1335. reporters.push(function(runner) {
  1336. socket.observe(runner);
  1337. });
  1338. }
  1339. if (htmlSuites$1.length > 0 || jsSuites$1.length > 0) {
  1340. reporters.push(HTML);
  1341. }
  1342. return reporters;
  1343. }
  1344. /**
  1345. * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy.
  1346. */
  1347. function injectMocha(Mocha) {
  1348. _injectPrototype(Console, Mocha.reporters.Base.prototype);
  1349. _injectPrototype(HTML, Mocha.reporters.HTML.prototype);
  1350. // Mocha doesn't expose its `EventEmitter` shim directly, so:
  1351. _injectPrototype(MultiReporter, Object.getPrototypeOf(Mocha.Runner.prototype));
  1352. }
  1353. function _injectPrototype(klass, prototype) {
  1354. var newPrototype = Object.create(prototype);
  1355. // Only support
  1356. Object.keys(klass.prototype).forEach(function(key) {
  1357. newPrototype[key] = klass.prototype[key];
  1358. });
  1359. klass.prototype = newPrototype;
  1360. }
  1361. /**
  1362. * Loads all environment scripts ...synchronously ...after us.
  1363. */
  1364. function loadSync() {
  1365. debug('Loading environment scripts:');
  1366. var a11ySuite = 'web-component-tester/data/a11ySuite.js';
  1367. var scripts = get('environmentScripts');
  1368. var a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySuite) > -1;
  1369. if (!a11ySuiteWillBeLoaded) {
  1370. // wct is running as a bower dependency, load a11ySuite from data/
  1371. scripts.push(a11ySuite);
  1372. }
  1373. scripts.forEach(function(path) {
  1374. var url = expandUrl(path, get('root'));
  1375. debug('Loading environment script:', url);
  1376. // Synchronous load.
  1377. document.write('<script src="' + encodeURI(url) + '"></script>'); // jshint ignore:line
  1378. });
  1379. debug('Environment scripts loaded');
  1380. var imports = get('environmentImports');
  1381. imports.forEach(function(path) {
  1382. var url = expandUrl(path, get('root'));
  1383. debug('Loading environment import:', url);
  1384. // Synchronous load.
  1385. document.write('<link rel="import" href="' + encodeURI(url) + '">'); // jshint ignore:line
  1386. });
  1387. debug('Environment imports loaded');
  1388. }
  1389. /**
  1390. * We have some hard dependencies on things that should be loaded via
  1391. * `environmentScripts`, so we assert that they're present here; and do any
  1392. * post-facto setup.
  1393. */
  1394. function ensureDependenciesPresent() {
  1395. _ensureMocha();
  1396. _checkChai();
  1397. }
  1398. function _ensureMocha() {
  1399. var Mocha = window.Mocha;
  1400. if (!Mocha) {
  1401. 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');
  1402. }
  1403. injectMocha(Mocha);
  1404. // Magic loading of mocha's stylesheet
  1405. var mochaPrefix = scriptPrefix('mocha.js');
  1406. // only load mocha stylesheet for the test runner output
  1407. // Not the end of the world, if it doesn't load.
  1408. if (mochaPrefix && window.top === window.self) {
  1409. loadStyle(mochaPrefix + 'mocha.css');
  1410. }
  1411. }
  1412. function _checkChai() {
  1413. if (!window.chai) {
  1414. debug('Chai not present; not registering shorthands');
  1415. return;
  1416. }
  1417. window.assert = window.chai.assert;
  1418. window.expect = window.chai.expect;
  1419. }
  1420. var SOCKETIO_ENDPOINT = window.location.protocol + '//' + window.location.host;
  1421. var SOCKETIO_LIBRARY = SOCKETIO_ENDPOINT + '/socket.io/socket.io.js';
  1422. /**
  1423. * A socket for communication between the CLI and browser runners.
  1424. *
  1425. * @param {string} browserId An ID generated by the CLI runner.
  1426. * @param {!io.Socket} socket The socket.io `Socket` to communicate over.
  1427. */
  1428. function CLISocket(browserId, socket) {
  1429. this.browserId = browserId;
  1430. this.socket = socket;
  1431. }
  1432. /**
  1433. * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting
  1434. * interesting events back to the CLI runner.
  1435. */
  1436. CLISocket.prototype.observe = function observe(runner) {
  1437. this.emitEvent('browser-start', {
  1438. url: window.location.toString(),
  1439. });
  1440. // We only emit a subset of events that we care about, and follow a more
  1441. // general event format that is hopefully applicable to test runners beyond
  1442. // mocha.
  1443. //
  1444. // For all possible mocha events, see:
  1445. // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36
  1446. runner.on('test', function(test) {
  1447. this.emitEvent('test-start', {test: getTitles(test)});
  1448. }.bind(this));
  1449. runner.on('test end', function(test) {
  1450. this.emitEvent('test-end', {
  1451. state: getState(test),
  1452. test: getTitles(test),
  1453. duration: test.duration,
  1454. error: test.err,
  1455. });
  1456. }.bind(this));
  1457. runner.on('childRunner start', function(childRunner) {
  1458. this.emitEvent('sub-suite-start', childRunner.share);
  1459. }.bind(this));
  1460. runner.on('childRunner end', function(childRunner) {
  1461. this.emitEvent('sub-suite-end', childRunner.share);
  1462. }.bind(this));
  1463. runner.on('end', function() {
  1464. this.emitEvent('browser-end');
  1465. }.bind(this));
  1466. };
  1467. /**
  1468. * @param {string} event The name of the event to fire.
  1469. * @param {*} data Additional data to pass with the event.
  1470. */
  1471. CLISocket.prototype.emitEvent = function emitEvent(event, data) {
  1472. this.socket.emit('client-event', {
  1473. browserId: this.browserId,
  1474. event: event,
  1475. data: data,
  1476. });
  1477. };
  1478. /**
  1479. * Builds a `CLISocket` if we are within a CLI-run environment; short-circuits
  1480. * otherwise.
  1481. *
  1482. * @param {function(*, CLISocket)} done Node-style callback.
  1483. */
  1484. CLISocket.init = function