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' diff --git a/Makefile b/Makefile index 2fa367e8ff..d6c13165d0 100644 --- a/Makefile +++ b/Makefile @@ -7,25 +7,42 @@ ifdef COVERAGE define test_node $(NYC) --report-dir coverage/reports/$(1) $(MOCHA) endef + instrument_browser := -t ./instrumentBrowserEntry else test_node := $(MOCHA) endif -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" - mkdir -p ${@D} +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: $(SRC) browser-entry.js + @printf "==> [Browser :: Build]\n" + $(call bundle_command) + +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" 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. 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 b8d73fd0e3..c519f8f9f1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -5,9 +5,16 @@ 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 nyc = require('./nycInstrumenter'); 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 +30,14 @@ 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' - ], + 'test/unit/*.spec.js', + 'test/browser-unit/*.spec.js', + 'test/browser-reporters/*.spec.js' + ]), preprocessors: { 'test/**/*.js': ['browserify'] }, @@ -128,14 +137,31 @@ 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 = { + 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: nyc }) ]; + console.error('Reporting coverage to ' + cfg.coverageReporter.dir); + } + config.set(cfg); }; diff --git a/lib/reporters/html.js b/lib/reporters/html.js index e29aa36a5a..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,9 +315,9 @@ function hideSuitesWithout (classname) { /** * Unhide .hidden suites. */ -function unhide () { - var els = document.getElementsByClassName('suite hidden'); - for (var i = 0; i < els.length; ++i) { +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'); } } 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)); } }; 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", 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/browser-reporters/html.spec.js b/test/browser-reporters/html.spec.js new file mode 100644 index 0000000000..0a66be662a --- /dev/null +++ b/test/browser-reporters/html.spec.js @@ -0,0 +1,1927 @@ +'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 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(/\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(); + } 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 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(/\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('
    '); + } 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 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(/\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('
      '); + } 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 @@ + + + + + + + + + + + + +
      + + + 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); }); });