From 94648c32160581f0a05c75024d4b9d3e87192410 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Thu, 15 Jun 2017 02:16:58 -0400 Subject: [PATCH 01/12] Find out what happens if every job posts to Coveralls; rationale: Coveralls typically uses the Job ID, not the Build ID, so presumably it is designed to handle multiple jobs in the same build? --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8a76015bdd..53f276ae7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,12 +11,13 @@ env: # phantomjs hosts binaries @ bitbucket, which has fairly restrictive # rate-limiting. pull it from this sketchy site in China instead. - PHANTOMJS_CDNURL='https://cnpmjs.org/downloads' + COVERAGE=true matrix: fast_finish: true include: - node_js: '8' - env: TARGET=test-node COVERAGE=true + env: TARGET=test-node - node_js: '7' env: TARGET=test-node - node_js: '6' From 5fdc4976b788fec222540d46c2c3590727e10666 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Fri, 16 Jun 2017 19:02:18 -0400 Subject: [PATCH 02/12] Instrumented code will run slower 200 ms timeout was already causing flakes even before the coverage additions anyway; testing suggests that specific tests won't pass with instrumentation without increased time. --- test/browser-fixtures/bdd.fixture.js | 2 +- test/browser-fixtures/exports.fixture.js | 2 +- test/browser-fixtures/qunit.fixture.js | 2 +- test/browser-fixtures/tdd.fixture.js | 2 +- test/unit/context.spec.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/browser-fixtures/bdd.fixture.js b/test/browser-fixtures/bdd.fixture.js index 5fe802f74a..90c8dd72d8 100644 --- a/test/browser-fixtures/bdd.fixture.js +++ b/test/browser-fixtures/bdd.fixture.js @@ -4,5 +4,5 @@ process.stdout = require('browser-stdout')(); -window.mocha.timeout(200) +window.mocha.timeout(500) .ui('bdd'); diff --git a/test/browser-fixtures/exports.fixture.js b/test/browser-fixtures/exports.fixture.js index a4c25cff99..1ddabc8620 100644 --- a/test/browser-fixtures/exports.fixture.js +++ b/test/browser-fixtures/exports.fixture.js @@ -4,5 +4,5 @@ process.stdout = require('browser-stdout')(); -window.mocha.timeout(200) +window.mocha.timeout(500) .ui('exports'); diff --git a/test/browser-fixtures/qunit.fixture.js b/test/browser-fixtures/qunit.fixture.js index 79b49498d5..2c0b642100 100644 --- a/test/browser-fixtures/qunit.fixture.js +++ b/test/browser-fixtures/qunit.fixture.js @@ -4,5 +4,5 @@ process.stdout = require('browser-stdout')(); -window.mocha.timeout(200) +window.mocha.timeout(500) .ui('qunit'); diff --git a/test/browser-fixtures/tdd.fixture.js b/test/browser-fixtures/tdd.fixture.js index 2fdc8f758c..a36040b17f 100644 --- a/test/browser-fixtures/tdd.fixture.js +++ b/test/browser-fixtures/tdd.fixture.js @@ -4,5 +4,5 @@ process.stdout = require('browser-stdout')(); -window.mocha.timeout(200) +window.mocha.timeout(500) .ui('tdd'); diff --git a/test/unit/context.spec.js b/test/unit/context.spec.js index cbcdedb49d..a73fc86210 100644 --- a/test/unit/context.spec.js +++ b/test/unit/context.spec.js @@ -68,6 +68,6 @@ describe('Context Siblings', function () { describe('timeout()', function () { it('should return the timeout', function () { - expect(this.timeout()).to.equal(200); + expect(this.timeout()).to.equal(!process.browser ? 200 : 500); }); }); From b4efa79992df86f98dd079e445d076d66b07eba5 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Fri, 16 Jun 2017 19:06:50 -0400 Subject: [PATCH 03/12] Add browser coverage --- karma.conf.js | 34 ++++++++++++++++++++++++++++++---- package.json | 2 ++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index b8d73fd0e3..1316004b4b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -5,9 +5,19 @@ var path = require('path'); var mkdirp = require('mkdirp'); var baseBundleDirpath = path.join(__dirname, '.karma'); var osName = require('os-name'); +var workaroundMultiplePreprocessorIncompatibility = require('browserify-istanbul'); +var karmaCoverageIstanbul; +try { + karmaCoverageIstanbul = require('karma-coverage/node_modules/istanbul'); +} catch (ignore) {} module.exports = function (config) { var bundleDirpath; + var filesBase = [ + // make browserify bundle these properly (if nothing else, this is necessary for coverage transform; unclear whether it makes a difference as to how browserify gets them otherwise, as it doesn't print any debug logs about them without it) + { pattern: 'browser-entry.js', included: false, served: false }, + { pattern: 'lib/**/*.js', included: false, served: false } + ]; var cfg = { frameworks: [ 'browserify', @@ -23,12 +33,12 @@ module.exports = function (config) { 'karma-spec-reporter', require('@coderbyheart/karma-sauce-launcher') ], - files: [ + files: filesBase.concat([ // we use the BDD interface for all of the tests that // aren't interface-specific. 'test/browser-fixtures/bdd.fixture.js', 'test/unit/*.spec.js' - ], + ]), preprocessors: { 'test/**/*.js': ['browserify'] }, @@ -128,14 +138,30 @@ module.exports = function (config) { if (cfg.sauceLabs) { cfg.sauceLabs.testName = 'Interface "' + ui + '" integration tests'; } - cfg.files = [ + cfg.files = filesBase.concat([ 'test/browser-fixtures/' + ui + '.fixture.js', 'test/interfaces/' + ui + '.spec.js' - ]; + ]); } else if (cfg.sauceLabs) { cfg.sauceLabs.testName = 'Unit Tests'; } + if (env.COVERAGE) { + cfg.plugins.push('karma-coverage'); + filesBase.forEach(function (file) { + cfg.preprocessors[file.pattern] = ['browserify']; + }); + cfg.reporters.push('coverage'); + cfg.coverageReporter = { + reporters: [ { type: 'json' }, { type: 'text-summary' } ], + dir: 'coverage/reports/browser' + (ui ? '-' + ui : ''), + subdir: '.', + includeAllSources: true + }; + cfg.browserify.transform = [ workaroundMultiplePreprocessorIncompatibility({ ignore: ['**/node_modules/**', '**/test/**'], instrumenter: karmaCoverageIstanbul }) ]; + console.error('Reporting coverage to ' + cfg.coverageReporter.dir); + } + config.set(cfg); }; diff --git a/package.json b/package.json index e772d90290..9b1f9a9e6c 100644 --- a/package.json +++ b/package.json @@ -323,6 +323,7 @@ "@coderbyheart/karma-sauce-launcher": "coderbyheart/karma-sauce-launcher#5259942cd6d40090eaa13ceeef5b0b8738c7710f", "assert": "^1.4.1", "browserify": "^13.0.0", + "browserify-istanbul": "^2.0.0", "coffee-script": "^1.10.0", "coveralls": "^2.11.15", "eslint": "^3.11.1", @@ -335,6 +336,7 @@ "karma": "1.3.0", "karma-browserify": "^5.0.5", "karma-chrome-launcher": "^2.0.0", + "karma-coverage": "^1.1.1", "karma-expect": "^1.1.2", "karma-mocha": "^1.3.0", "karma-phantomjs-launcher": "0.2.3", From 2aa9c6a5a20821118e21a88a67abcd12272bec1b Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Thu, 15 Jun 2017 16:37:56 -0400 Subject: [PATCH 04/12] Try using the same instrumentation as NYC from the Node tests --- karma.conf.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 1316004b4b..b58ffbdc85 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -6,10 +6,13 @@ var mkdirp = require('mkdirp'); var baseBundleDirpath = path.join(__dirname, '.karma'); var osName = require('os-name'); var workaroundMultiplePreprocessorIncompatibility = require('browserify-istanbul'); -var karmaCoverageIstanbul; +var istanbulLib; try { - karmaCoverageIstanbul = require('karma-coverage/node_modules/istanbul'); -} catch (ignore) {} + istanbulLib = require('nyc/node_modules/istanbul-lib-instrument'); +} catch (ignore) { + istanbulLib = require('istanbul-lib-instrument'); +} +var nyc = { Instrumenter: function (options) { return istanbulLib.createInstrumenter(options); } }; module.exports = function (config) { var bundleDirpath; @@ -153,12 +156,13 @@ module.exports = function (config) { }); cfg.reporters.push('coverage'); cfg.coverageReporter = { + instrumenters: { istanbul: nyc }, reporters: [ { type: 'json' }, { type: 'text-summary' } ], dir: 'coverage/reports/browser' + (ui ? '-' + ui : ''), subdir: '.', includeAllSources: true }; - cfg.browserify.transform = [ workaroundMultiplePreprocessorIncompatibility({ ignore: ['**/node_modules/**', '**/test/**'], instrumenter: karmaCoverageIstanbul }) ]; + cfg.browserify.transform = [ workaroundMultiplePreprocessorIncompatibility({ ignore: ['**/node_modules/**', '**/test/**'], instrumenter: nyc, instrumenterConfig: { autoWrap: true, embedSource: true, produceSourceMap: true, noCompact: false } }) ]; console.error('Reporting coverage to ' + cfg.coverageReporter.dir); } From 58e4b5d93feeb6f8ff3e24dd27d7a9d7a4889fd6 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Sun, 18 Jun 2017 02:22:12 -0400 Subject: [PATCH 05/12] Try using browser tests as integration testing for `browser-entry.js` by covering it in the `BUILDTMP/mocha.js` bundle --- Makefile | 26 ++++++++++++++++++-------- instrumentBrowserEntry.js | 11 +++++++++++ karma.conf.js | 10 ++-------- nycInstrumenter.js | 16 ++++++++++++++++ 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 instrumentBrowserEntry.js create mode 100644 nycInstrumenter.js diff --git a/Makefile b/Makefile index 2fa367e8ff..fcd4c3f60a 100644 --- a/Makefile +++ b/Makefile @@ -7,25 +7,35 @@ ifdef COVERAGE define test_node $(NYC) --report-dir coverage/reports/$(1) $(MOCHA) endef + instrument_browser := -t ./instrumentBrowserEntry else test_node := $(MOCHA) endif +define bundle_command + $(BROWSERIFY) ./browser-entry \ + $(1) \ + --plugin ./scripts/dedefine \ + --ignore 'fs' \ + --ignore 'glob' \ + --ignore 'path' \ + --ignore 'supports-color' > $@ +endef + TM_BUNDLE = JavaScript\ mocha.tmbundle SRC = $(shell find lib -name "*.js" -type f | LC_ALL=C sort) TESTS = $(shell find test -name "*.js" -type f | sort) all: mocha.js -mocha.js BUILDTMP/mocha.js: $(SRC) browser-entry.js - @printf "==> [Browser :: build]\n" +mocha.js: $(SRC) browser-entry.js + @printf "==> [Browser :: Build]\n" + $(call bundle_command) + +BUILDTMP/mocha.js: $(SRC) browser-entry.js + @printf "==> [Browser :: Build :: Test]\n" mkdir -p ${@D} - $(BROWSERIFY) ./browser-entry \ - --plugin ./scripts/dedefine \ - --ignore 'fs' \ - --ignore 'glob' \ - --ignore 'path' \ - --ignore 'supports-color' > $@ + $(call bundle_command,$(instrument_browser)) clean: @printf "==> [Clean]\n" diff --git a/instrumentBrowserEntry.js b/instrumentBrowserEntry.js new file mode 100644 index 0000000000..2e53dc08af --- /dev/null +++ b/instrumentBrowserEntry.js @@ -0,0 +1,11 @@ +'use strict'; + +var browserifyIstanbul = require('browserify-istanbul'); + +var nyc = require('./nycInstrumenter'); + +var overrideOptions = { ignore: ['**/lib/**', '**/node_modules/**', '**/test/**'], instrumenter: nyc }; + +module.exports = function (file, options) { + return browserifyIstanbul(file, Object.assign({}, options, overrideOptions)); +}; diff --git a/karma.conf.js b/karma.conf.js index b58ffbdc85..a95fd321d8 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -6,13 +6,7 @@ var mkdirp = require('mkdirp'); var baseBundleDirpath = path.join(__dirname, '.karma'); var osName = require('os-name'); var workaroundMultiplePreprocessorIncompatibility = require('browserify-istanbul'); -var istanbulLib; -try { - istanbulLib = require('nyc/node_modules/istanbul-lib-instrument'); -} catch (ignore) { - istanbulLib = require('istanbul-lib-instrument'); -} -var nyc = { Instrumenter: function (options) { return istanbulLib.createInstrumenter(options); } }; +var nyc = require('./nycInstrumenter'); module.exports = function (config) { var bundleDirpath; @@ -162,7 +156,7 @@ module.exports = function (config) { subdir: '.', includeAllSources: true }; - cfg.browserify.transform = [ workaroundMultiplePreprocessorIncompatibility({ ignore: ['**/node_modules/**', '**/test/**'], instrumenter: nyc, instrumenterConfig: { autoWrap: true, embedSource: true, produceSourceMap: true, noCompact: false } }) ]; + cfg.browserify.transform = [ workaroundMultiplePreprocessorIncompatibility({ ignore: ['**/node_modules/**', '**/test/**'], instrumenter: nyc }) ]; console.error('Reporting coverage to ' + cfg.coverageReporter.dir); } diff --git a/nycInstrumenter.js b/nycInstrumenter.js new file mode 100644 index 0000000000..ca3da3952c --- /dev/null +++ b/nycInstrumenter.js @@ -0,0 +1,16 @@ +'use strict'; + +var defaultOptions = { + autoWrap: true, + embedSource: true, + produceSourceMap: true, + noCompact: false +}; + +var istanbulLib; +try { + istanbulLib = require('nyc/node_modules/istanbul-lib-instrument'); +} catch (ignore) { + istanbulLib = require('istanbul-lib-instrument'); +} +module.exports = { Instrumenter: function (options) { return istanbulLib.createInstrumenter(Object.assign({}, defaultOptions, options)); } }; From da7b42d15b306dca2366ee69270859357f721a1a Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Sun, 18 Jun 2017 20:10:39 -0400 Subject: [PATCH 06/12] In prep for browser reporter tests, ensure nothing is missing (css) --- Makefile | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fcd4c3f60a..d6c13165d0 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,18 @@ mocha.js: $(SRC) browser-entry.js @printf "==> [Browser :: Build]\n" $(call bundle_command) -BUILDTMP/mocha.js: $(SRC) browser-entry.js - @printf "==> [Browser :: Build :: Test]\n" - mkdir -p ${@D} +BUILDTMP/mocha.js: $(SRC) browser-entry.js BUILDTMP BUILDTMP/mocha.css + @printf "==> [Browser :: Build :: Test :: Bundle]\n" $(call bundle_command,$(instrument_browser)) +BUILDTMP/mocha.css: BUILDTMP + @printf "==> [Browser :: Build :: Test :: CSS]\n" + cp mocha.css $@ + +BUILDTMP: + @printf "==> [Browser :: Build :: Test :: Temporary Directory]\n" + mkdir -p $@ + clean: @printf "==> [Clean]\n" rm -rf BUILDTMP From a9bfeb44af95845b6fa04bc68dd197bbd3cb99b1 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Sun, 18 Jun 2017 20:11:26 -0400 Subject: [PATCH 07/12] Run browser-only tests of various sorts --- karma.conf.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index a95fd321d8..c519f8f9f1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -34,7 +34,9 @@ module.exports = function (config) { // we use the BDD interface for all of the tests that // aren't interface-specific. 'test/browser-fixtures/bdd.fixture.js', - 'test/unit/*.spec.js' + 'test/unit/*.spec.js', + 'test/browser-unit/*.spec.js', + 'test/browser-reporters/*.spec.js' ]), preprocessors: { 'test/**/*.js': ['browserify'] From 02ce4a3b6186320e3b036ed7d28326c4d6c03028 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Sun, 18 Jun 2017 20:46:06 -0400 Subject: [PATCH 08/12] Add HTML reporter tests --- test/browser-reporters/html.spec.js | 1918 +++++++++++++++++++++++++++ test/browser/self-test.html | 31 + 2 files changed, 1949 insertions(+) create mode 100644 test/browser-reporters/html.spec.js create mode 100644 test/browser/self-test.html diff --git a/test/browser-reporters/html.spec.js b/test/browser-reporters/html.spec.js new file mode 100644 index 0000000000..b8a4d2ce39 --- /dev/null +++ b/test/browser-reporters/html.spec.js @@ -0,0 +1,1918 @@ +'use strict'; + +var HTML = require('../../lib/reporters/html'); + +var Suite = require('../../lib/suite'); +var Context = require('../../lib/context'); +var Test = require('../../lib/test'); + +describe('HTML reporter', function () { + this.slow(250); + + var realOutput; + var realOutputStats; + var realOutputReport; + + function saveRealOutput () { + if (!realOutput) { + realOutput = document.getElementById('mocha'); + realOutputStats = document.getElementById('mocha-stats'); + realOutputReport = document.getElementById('mocha-report'); + } + } + + function removeRealOutput () { + saveRealOutput(); + if (realOutput) { + realOutput.id = null; + realOutputStats.id = null; + realOutputReport.id = null; + } + } + + function stubOutput () { + removeRealOutput(); + var tmp = document.createElement('div'); + tmp.id = 'mocha'; + document.body.appendChild(tmp); + return tmp; + } + + function restoreOutput () { + var tmp = document.getElementById('mocha'); + if (tmp && tmp !== realOutput) { tmp.parentNode.removeChild(tmp); } + if (realOutput) { + realOutput.id = 'mocha'; + realOutputStats.id = 'mocha-stats'; + realOutputReport.id = 'mocha-report'; + } + return tmp !== realOutput ? tmp : undefined; + } + + before(saveRealOutput); // so that the following afterEach is guaranteed to work + + afterEach(function () { restoreOutput(); }); // WARNING: this is solely a fallback so *further* tests can be guaranteed to report correctly; these tests will not be reported correctly unless `restoreOutput` is called at the end of the test, before throwing any errors + + it('should show an error if the tag is missing', function () { + try { + // TODO: Figure out why it's ok to add an element to display this error but not ok to just add the missing element in the first place. + removeRealOutput(); + new HTML(new Runner()); // eslint-disable-line no-new + var error = document.getElementById('mocha-error'); + expect(error).not.to.be(null); + expect(error).not.to.be(undefined); + error.parentNode.removeChild(error); + expect(error.outerHTML).to.equal('
#mocha div missing, add it to your document
'); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should output to the "mocha" div', function () { + try { + stubOutput(); + var runner = new Runner(7); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + var mainSuite = Suite.create(rootSuite, 'suite'); + mainSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + mainSuite.addTest(new Test('pending')); + mainSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + mainSuite.addTest(new Test('slow', function (done) { setTimeout(done, 100); })); + mainSuite.addTest(new Test('less slow', function (done) { setTimeout(done, 50); })); + var failingTest = function () { + var AssertionError = function (message, actual, expected) { + this.message = message; + this.actual = actual; + this.expected = expected; + this.showDiff = true; + }; + AssertionError.prototype = Object.create(Error.prototype); + AssertionError.prototype.name = 'AssertionError'; + AssertionError.prototype.constructor = AssertionError; + throw new AssertionError('example', 'text with a typo', 'text without a typo'); + }; + mainSuite.addTest(new Test('failing', failingTest)); + mainSuite.addTest(new Test('timeout', function (done) { /* does not complete */; })); // eslint-disable-line no-extra-semi + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', mainSuite); + runner.emit('test', mainSuite.tests[0]); + mainSuite.tests[0].duration = 0; + mainSuite.tests[0].state = 'passed'; + runner.emit('pass', mainSuite.tests[0]); + runner.emit('test end', mainSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', mainSuite.tests[1]); + runner.emit('test end', mainSuite.tests[1]); + runner.emit('test', mainSuite.tests[2]); + mainSuite.tests[2].pending = true; + mainSuite.tests[2].duration = 0; + runner.emit('pending', mainSuite.tests[2]); + runner.emit('test end', mainSuite.tests[2]); + runner.emit('test', mainSuite.tests[3]); + mainSuite.tests[3].timer = { '0': null }; + mainSuite.tests[3].duration = 116; + mainSuite.tests[3].state = 'passed'; + runner.emit('pass', mainSuite.tests[3]); + runner.emit('test end', mainSuite.tests[3]); + runner.emit('test', mainSuite.tests[4]); + mainSuite.tests[4].timer = { '0': null }; + mainSuite.tests[4].duration = 63; + mainSuite.tests[4].state = 'passed'; + runner.emit('pass', mainSuite.tests[4]); + runner.emit('test end', mainSuite.tests[4]); + runner.emit('test', mainSuite.tests[5]); + mainSuite.tests[5].duration = 0; + mainSuite.tests[5].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', mainSuite.tests[5], assertion); + } + runner.emit('test end', mainSuite.tests[5]); + runner.emit('test', mainSuite.tests[6]); + mainSuite.tests[6].timer = { '0': null }; + mainSuite.tests[6].duration = 211; + mainSuite.tests[6].state = 'failed'; + runner.emit('fail', mainSuite.tests[6], new Error('Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.')); + runner.emit('test end', mainSuite.tests[6]); + runner.emit('suite end', mainSuite); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; + var output = restoreOutput().outerHTML + .replace(/\u2023/g, '‣') + .replace(/(
  • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') + .replace(/href="[^"?]*[?]/g, 'href="?') + .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') + .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') + .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*
    • suite

      • passing0ms

        /* content here */;
      • pending

      • skipped at runtime

      • slow116ms

        setTimeout(done, 100);
      • less slow63ms

        setTimeout(done, 50);
      • failing

        AssertionError: example\nat STACKTRACE
        var AssertionError = function (message, actual, expected) {\n  this.message = message;\n  this.actual = actual;\n  this.expected = expected;\n  this.showDiff = true;\n};\nAssertionError.prototype = Object.create(Error.prototype);\nAssertionError.prototype.name = \'AssertionError\';\nAssertionError.prototype.constructor = AssertionError;\nthrow new AssertionError(\'example\', \'text with a typo\', \'text without a typo\');
      • timeout

        Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
        /* does not complete */;
    '); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should correctly output multiple adjacent nested suites', function () { + try { + stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'outer suite 1'); + Suite.create(rootSuite.suites[0], 'inner suite 1'); + rootSuite.suites[0].suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite.suites[0], 'inner suite 2'); + rootSuite.suites[0].suites[1].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'outer suite 2'); + Suite.create(rootSuite.suites[1], 'inner suite 1'); + rootSuite.suites[1].suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite.suites[1], 'inner suite 2'); + rootSuite.suites[1].suites[1].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[0].suites[0]); + runner.emit('test', rootSuite.suites[0].suites[0].tests[0]); + rootSuite.suites[0].suites[0].tests[0].duration = 0; + rootSuite.suites[0].suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0].suites[0]); + runner.emit('suite', rootSuite.suites[0].suites[1]); + runner.emit('test', rootSuite.suites[0].suites[1].tests[0]); + rootSuite.suites[0].suites[1].tests[0].duration = 0; + rootSuite.suites[0].suites[1].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[0].suites[1].tests[0]); + runner.emit('suite end', rootSuite.suites[0].suites[1]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[1].suites[0]); + runner.emit('test', rootSuite.suites[1].suites[0].tests[0]); + rootSuite.suites[1].suites[0].tests[0].duration = 0; + rootSuite.suites[1].suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[1].suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[1].suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[1].suites[0]); + runner.emit('suite', rootSuite.suites[1].suites[1]); + runner.emit('test', rootSuite.suites[1].suites[1].tests[0]); + rootSuite.suites[1].suites[1].tests[0].duration = 0; + rootSuite.suites[1].suites[1].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[1].suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].suites[1].tests[0]); + runner.emit('suite end', rootSuite.suites[1].suites[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; + var output = restoreOutput().outerHTML + .replace(/\u2023/g, '‣') + .replace(/(
  • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') + .replace(/href="[^"?]*[?]/g, 'href="?') + .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') + .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') + .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*'); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should output sanely when there are no tests', function () { + try { + stubOutput(); + var runner = new Runner(0); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; + var output = restoreOutput().outerHTML + .replace(/\u2023/g, '‣') + .replace(/(
  • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') + .replace(/href="[^"?]*[?]/g, 'href="?') + .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') + .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') + .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*
      '); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + // this is mostly to validate the reliability of the other tests, rather than because the reporter is actually meant to run multiple instances; after all, it depends on the current global document.getElementById('mocha') + describe('multiple instances', function () { + it('should print only to the current instance', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + var initialStates = [ + rootSuite.tests[0].state, + rootSuite.tests[1].pending, + rootSuite.tests[2].pending, + rootSuite.tests[3].state + ]; + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + var assertion; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (caughtAssertion) { + assertion = caughtAssertion; + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var originalHTML = output.outerHTML; + restoreOutput(); + var other = stubOutput(); + runner = new Runner(0); + new HTML(runner); // eslint-disable-line no-new + rootSuite.tests[0].state = initialStates[0]; + rootSuite.tests[1].pending = initialStates[1]; + rootSuite.tests[2].pending = initialStates[2]; + rootSuite.tests[3].state = initialStates[3]; + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 1; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 1; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 1; + rootSuite.tests[3].state = 'failed'; + runner.emit('fail', rootSuite.tests[3], assertion); + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + expect(output.outerHTML).to.be(originalHTML); + expect(output.outerHTML).not.to.equal(other.outerHTML); + expect(output.outerHTML + .replace(/(
    • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2')) + .to.equal(other.outerHTML.replace(/1ms/g, '0ms') + .replace(/(
    • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2')); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not affect other instances when the passes filter is clicked', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var originalHTML = output.outerHTML; + restoreOutput(); + var other = stubOutput(); + runner = new Runner(0); + new HTML(runner); // eslint-disable-line no-new + rootSuite = new Suite('', new Context()); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(other.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + expect(output.outerHTML).to.be(originalHTML); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not affect other instances when the failures filter is clicked', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var originalHTML = output.outerHTML; + restoreOutput(); + var other = stubOutput(); + runner = new Runner(0); + new HTML(runner); // eslint-disable-line no-new + rootSuite = new Suite('', new Context()); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(other.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + expect(output.outerHTML).to.be(originalHTML); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + }); + + describe('passes filter', function () { + it('should still show passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (/\bpass\b/.test(tests[index].className)) { + assertDisplayed(tests[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide all non-passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (!/\bpass\b/.test(tests[index].className)) { + assertDisplayed(tests[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide suites with only non-passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (!suites[index].getElementsByClassName('test pass').length) { + assertDisplayed(suites[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not hide suites with passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (suites[index].getElementsByClassName('test pass').length > 0) { + assertDisplayed(suites[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should unhide all tests and suites when clicked again', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex += 1) { + assertDisplayed(suites[suiteIndex]); + } + var tests = output.getElementsByClassName('test'); + for (var testIndex = 0; testIndex < tests.length; testIndex += 1) { + assertDisplayed(tests[testIndex]); + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + describe('after failures filter was clicked', function () { + it('should show passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (/\bpass\b/.test(tests[index].className)) { + assertDisplayed(tests[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide all non-passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (!/\bpass\b/.test(tests[index].className)) { + assertDisplayed(tests[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide suites with only non-passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (!suites[index].getElementsByClassName('test pass').length) { + assertDisplayed(suites[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not hide suites with passed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (suites[index].getElementsByClassName('test pass').length > 0) { + assertDisplayed(suites[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should unhide all tests and suites when clicked again', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex += 1) { + assertDisplayed(suites[suiteIndex]); + } + var tests = output.getElementsByClassName('test'); + for (var testIndex = 0; testIndex < tests.length; testIndex += 1) { + assertDisplayed(tests[testIndex]); + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + }); + }); + + describe('failures filter', function () { + it('should still show failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (/\bfail\b/.test(tests[index].className)) { + assertDisplayed(tests[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide all non-failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (!/\bfail\b/.test(tests[index].className)) { + assertDisplayed(tests[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide suites with only non-failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (!suites[index].getElementsByClassName('test fail').length) { + assertDisplayed(suites[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not hide suites with failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (suites[index].getElementsByClassName('test fail').length > 0) { + assertDisplayed(suites[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should unhide all tests and suites when clicked again', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex += 1) { + assertDisplayed(suites[suiteIndex]); + } + var tests = output.getElementsByClassName('test'); + for (var testIndex = 0; testIndex < tests.length; testIndex += 1) { + assertDisplayed(tests[testIndex]); + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + describe('after passes filter was clicked', function () { + it('should show failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (/\bfail\b/.test(tests[index].className)) { + assertDisplayed(tests[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide all non-failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + rootSuite.addTest(new Test('pending')); + rootSuite.addTest(new Test('skipped at runtime', function () { this.skip(); })); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('test', rootSuite.tests[2]); + rootSuite.tests[2].pending = true; + rootSuite.tests[2].duration = 0; + runner.emit('pending', rootSuite.tests[2]); + runner.emit('test end', rootSuite.tests[2]); + runner.emit('test', rootSuite.tests[3]); + rootSuite.tests[3].duration = 0; + rootSuite.tests[3].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.tests[3], assertion); + } + runner.emit('test end', rootSuite.tests[3]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var tests = output.getElementsByClassName('test'); + for (var index = 0; index < tests.length; index += 1) { + if (!/\bfail\b/.test(tests[index].className)) { + assertDisplayed(tests[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should hide suites with only non-failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (!suites[index].getElementsByClassName('test fail').length) { + assertDisplayed(suites[index], false); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not hide suites with failed tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var index = 0; index < suites.length; index += 1) { + if (suites[index].getElementsByClassName('test fail').length > 0) { + assertDisplayed(suites[index]); + } + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should unhide all tests and suites when clicked again', function () { + try { + var output = stubOutput(); + var runner = new Runner(4); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + Suite.create(rootSuite, 'passing'); + rootSuite.suites[0].addTest(new Test('passing', function () { /* content here */; })); // eslint-disable-line no-extra-semi + Suite.create(rootSuite, 'pending'); + rootSuite.suites[1].addTest(new Test('pending')); + rootSuite.suites[1].addTest(new Test('skipped at runtime', function () { this.skip(); })); + Suite.create(rootSuite, 'failing'); + var failingTest = function () { throw new Error('fail test'); }; + rootSuite.suites[2].addTest(new Test('failing', failingTest)); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', rootSuite.suites[0]); + runner.emit('test', rootSuite.suites[0].tests[0]); + rootSuite.suites[0].tests[0].duration = 0; + rootSuite.suites[0].tests[0].state = 'passed'; + runner.emit('pass', rootSuite.suites[0].tests[0]); + runner.emit('test end', rootSuite.suites[0].tests[0]); + runner.emit('suite end', rootSuite.suites[0]); + runner.emit('suite', rootSuite.suites[1]); + // no 'test' event is emitted for pending tests + runner.emit('pending', rootSuite.suites[1].tests[0]); + runner.emit('test end', rootSuite.suites[1].tests[0]); + runner.emit('test', rootSuite.suites[1].tests[1]); + rootSuite.suites[1].tests[1].pending = true; + rootSuite.suites[1].tests[1].duration = 0; + runner.emit('pending', rootSuite.suites[1].tests[1]); + runner.emit('test end', rootSuite.suites[1].tests[1]); + runner.emit('suite end', rootSuite.suites[1]); + runner.emit('suite', rootSuite.suites[2]); + runner.emit('test', rootSuite.suites[2].tests[0]); + rootSuite.suites[2].tests[0].duration = 0; + rootSuite.suites[2].tests[0].state = 'failed'; + try { + failingTest(); + throw new Error('Failing test did not throw assertion'); + } catch (assertion) { + runner.emit('fail', rootSuite.suites[2].tests[0], assertion); + } + runner.emit('test end', rootSuite.suites[2].tests[0]); + runner.emit('suite end', rootSuite.suites[2]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('passes').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + click(output.getElementsByClassName('failures').item(0).getElementsByTagName('a').item(0)); + var suites = output.getElementsByClassName('suite'); + for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex += 1) { + assertDisplayed(suites[suiteIndex]); + } + var tests = output.getElementsByClassName('test'); + for (var testIndex = 0; testIndex < tests.length; testIndex += 1) { + assertDisplayed(tests[testIndex]); + } + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + }); + }); + + describe('test source', function () { + it('should not be displayed initially', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var testCodeElement = output.getElementsByClassName('test').item(0).getElementsByTagName('pre').item(0); + assertDisplayed(testCodeElement, false); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should be displayed when the test is clicked upon', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var testCodeElement = output.getElementsByClassName('test').item(0).getElementsByTagName('pre').item(0); + click(testCodeElement.parentNode.getElementsByTagName('h2').item(0)); + assertDisplayed(testCodeElement); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should be hidden again when clicked twice', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var testCodeElement = output.getElementsByClassName('test').item(0).getElementsByTagName('pre').item(0); + click(testCodeElement.parentNode.getElementsByTagName('h2').item(0)); + click(testCodeElement.parentNode.getElementsByTagName('h2').item(0)); + assertDisplayed(testCodeElement, false); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should not affect other tests than the one clicked', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { })); + rootSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + runner.emit('test', rootSuite.tests[1]); + rootSuite.tests[1].duration = 0; + rootSuite.tests[1].state = 'passed'; + runner.emit('pass', rootSuite.tests[1]); + runner.emit('test end', rootSuite.tests[1]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + click(output.getElementsByClassName('test').item(0).getElementsByTagName('pre').item(0).parentNode.getElementsByTagName('h2').item(0)); + assertDisplayed(output.getElementsByClassName('test').item(1).getElementsByTagName('pre').item(0), false); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + }); + + describe('grep links', function () { + it('should be generated for suites', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + var mainSuite = Suite.create(rootSuite, 'suite'); + mainSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('suite', mainSuite); + runner.emit('test', mainSuite.tests[0]); + mainSuite.tests[0].duration = 0; + mainSuite.tests[0].state = 'passed'; + runner.emit('pass', mainSuite.tests[0]); + runner.emit('test end', mainSuite.tests[0]); + runner.emit('suite end', mainSuite); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var href = output.getElementsByClassName('suite').item(0).getElementsByTagName('a').item(0).href.replace(/.*[?]/, '?'); + expect(href).to.be('?grep=suite'); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + + it('should be generated for tests', function () { + try { + var output = stubOutput(); + var runner = new Runner(1); + new HTML(runner); // eslint-disable-line no-new + var rootSuite = new Suite('', new Context()); + rootSuite.addTest(new Test('passing', function () { })); + runner.emit('start'); + runner.emit('suite', rootSuite); + runner.emit('test', rootSuite.tests[0]); + rootSuite.tests[0].duration = 0; + rootSuite.tests[0].state = 'passed'; + runner.emit('pass', rootSuite.tests[0]); + runner.emit('test end', rootSuite.tests[0]); + runner.emit('suite end', rootSuite); + runner.emit('end'); + var href = output.getElementsByClassName('test').item(0).getElementsByTagName('a').item(0).href.replace(/.*[?]/, '?'); + expect(href).to.be('?grep=%20passing'); + restoreOutput(); + } catch (error) { + try { + restoreOutput(); + } catch (ignore) {} + throw error; + } + }); + }); +}); + +function Runner (total) { + var callbacks = {}; + return { + total: total, + on: function (event, callback) { + if (!callbacks[event]) { + callbacks[event] = []; + } + callbacks[event].push(callback); + }, + emit: function (event) { + var args = [].slice.call(arguments, 1); + (callbacks[event] || []).forEach(function (callback) { + callback.apply(null, args); + }); + } + }; +} + +function click (element) { + var event; + try { + event = new MouseEvent('click'); + } catch (ignore) { + event = document.createEvent('MouseEvent'); + event.initEvent('click', true, true); + } + if (element.dispatchEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event); + } +} + +function assertDisplayed (element, yes) { + if (yes === undefined) { yes = true; } + var expectDisplay; + if (window.getComputedStyle) { + expectDisplay = expect(window.getComputedStyle(element).getPropertyValue('display')); + } else { + expectDisplay = expect(element.currentStyle.display); + } + if (yes) { + expectDisplay.not.to.be('none'); + } else { + expectDisplay.to.be('none'); + } +} diff --git a/test/browser/self-test.html b/test/browser/self-test.html new file mode 100644 index 0000000000..337e979ddb --- /dev/null +++ b/test/browser/self-test.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + +
      + + + From c055923192482132048c8a0ca6391463ace78681 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Wed, 21 Jun 2017 00:35:58 -0400 Subject: [PATCH 09/12] Update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e4d4dcbea..4db7b3712c 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,14 @@ *Thank you* :kissing_heart: to all of you interested in helping. These are Mocha's immediate needs: 1. Increase test coverage on Node.js and browser - - Increase integration coverage for all reporters - - `html` reporter must be tested in browser + - ~~Increase integration coverage for all reporters~~ + - ~~`html` reporter must be tested in browser~~ - ~~Basic console reporters (*not* `nyan`, `landing`, etc.) must be tested in **both** browser and Node.js contexts; PhantomJS can consume all console reporters~~ - ~~Filesystem-based reporters must be tested in Node.js context~~ - - **UPDATE - May 24 2017**: Thanks to [community contributions](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md#mag-coverage), the coverage on most reporters has increased dramatically! The `html` reporter is still in [dire need of coverage](https://coveralls.io/builds/11674428/source?filename=lib%2Freporters%2Fhtml.js). + - **UPDATE - May 24 2017**: Thanks to [community contributions](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md#mag-coverage), the coverage on most reporters has increased dramatically! ~~The `html` reporter is still in dire need of coverage.~~ - Increase coverage against all interfaces (`exports` in particular). Ideally this becomes a "matrix" where we repeat sets of integration tests across all interfaces. - Refactor non-Node.js-specific tests to allow them to run in a browser context. Node.js-specific tests include those which *require* the CLI or filesystem. Most everything else is fair game. + - In general, anything with relatively low coverage percentage could use a little more attention; but also feel free to look for files with a low but non-zero number of uncovered branches or functions, which may not need much to test the other branch and/or the function in question. 2. Review current open pull requests - We need individuals familiar with Mocha's codebase. Got questions? Ask them in [our chat room](https://gitter.im/mochajs/mocha). - Pull requests **must** have supporting tests. The only exceptions are pure cosmetic or non-functional changes. From 09429cb351434ba8e60cd4ccb3b18e7a3c3ae6ec Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Wed, 21 Jun 2017 23:30:48 -0400 Subject: [PATCH 10/12] Fix multiple suites problem --- lib/reporters/html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reporters/html.js b/lib/reporters/html.js index e29aa36a5a..262ab467ad 100644 --- a/lib/reporters/html.js +++ b/lib/reporters/html.js @@ -317,7 +317,7 @@ function hideSuitesWithout (classname) { */ function unhide () { var els = document.getElementsByClassName('suite hidden'); - for (var i = 0; i < els.length; ++i) { + for (var i = els.length - 1; i >= 0; --i) { els[i].className = els[i].className.replace('suite hidden', 'suite'); } } From f50728abf6db11851fe29541343d1f8ce27a523d Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Wed, 21 Jun 2017 23:32:26 -0400 Subject: [PATCH 11/12] Fix multiple report instances problem (mainly affects Mocha tests rather than real usage) --- lib/reporters/html.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/reporters/html.js b/lib/reporters/html.js index 262ab467ad..b1e98e7a9d 100644 --- a/lib/reporters/html.js +++ b/lib/reporters/html.js @@ -86,22 +86,22 @@ function HTML (runner) { // pass toggle on(passesLink, 'click', function (evt) { evt.preventDefault(); - unhide(); + unhide(report); var name = (/pass/).test(report.className) ? '' : ' pass'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) { - hideSuitesWithout('test pass'); + hideSuitesWithout(report, 'test pass'); } }); // failure toggle on(failuresLink, 'click', function (evt) { evt.preventDefault(); - unhide(); + unhide(report); var name = (/fail/).test(report.className) ? '' : ' fail'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) { - hideSuitesWithout('test fail'); + hideSuitesWithout(report, 'test fail'); } }); @@ -302,8 +302,8 @@ function fragment (html) { * * @param {text} classname */ -function hideSuitesWithout (classname) { - var suites = document.getElementsByClassName('suite'); +function hideSuitesWithout (root, classname) { + var suites = root.getElementsByClassName('suite'); for (var i = 0; i < suites.length; i++) { var els = suites[i].getElementsByClassName(classname); if (!els.length) { @@ -315,8 +315,8 @@ function hideSuitesWithout (classname) { /** * Unhide .hidden suites. */ -function unhide () { - var els = document.getElementsByClassName('suite hidden'); +function unhide (root) { + var els = root.getElementsByClassName('suite hidden'); for (var i = els.length - 1; i >= 0; --i) { els[i].className = els[i].className.replace('suite hidden', 'suite'); } From 5fcc1b5568a3ebd8ba13741965cd7edc86f71351 Mon Sep 17 00:00:00 2001 From: ScottFreeCode Date: Thu, 22 Jun 2017 01:50:44 -0400 Subject: [PATCH 12/12] Fix various attribute and stacktrace oddities in various browsers --- test/browser-reporters/html.spec.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/browser-reporters/html.spec.js b/test/browser-reporters/html.spec.js index b8a4d2ce39..0a66be662a 100644 --- a/test/browser-reporters/html.spec.js +++ b/test/browser-reporters/html.spec.js @@ -145,15 +145,18 @@ describe('HTML reporter', function () { runner.emit('suite end', mainSuite); runner.emit('suite end', rootSuite); runner.emit('end'); - var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; var output = restoreOutput().outerHTML .replace(/\u2023/g, '‣') .replace(/(
    • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') .replace(/href="[^"?]*[?]/g, 'href="?') .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') - .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*
      • suite

        • passing0ms

          /* content here */;
        • pending

        • skipped at runtime

        • slow116ms

          setTimeout(done, 100);
        • less slow63ms

          setTimeout(done, 50);
        • failing

          AssertionError: example\nat STACKTRACE
          var AssertionError = function (message, actual, expected) {\n  this.message = message;\n  this.actual = actual;\n  this.expected = expected;\n  this.showDiff = true;\n};\nAssertionError.prototype = Object.create(Error.prototype);\nAssertionError.prototype.name = \'AssertionError\';\nAssertionError.prototype.constructor = AssertionError;\nthrow new AssertionError(\'example\', \'text with a typo\', \'text without a typo\');
        • timeout

          Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
          /* does not complete */;
      '); + .replace(/\s+style=""/g, '') + .replace(/(height="[^"]*")(\s+)(width="[^"]*")/g, '$3$2$1') + .replace(/
      AssertionError: example[^<]*<\/pre>/g, '
      AssertionError: example\nat STACKTRACE
      ') + .replace(/
      Error: Timeout of 200ms exceeded[.] For async tests and hooks, ensure "done[(][)]" is called; if returning a Promise, ensure it resolves[.][^<]*<\/pre>/g, '
      Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.\nat STACKTRACE
      ') + .replace(/\r\n/g, '\n'); + expect(output).to.equal('
      • suite

        • passing0ms

          /* content here */;
        • pending

        • skipped at runtime

        • slow116ms

          setTimeout(done, 100);
        • less slow63ms

          setTimeout(done, 50);
        • failing

          AssertionError: example\nat STACKTRACE
          var AssertionError = function (message, actual, expected) {\n  this.message = message;\n  this.actual = actual;\n  this.expected = expected;\n  this.showDiff = true;\n};\nAssertionError.prototype = Object.create(Error.prototype);\nAssertionError.prototype.name = \'AssertionError\';\nAssertionError.prototype.constructor = AssertionError;\nthrow new AssertionError(\'example\', \'text with a typo\', \'text without a typo\');
        • timeout

          Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.\nat STACKTRACE
          /* does not complete */;
      '); } catch (error) { try { restoreOutput(); @@ -214,14 +217,17 @@ describe('HTML reporter', function () { runner.emit('suite end', rootSuite.suites[1]); runner.emit('suite end', rootSuite); runner.emit('end'); - var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; var output = restoreOutput().outerHTML .replace(/\u2023/g, '‣') .replace(/(
    • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') .replace(/href="[^"?]*[?]/g, 'href="?') .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') - .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*AssertionError: example[^<]*<\/pre>/g, '
      AssertionError: example\nat STACKTRACE
      ') + .replace(/
      Error: Timeout of 200ms exceeded[.] For async tests and hooks, ensure "done[(][)]" is called; if returning a Promise, ensure it resolves[.][^<]*<\/pre>/g, '
      Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.\nat STACKTRACE
      ') + .replace(/\r\n/g, '\n'); expect(output).to.equal('
      '); } catch (error) { try { @@ -241,14 +247,17 @@ describe('HTML reporter', function () { runner.emit('suite', rootSuite); runner.emit('suite end', rootSuite); runner.emit('end'); - var stacktraceLine = 'at\\s.*(?:[(][^()]+:[0-9]+[)]|:[0-9]+)'; var output = restoreOutput().outerHTML .replace(/\u2023/g, '‣') .replace(/(
    • duration: )[0-9]+(?:[.][0-9]+)?(<\/em>s<\/li>)/, '$1$2') .replace(/href="[^"?]*[?]/g, 'href="?') .replace(/class="replay" (href="[^"]*")/g, '$1 class="replay"') .replace(/style="display:\s*none;?\s*"/g, 'style="display: none;"') - .replace(/\r\n/g, '\n').replace(new RegExp('^\\s*(?:' + stacktraceLine + '\n\\s*)*' + stacktraceLine + '\\s*AssertionError: example[^<]*<\/pre>/g, '
      AssertionError: example\nat STACKTRACE
      ') + .replace(/
      Error: Timeout of 200ms exceeded[.] For async tests and hooks, ensure "done[(][)]" is called; if returning a Promise, ensure it resolves[.][^<]*<\/pre>/g, '
      Error: Timeout of 200ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.\nat STACKTRACE
      ') + .replace(/\r\n/g, '\n'); expect(output).to.equal('
        '); } catch (error) { try {