From 52b9a5fb97bc3a6581dc6538aa0092276e71ea41 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Fri, 4 Jan 2019 15:55:18 -0800 Subject: [PATCH] refactor: use constants for event names instead of string literals - also a few other string literals got replaced, but not error messages nor codes - supporting refactors: - move the suite "GC" function into the `Suite` prototype - move stats collector init to `Mocha#run` due to circular reference with `Runner` - rename a couple fixture files lacking proper extension - rename variable in integration test helpers - add `utils.createMap()` and `utils.defineConstants()` - add `test/integration/fixtures` to `.eslintignore` so no fixture linting occurs whatsoever - consolidated use of `object.assign` polyfill into `utils` module - some docstring fixes/consistency - adds `EVENT_DELAY_END` emitted from `Runner` for reporter use - specifically did NOT refactor event names from Node.js incl. `error` and `uncaughtException` - add custom reporter tutorial to API documentation - automatically injects reporter example into tutorial via `markdown-magic` - add `docs.preprocess.api` script - add JSDoc configuration to support tutorials - sort JSDoc config object because fussy - fix broken custom assertion --- .eslintignore | 1 + .wallaby.js | 13 +- docs/api-tutorials/custom-reporter.md | 120 ++++++++ docs/api-tutorials/jsdoc.tutorials.json | 5 + jsdoc.conf.json | 35 +-- lib/browser/growl.js | 3 +- lib/growl.js | 3 +- lib/interfaces/bdd.js | 4 +- lib/interfaces/exports.js | 2 +- lib/interfaces/qunit.js | 4 +- lib/interfaces/tdd.js | 4 +- lib/mocha.js | 19 +- lib/reporters/base.js | 7 +- lib/reporters/doc.js | 13 +- lib/reporters/dot.js | 16 +- lib/reporters/html.js | 16 +- lib/reporters/json-stream.js | 13 +- lib/reporters/json.js | 16 +- lib/reporters/landing.js | 14 +- lib/reporters/list.js | 19 +- lib/reporters/markdown.js | 13 +- lib/reporters/min.js | 7 +- lib/reporters/nyan.js | 16 +- lib/reporters/progress.js | 10 +- lib/reporters/spec.js | 22 +- lib/reporters/tap.js | 19 +- lib/reporters/xunit.js | 21 +- lib/runnable.js | 33 ++- lib/runner.js | 260 ++++++++++-------- lib/stats-collector.js | 32 ++- lib/suite.js | 180 ++++++++++-- lib/test.js | 5 +- lib/utils.js | 35 ++- package-scripts.js | 21 +- scripts/markdown-magic.config.js | 69 ++++- test/assertions.js | 2 +- test/integration/events.spec.js | 19 ++ .../{simple-ui.js => simple-ui.fixture.js} | 17 +- .../fixtures/runner/events-bail.fixture.js | 31 ++- .../fixtures/runner/events-basic.fixture.js | 46 +++- .../fixtures/runner/events-delay.fixture.js | 71 +++++ .../fixtures/runner/events-retries.fixture.js | 39 ++- test/integration/fixtures/simple-reporter.js | 69 +++-- test/integration/helpers.js | 10 +- test/integration/regression.spec.js | 2 +- test/jsapi/index.js | 10 +- test/reporters/landing.spec.js | 10 +- test/reporters/xunit.spec.js | 7 +- test/unit/runnable.spec.js | 11 +- test/unit/runner.spec.js | 55 ++-- test/unit/suite.spec.js | 30 +- test/unit/throw.spec.js | 46 ++-- test/unit/utils.spec.js | 20 ++ 53 files changed, 1185 insertions(+), 380 deletions(-) create mode 100644 docs/api-tutorials/custom-reporter.md create mode 100644 docs/api-tutorials/jsdoc.tutorials.json rename test/integration/fixtures/regression/1794/{simple-ui.js => simple-ui.fixture.js} (60%) create mode 100644 test/integration/fixtures/runner/events-delay.fixture.js diff --git a/.eslintignore b/.eslintignore index 5b8791b988..d9a508ec53 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,5 @@ mocha.js docs/ out/ !lib/mocha.js +test/integration/fixtures !.*.js diff --git a/.wallaby.js b/.wallaby.js index fa6063fa55..a54f3576fc 100644 --- a/.wallaby.js +++ b/.wallaby.js @@ -38,8 +38,14 @@ module.exports = () => { const runningMocha = wallaby.testFramework; runningMocha.timeout(200); // to expose it/describe etc. on the mocha under test - const mochaUnderTest = new (require('./'))(); - mochaUnderTest.suite.emit('pre-require', global, '', mochaUnderTest); + const MochaUnderTest = require('./'); + const mochaUnderTest = new MochaUnderTest(); + mochaUnderTest.suite.emit( + MochaUnderTest.Suite.constants.EVENT_FILE_PRE_REQUIRE, + global, + '', + mochaUnderTest + ); // to make test/node-unit/color.spec.js pass, we need to run mocha in the project's folder context const childProcess = require('child_process'); const execFile = childProcess.execFile; @@ -53,6 +59,7 @@ module.exports = () => { return execFile.apply(this, arguments); }; require('./test/setup'); - } + }, + debug: true }; }; diff --git a/docs/api-tutorials/custom-reporter.md b/docs/api-tutorials/custom-reporter.md new file mode 100644 index 0000000000..3f0313aac8 --- /dev/null +++ b/docs/api-tutorials/custom-reporter.md @@ -0,0 +1,120 @@ +Mocha allows you to define and use custom reporters install via `npm`. + +For example, if `mocha-foo-reporter` was published to the npm registry, you could install it via `npm install mocha-foo-reporter --save-dev`, then use it via `mocha --reporter mocha-foo-reporter`. + +## Example Custom Reporter + +If you're looking to get started quickly, here's an example of a custom reporter: + + + +```js +// my-reporter.js +'use strict'; + +const Mocha = require('mocha'); +const { + EVENT_RUN_BEGIN, + EVENT_RUN_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_SUITE_BEGIN, + EVENT_SUITE_END +} = Mocha.Runner.constants; + +// this reporter outputs test results, indenting two spaces per suite +class MyReporter { + constructor(runner) { + this._indents = 0; + const stats = runner.stats; + + runner + .once(EVENT_RUN_BEGIN, () => { + console.log('start'); + }) + .on(EVENT_SUITE_BEGIN, () => { + this.increaseIndent(); + }) + .on(EVENT_SUITE_END, () => { + this.decreaseIndent(); + }) + .on(EVENT_TEST_PASS, test => { + // Test#fullTitle() returns the suite name(s) + // prepended to the test title + console.log(`${this.indent()}pass: ${test.fullTitle()}`); + }) + .on(EVENT_TEST_FAIL, (test, err) => { + console.log( + `${this.indent()}fail: ${test.fullTitle()} - error: ${err.message}` + ); + }) + .once(EVENT_RUN_END, () => { + console.log(`end: ${stats.passes}/${stats.passes + stats.failures} ok`); + }); + } + + indent() { + return Array(this._indents).join(' '); + } + + increaseIndent() { + this._indents++; + } + + decreaseIndent() { + this._indents--; + } +} + +module.exports = MyReporter; +``` + + + +To use this reporter, execute `mocha --reporter /path/to/my-reporter.js`. + +For further examples, the built-in reporter implementations are the [best place to look](https://github.com/mochajs/mocha/tree/master/lib/reporters). The [integration tests](https://github.com/mochajs/mocha/tree/master/test/reporters) may also be helpful. + +## The `Base` Reporter Class + +[Base] is an "abstract" class. It contains console-specific settings and utilities, commonly used by built-in reporters. Browsing the built-in reporter implementations, you may often see static properties of [Base] referenced. + +It's often useful--but not necessary--for a custom reporter to extend [Base]. + +## Statistics + +Mocha adds a `stats` property of type [StatsCollector](/api/module-lib_stats-collector.html) to the reporter's `Runner` instance (passed as first argument to constructor). + +## Events + +A reporter should listen for events emitted from the `runner` (a singleton instance of [Runner]). + +The event names are exported from the `constants` property of `Mocha.Runner`: + +| Constant | Event Name | Event Arguments | Description | +| -------------------- | ----------- | --------------- | ------------------------------------------------------------------------------------------- | +| `EVENT_RUN_BEGIN` | `start` | _(n/a)_ | Execution will begin. | +| `EVENT_RUN_END` | `end` | _(n/a)_ | All [Suite]s, [Test]s and [Hook]s have completed execution. | +| `EVENT_DELAY_BEGIN` | `waiting` | _(n/a)_ | Waiting for `global.run()` to be called; only emitted when [delay] option is `true`. | +| `EVENT_DELAY_END` | `ready` | _(n/a)_ | User called `global.run()` and the root suite is ready to execute. | +| `EVENT_SUITE_BEGIN` | `suite` | `Suite` | The [Hook]s and [Test]s within a [Suite] will be executed, including any nested [Suite]s. | +| `EVENT_SUITE_END` | `suite end` | `Suite` | The [Hook]s, [Test]s, and nested [Suite]s within a [Suite] have completed execution. | +| `EVENT_HOOK_BEGIN` | `hook` | `Hook` | A [Hook] will be executed. | +| `EVENT_HOOK_END` | `hook end` | `Hook` | A [Hook] has completed execution. | +| `EVENT_TEST_BEGIN` | `test` | `Test` | A [Test] will be executed. | +| `EVENT_TEST_END` | `test end` | `Test` | A [Test] has completed execution. | +| `EVENT_TEST_FAIL` | `fail` | `Test`, `Error` | A [Test] has failed or thrown an exception. | +| `EVENT_TEST_PASS` | `pass` | `Test` | A [Test] has passed. | +| `EVENT_TEST_PENDING` | `pending` | `Test` | A [Test] was skipped. | +| `EVENT_TEST_RETRY` | `retry` | `Test`, `Error` | A [Test] failed, but is about to be retried; only emitted if the `retry` option is nonzero. | + +**Please use these constants** instead of the event names in your own reporter! This will ensure compatibility with future versions of Mocha. + +> It's important to understand that all `Suite` callbacks will be run _before_ the [Runner] emits `EVENT_RUN_BEGIN`. Hooks and tests, however, won't run until _after_ the [Runner] emits `EVENT_RUN_BEGIN`. + +[runner]: /api/mocha.runner +[test]: /api/mocha.test +[hook]: /api/mocha.hook +[suite]: /api/mocha.suite +[base]: /api/mocha.reporters.base +[delay]: /#delayed-root-suite diff --git a/docs/api-tutorials/jsdoc.tutorials.json b/docs/api-tutorials/jsdoc.tutorials.json new file mode 100644 index 0000000000..4880bf552f --- /dev/null +++ b/docs/api-tutorials/jsdoc.tutorials.json @@ -0,0 +1,5 @@ +{ + "custom-reporter": { + "title": "Create a Custom Reporter" + } +} diff --git a/jsdoc.conf.json b/jsdoc.conf.json index f696d2c683..1407bf170e 100644 --- a/jsdoc.conf.json +++ b/jsdoc.conf.json @@ -1,32 +1,33 @@ { - "tags": { - "allowUnknownTags": true + "markdown": { + "hardwrap": true, + "parser": "gfm" }, - "source": { - "include": ["lib/", "./docs/API.md", "bin"] + "mocha-docdash": { + "sort": true, + "static": false }, - "plugins": ["plugins/markdown"], "opts": { - "encoding": "utf8", - "template": "node_modules/@mocha/docdash", "destination": "docs/_dist/api", + "encoding": "utf8", "recurse": true, + "template": "node_modules/@mocha/docdash", + "tutorials": "docs/api-tutorials", "verbose": true }, - "markdown": { - "parser": "gfm", - "hardwrap": true + "plugins": ["plugins/markdown"], + "source": { + "include": ["lib/", "./docs/API.md", "bin"] + }, + "tags": { + "allowUnknownTags": true }, "templates": { "cleverLinks": false, - "monospaceLinks": false, "default": { - "outputSourceFiles": true, - "includeDate": false - } + "includeDate": false, + "outputSourceFiles": true }, - "mocha-docdash": { - "static": false, - "sort": true + "monospaceLinks": false } } diff --git a/lib/browser/growl.js b/lib/browser/growl.js index 8472cebb2b..016798501a 100644 --- a/lib/browser/growl.js +++ b/lib/browser/growl.js @@ -10,6 +10,7 @@ */ var Date = global.Date; var setTimeout = global.setTimeout; +var EVENT_RUN_END = require('../runner').constants.EVENT_RUN_END; /** * Checks if browser notification support exists. @@ -53,7 +54,7 @@ exports.notify = function(runner) { .catch(notPermitted); }; - runner.once('end', sendNotification); + runner.once(EVENT_RUN_END, sendNotification); }; /** diff --git a/lib/growl.js b/lib/growl.js index d9ee37cf50..53164563bb 100644 --- a/lib/growl.js +++ b/lib/growl.js @@ -8,6 +8,7 @@ const os = require('os'); const path = require('path'); const {sync: which} = require('which'); +const {EVENT_RUN_END} = require('./runner').constants; /** * @summary @@ -41,7 +42,7 @@ exports.isCapable = () => { * @param {Runner} runner - Runner instance. */ exports.notify = runner => { - runner.once('end', () => { + runner.once(EVENT_RUN_END, () => { display(runner); }); }; diff --git a/lib/interfaces/bdd.js b/lib/interfaces/bdd.js index 87408f8a8d..ef8d281e17 100644 --- a/lib/interfaces/bdd.js +++ b/lib/interfaces/bdd.js @@ -1,6 +1,8 @@ 'use strict'; var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; /** * BDD-style interface: @@ -22,7 +24,7 @@ var Test = require('../test'); module.exports = function bddInterface(suite) { var suites = [suite]; - suite.on('pre-require', function(context, file, mocha) { + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { var common = require('./common')(suites, context, mocha); context.before = common.before; diff --git a/lib/interfaces/exports.js b/lib/interfaces/exports.js index da5e5d84d8..5b4c7bd6f4 100644 --- a/lib/interfaces/exports.js +++ b/lib/interfaces/exports.js @@ -22,7 +22,7 @@ var Test = require('../test'); module.exports = function(suite) { var suites = [suite]; - suite.on('require', visit); + suite.on(Suite.constants.EVENT_FILE_REQUIRE, visit); function visit(obj, file) { var suite; diff --git a/lib/interfaces/qunit.js b/lib/interfaces/qunit.js index becc110859..a2dc332030 100644 --- a/lib/interfaces/qunit.js +++ b/lib/interfaces/qunit.js @@ -1,6 +1,8 @@ 'use strict'; var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; /** * QUnit-style interface: @@ -30,7 +32,7 @@ var Test = require('../test'); module.exports = function qUnitInterface(suite) { var suites = [suite]; - suite.on('pre-require', function(context, file, mocha) { + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { var common = require('./common')(suites, context, mocha); context.before = common.before; diff --git a/lib/interfaces/tdd.js b/lib/interfaces/tdd.js index c36c7afc44..a44c7fe27d 100644 --- a/lib/interfaces/tdd.js +++ b/lib/interfaces/tdd.js @@ -1,6 +1,8 @@ 'use strict'; var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; /** * TDD-style interface: @@ -30,7 +32,7 @@ var Test = require('../test'); module.exports = function(suite) { var suites = [suite]; - suite.on('pre-require', function(context, file, mocha) { + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { var common = require('./common')(suites, context, mocha); context.setup = common.beforeEach; diff --git a/lib/mocha.js b/lib/mocha.js index 687f5c77a3..9886ded56d 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -12,10 +12,14 @@ var builtinReporters = require('./reporters'); var growl = require('./growl'); var utils = require('./utils'); var mocharc = require('./mocharc.json'); -var assign = require('object.assign').getPolyfill(); var errors = require('./errors'); +var Suite = require('./suite'); +var createStatsCollector = require('./stats-collector'); var createInvalidReporterError = errors.createInvalidReporterError; var createInvalidInterfaceError = errors.createInvalidInterfaceError; +var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE; +var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE; +var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE; exports = module.exports = Mocha; @@ -51,7 +55,7 @@ exports.Context = require('./context'); * @memberof Mocha */ exports.Runner = require('./runner'); -exports.Suite = require('./suite'); +exports.Suite = Suite; exports.Hook = require('./hook'); exports.Test = require('./test'); @@ -88,7 +92,7 @@ exports.Test = require('./test'); * @param {boolean} [options.useInlineDiffs] - Use inline diffs? */ function Mocha(options) { - options = assign({}, mocharc, options || {}); + options = utils.assign({}, mocharc, options || {}); this.files = []; this.options = options; // root suite @@ -276,7 +280,7 @@ Mocha.prototype.ui = function(name) { } this._ui = this._ui(this.suite); - this.suite.on('pre-require', function(context) { + this.suite.on(EVENT_FILE_PRE_REQUIRE, function(context) { exports.afterEach = context.afterEach || context.teardown; exports.after = context.after || context.suiteTeardown; exports.beforeEach = context.beforeEach || context.setup; @@ -313,9 +317,9 @@ Mocha.prototype.loadFiles = function(fn) { var suite = this.suite; this.files.forEach(function(file) { file = path.resolve(file); - suite.emit('pre-require', global, file, self); - suite.emit('require', require(file), file, self); - suite.emit('post-require', global, file, self); + suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); + suite.emit(EVENT_FILE_REQUIRE, require(file), file, self); + suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); }); fn && fn(); }; @@ -759,6 +763,7 @@ Mocha.prototype.run = function(fn) { var options = this.options; options.files = this.files; var runner = new exports.Runner(suite, options.delay); + createStatsCollector(runner); var reporter = new this._reporter(runner, options); runner.ignoreLeaks = options.ignoreLeaks !== false; runner.fullStackTrace = options.fullStackTrace; diff --git a/lib/reporters/base.js b/lib/reporters/base.js index 16d4ca586e..343b94fb9e 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -11,6 +11,9 @@ var diff = require('diff'); var milliseconds = require('ms'); var utils = require('../utils'); var supportsColor = process.browser ? null : require('supports-color'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; /** * Expose `Base`. @@ -274,7 +277,7 @@ function Base(runner) { this.stats = runner.stats; // assigned so Reporters keep a closer reference this.runner = runner; - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { if (test.duration > test.slow()) { test.speed = 'slow'; } else if (test.duration > test.slow() / 2) { @@ -284,7 +287,7 @@ function Base(runner) { } }); - runner.on('fail', function(test, err) { + runner.on(EVENT_TEST_FAIL, function(test, err) { if (showDiff(err)) { stringifyDiffObjs(err); } diff --git a/lib/reporters/doc.js b/lib/reporters/doc.js index ce183d6766..67246a9931 100644 --- a/lib/reporters/doc.js +++ b/lib/reporters/doc.js @@ -8,6 +8,11 @@ var Base = require('./base'); var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; /** * Expose `Doc`. @@ -33,7 +38,7 @@ function Doc(runner) { return Array(indents).join(' '); } - runner.on('suite', function(suite) { + runner.on(EVENT_SUITE_BEGIN, function(suite) { if (suite.root) { return; } @@ -44,7 +49,7 @@ function Doc(runner) { console.log('%s
', indent()); }); - runner.on('suite end', function(suite) { + runner.on(EVENT_SUITE_END, function(suite) { if (suite.root) { return; } @@ -54,13 +59,13 @@ function Doc(runner) { --indents; }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { console.log('%s
%s
', indent(), utils.escape(test.title)); var code = utils.escape(utils.clean(test.body)); console.log('%s
%s
', indent(), code); }); - runner.on('fail', function(test, err) { + runner.on(EVENT_TEST_FAIL, function(test, err) { console.log( '%s
%s
', indent(), diff --git a/lib/reporters/dot.js b/lib/reporters/dot.js index 8479c31e6d..26d4ff953d 100644 --- a/lib/reporters/dot.js +++ b/lib/reporters/dot.js @@ -8,6 +8,12 @@ var Base = require('./base'); var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; /** * Expose `Dot`. @@ -31,18 +37,18 @@ function Dot(runner) { var width = (Base.window.width * 0.75) | 0; var n = -1; - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { process.stdout.write('\n'); }); - runner.on('pending', function() { + runner.on(EVENT_TEST_PENDING, function() { if (++n % width === 0) { process.stdout.write('\n '); } process.stdout.write(Base.color('pending', Base.symbols.comma)); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { if (++n % width === 0) { process.stdout.write('\n '); } @@ -53,14 +59,14 @@ function Dot(runner) { } }); - runner.on('fail', function() { + runner.on(EVENT_TEST_FAIL, function() { if (++n % width === 0) { process.stdout.write('\n '); } process.stdout.write(Base.color('fail', Base.symbols.bang)); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { console.log(); self.epilogue(); }); diff --git a/lib/reporters/html.js b/lib/reporters/html.js index 71bc21e113..3c854eb529 100644 --- a/lib/reporters/html.js +++ b/lib/reporters/html.js @@ -12,6 +12,12 @@ var Base = require('./base'); var utils = require('../utils'); var Progress = require('../browser/progress'); var escapeRe = require('escape-string-regexp'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; var escape = utils.escape; /** @@ -112,7 +118,7 @@ function HTML(runner) { progress.size(40); } - runner.on('suite', function(suite) { + runner.on(EVENT_SUITE_BEGIN, function(suite) { if (suite.root) { return; } @@ -131,7 +137,7 @@ function HTML(runner) { el.appendChild(stack[0]); }); - runner.on('suite end', function(suite) { + runner.on(EVENT_SUITE_END, function(suite) { if (suite.root) { updateStats(); return; @@ -139,7 +145,7 @@ function HTML(runner) { stack.shift(); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { var url = self.testURL(test); var markup = '
  • %e%ems ' + @@ -152,7 +158,7 @@ function HTML(runner) { updateStats(); }); - runner.on('fail', function(test) { + runner.on(EVENT_TEST_FAIL, function(test) { var el = fragment( '
  • %e ' + playIcon + @@ -208,7 +214,7 @@ function HTML(runner) { updateStats(); }); - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { var el = fragment( '
  • %e

  • ', test.title diff --git a/lib/reporters/json-stream.js b/lib/reporters/json-stream.js index c05050227b..5ca0a42051 100644 --- a/lib/reporters/json-stream.js +++ b/lib/reporters/json-stream.js @@ -7,6 +7,11 @@ */ var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; /** * Expose `JSONStream`. @@ -29,22 +34,22 @@ function JSONStream(runner) { var self = this; var total = runner.total; - runner.once('start', function() { + runner.once(EVENT_RUN_BEGIN, function() { writeEvent(['start', {total: total}]); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { writeEvent(['pass', clean(test)]); }); - runner.on('fail', function(test, err) { + runner.on(EVENT_TEST_FAIL, function(test, err) { test = clean(test); test.err = err.message; test.stack = err.stack || null; writeEvent(['fail', test]); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { writeEvent(['end', self.stats]); }); } diff --git a/lib/reporters/json.js b/lib/reporters/json.js index c5106d3a65..8678b3a18d 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -7,6 +7,12 @@ */ var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; /** * Expose `JSON`. @@ -32,23 +38,23 @@ function JSONReporter(runner) { var failures = []; var passes = []; - runner.on('test end', function(test) { + runner.on(EVENT_TEST_END, function(test) { tests.push(test); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { passes.push(test); }); - runner.on('fail', function(test) { + runner.on(EVENT_TEST_FAIL, function(test) { failures.push(test); }); - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { pending.push(test); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { var obj = { stats: self.stats, tests: tests.map(clean), diff --git a/lib/reporters/landing.js b/lib/reporters/landing.js index a33267adeb..6c16cda7e5 100644 --- a/lib/reporters/landing.js +++ b/lib/reporters/landing.js @@ -8,6 +8,12 @@ var Base = require('./base'); var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; + var cursor = Base.cursor; var color = Base.color; @@ -60,17 +66,17 @@ function Landing(runner) { return ' ' + color('runway', buf); } - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { stream.write('\n\n\n '); cursor.hide(); }); - runner.on('test end', function(test) { + runner.on(EVENT_TEST_END, function(test) { // check if the plane crashed var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed; // show the crash - if (test.state === 'failed') { + if (test.state === STATE_FAILED) { plane = color('plane crash', '✈'); crashed = col; } @@ -86,7 +92,7 @@ function Landing(runner) { stream.write('\u001b[0m'); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { cursor.show(); console.log(); self.epilogue(); diff --git a/lib/reporters/list.js b/lib/reporters/list.js index 4e324b61c7..db4bd860e7 100644 --- a/lib/reporters/list.js +++ b/lib/reporters/list.js @@ -8,6 +8,13 @@ var Base = require('./base'); var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; var color = Base.color; var cursor = Base.cursor; @@ -32,20 +39,20 @@ function List(runner) { var self = this; var n = 0; - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { console.log(); }); - runner.on('test', function(test) { + runner.on(EVENT_TEST_BEGIN, function(test) { process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); }); - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { var fmt = color('checkmark', ' -') + color('pending', ' %s'); console.log(fmt, test.fullTitle()); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { var fmt = color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s: ') + @@ -54,12 +61,12 @@ function List(runner) { console.log(fmt, test.fullTitle(), test.duration); }); - runner.on('fail', function(test) { + runner.on(EVENT_TEST_FAIL, function(test) { cursor.CR(); console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); }); - runner.once('end', self.epilogue.bind(self)); + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); } /** diff --git a/lib/reporters/markdown.js b/lib/reporters/markdown.js index 70926ac617..f202c8f951 100644 --- a/lib/reporters/markdown.js +++ b/lib/reporters/markdown.js @@ -8,6 +8,11 @@ var Base = require('./base'); var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; /** * Constants @@ -77,18 +82,18 @@ function Markdown(runner) { generateTOC(runner.suite); - runner.on('suite', function(suite) { + runner.on(EVENT_SUITE_BEGIN, function(suite) { ++level; var slug = utils.slug(suite.fullTitle()); buf += '
    ' + '\n'; buf += title(suite.title) + '\n'; }); - runner.on('suite end', function() { + runner.on(EVENT_SUITE_END, function() { --level; }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { var code = utils.clean(test.body); buf += test.title + '.\n'; buf += '\n```js\n'; @@ -96,7 +101,7 @@ function Markdown(runner) { buf += '```\n\n'; }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { process.stdout.write('# TOC\n'); process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); diff --git a/lib/reporters/min.js b/lib/reporters/min.js index 3277ed3442..930c97edbc 100644 --- a/lib/reporters/min.js +++ b/lib/reporters/min.js @@ -8,6 +8,9 @@ var Base = require('./base'); var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; /** * Expose `Min`. @@ -27,14 +30,14 @@ exports = module.exports = Min; function Min(runner) { Base.call(this, runner); - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { // clear screen process.stdout.write('\u001b[2J'); // set cursor position process.stdout.write('\u001b[1;3H'); }); - runner.once('end', this.epilogue.bind(this)); + runner.once(EVENT_RUN_END, this.epilogue.bind(this)); } /** diff --git a/lib/reporters/nyan.js b/lib/reporters/nyan.js index c91cd5ddf3..9c0a44a653 100644 --- a/lib/reporters/nyan.js +++ b/lib/reporters/nyan.js @@ -7,7 +7,13 @@ */ var Base = require('./base'); +var constants = require('../runner').constants; var inherits = require('../utils').inherits; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; /** * Expose `Dot`. @@ -40,24 +46,24 @@ function NyanCat(runner) { this.trajectories = [[], [], [], []]; this.trajectoryWidthMax = width - nyanCatWidth; - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { Base.cursor.hide(); self.draw(); }); - runner.on('pending', function() { + runner.on(EVENT_TEST_PENDING, function() { self.draw(); }); - runner.on('pass', function() { + runner.on(EVENT_TEST_PASS, function() { self.draw(); }); - runner.on('fail', function() { + runner.on(EVENT_TEST_FAIL, function() { self.draw(); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { Base.cursor.show(); for (var i = 0; i < self.numberOfLines; i++) { write('\n'); diff --git a/lib/reporters/progress.js b/lib/reporters/progress.js index 5b174f0d5b..470feb982c 100644 --- a/lib/reporters/progress.js +++ b/lib/reporters/progress.js @@ -7,6 +7,10 @@ */ var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; var inherits = require('../utils').inherits; var color = Base.color; var cursor = Base.cursor; @@ -53,13 +57,13 @@ function Progress(runner, options) { options.verbose = reporterOptions.verbose || false; // tests started - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { console.log(); cursor.hide(); }); // tests complete - runner.on('test end', function() { + runner.on(EVENT_TEST_END, function() { complete++; var percent = complete / total; @@ -85,7 +89,7 @@ function Progress(runner, options) { // tests are complete, output some stats // and the failures if any - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { cursor.show(); console.log(); self.epilogue(); diff --git a/lib/reporters/spec.js b/lib/reporters/spec.js index 82a438fba7..49522096ae 100644 --- a/lib/reporters/spec.js +++ b/lib/reporters/spec.js @@ -7,6 +7,14 @@ */ var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; var inherits = require('../utils').inherits; var color = Base.color; @@ -36,28 +44,28 @@ function Spec(runner) { return Array(indents).join(' '); } - runner.on('start', function() { + runner.on(EVENT_RUN_BEGIN, function() { console.log(); }); - runner.on('suite', function(suite) { + runner.on(EVENT_SUITE_BEGIN, function(suite) { ++indents; console.log(color('suite', '%s%s'), indent(), suite.title); }); - runner.on('suite end', function() { + runner.on(EVENT_SUITE_END, function() { --indents; if (indents === 1) { console.log(); } }); - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { var fmt = indent() + color('pending', ' - %s'); console.log(fmt, test.title); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { var fmt; if (test.speed === 'fast') { fmt = @@ -75,11 +83,11 @@ function Spec(runner) { } }); - runner.on('fail', function(test) { + runner.on(EVENT_TEST_FAIL, function(test) { console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); }); - runner.once('end', self.epilogue.bind(self)); + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); } /** diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index 4c7c15a864..f71269bebb 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -8,6 +8,13 @@ var util = require('util'); var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_END = constants.EVENT_TEST_END; var inherits = require('../utils').inherits; var sprintf = util.format; @@ -42,29 +49,29 @@ function TAP(runner, options) { this._producer = createProducer(tapVersion); - runner.once('start', function() { + runner.once(EVENT_RUN_BEGIN, function() { var ntests = runner.grepTotal(runner.suite); self._producer.writeVersion(); self._producer.writePlan(ntests); }); - runner.on('test end', function() { + runner.on(EVENT_TEST_END, function() { ++n; }); - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { self._producer.writePending(n, test); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { self._producer.writePass(n, test); }); - runner.on('fail', function(test, err) { + runner.on(EVENT_TEST_FAIL, function(test, err) { self._producer.writeFail(n, test, err); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { self._producer.writeEpilogue(runner.stats); }); } diff --git a/lib/reporters/xunit.js b/lib/reporters/xunit.js index 643367aa9d..faccdaf7e9 100644 --- a/lib/reporters/xunit.js +++ b/lib/reporters/xunit.js @@ -8,13 +8,20 @@ var Base = require('./base'); var utils = require('../utils'); -var inherits = utils.inherits; var fs = require('fs'); -var escape = utils.escape; var mkdirp = require('mkdirp'); var path = require('path'); var errors = require('../errors'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; +var inherits = utils.inherits; +var escape = utils.escape; var createNotSupportedError = errors.createNotSupportedError; + /** * Save timer references to avoid Sinon interfering (see GH-237). */ @@ -65,19 +72,19 @@ function XUnit(runner, options) { // fall back to the default suite name suiteName = suiteName || DEFAULT_SUITE_NAME; - runner.on('pending', function(test) { + runner.on(EVENT_TEST_PENDING, function(test) { tests.push(test); }); - runner.on('pass', function(test) { + runner.on(EVENT_TEST_PASS, function(test) { tests.push(test); }); - runner.on('fail', function(test) { + runner.on(EVENT_TEST_FAIL, function(test) { tests.push(test); }); - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { self.write( tag( 'testsuite', @@ -152,7 +159,7 @@ XUnit.prototype.test = function(test) { time: test.duration / 1000 || 0 }; - if (test.state === 'failed') { + if (test.state === STATE_FAILED) { var err = test.err; var diff = Base.hideDiff || !err.actual || !err.expected diff --git a/lib/runnable.js b/lib/runnable.js index be06cdf318..2509d19996 100644 --- a/lib/runnable.js +++ b/lib/runnable.js @@ -1,4 +1,5 @@ 'use strict'; + var EventEmitter = require('events').EventEmitter; var Pending = require('./pending'); var debug = require('debug')('mocha:runnable'); @@ -16,10 +17,11 @@ var toString = Object.prototype.toString; module.exports = Runnable; /** - * Initialize a new `Runnable` with the given `title` and callback `fn`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) + * Initialize a new `Runnable` with the given `title` and callback `fn`. * * @class - * @extends EventEmitter + * @extends external:EventEmitter + * @public * @param {String} title * @param {Function} fn */ @@ -149,7 +151,7 @@ Runnable.prototype.isPending = function() { * @private */ Runnable.prototype.isFailed = function() { - return !this.isPending() && this.state === 'failed'; + return !this.isPending() && this.state === constants.STATE_FAILED; }; /** @@ -158,7 +160,7 @@ Runnable.prototype.isFailed = function() { * @private */ Runnable.prototype.isPassed = function() { - return !this.isPending() && this.state === 'passed'; + return !this.isPending() && this.state === constants.STATE_PASSED; }; /** @@ -450,3 +452,26 @@ Runnable.prototype._timeoutError = function(ms) { } return new Error(msg); }; + +var constants = utils.defineConstants( + /** + * {@link Runnable}-related constants. + * @public + * @memberof Runnable + * @readonly + * @static + * @alias constants + * @enum {string} + */ { + /** + * Value of `state` prop when a `Runnable` has failed + */ + STATE_FAILED: 'failed', + /** + * Value of `state` prop when a `Runnable` has passed + */ + STATE_PASSED: 'passed' + } +); + +Runnable.constants = constants; diff --git a/lib/runner.js b/lib/runner.js index 7c4435219e..6952efa92c 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,18 +1,19 @@ 'use strict'; -/** - * @module Runner - */ -/** - * Module dependencies. - */ var EventEmitter = require('events').EventEmitter; var Pending = require('./pending'); var utils = require('./utils'); var inherits = utils.inherits; var debug = require('debug')('mocha:runner'); var Runnable = require('./runnable'); -var createStatsCollector = require('./stats-collector'); +var Suite = require('./suite'); +var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH; +var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH; +var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL; +var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL; +var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN; +var STATE_FAILED = Runnable.constants.STATE_FAILED; +var STATE_PASSED = Runnable.constants.STATE_PASSED; var stackFilter = utils.stackTraceFilter(); var stringify = utils.stringify; var type = utils.type; @@ -20,8 +21,8 @@ var undefinedError = utils.undefinedError; /** * Non-enumerable globals. + * @readonly */ - var globals = [ 'setTimeout', 'clearTimeout', @@ -33,33 +34,85 @@ var globals = [ 'clearImmediate' ]; -/** - * Expose `Runner`. - */ +var constants = utils.defineConstants( + /** + * {@link Runner}-related constants. + * @public + * @memberof Runner + * @readonly + * @alias constants + * @static + * @enum {string} + */ + { + /** + * Emitted when {@link Hook} execution begins + */ + EVENT_HOOK_BEGIN: 'hook', + /** + * Emitted when {@link Hook} execution ends + */ + EVENT_HOOK_END: 'hook end', + /** + * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution) + */ + EVENT_RUN_BEGIN: 'start', + /** + * Emitted when Root {@link Suite} execution has been delayed via `delay` option + */ + EVENT_DELAY_BEGIN: 'waiting', + /** + * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()` + */ + EVENT_DELAY_END: 'ready', + /** + * Emitted when Root {@link Suite} execution ends + */ + EVENT_RUN_END: 'end', + /** + * Emitted when {@link Suite} execution begins + */ + EVENT_SUITE_BEGIN: 'suite', + /** + * Emitted when {@link Suite} execution ends + */ + EVENT_SUITE_END: 'suite end', + /** + * Emitted when {@link Test} execution begins + */ + EVENT_TEST_BEGIN: 'test', + /** + * Emitted when {@link Test} execution ends + */ + EVENT_TEST_END: 'test end', + /** + * Emitted when {@link Test} execution fails + */ + EVENT_TEST_FAIL: 'fail', + /** + * Emitted when {@link Test} execution succeeds + */ + EVENT_TEST_PASS: 'pass', + /** + * Emitted when {@link Test} becomes pending + */ + EVENT_TEST_PENDING: 'pending', + /** + * Emitted when {@link Test} execution has failed, but will retry + */ + EVENT_TEST_RETRY: 'retry' + } +); module.exports = Runner; /** - * Initialize a `Runner` for the given `suite`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) - * - * Events: + * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}. * - * - `start` execution started - * - `end` execution complete - * - `suite` (suite) test suite execution started - * - `suite end` (suite) all tests (and sub-suites) have finished - * - `test` (test) test execution started - * - `test end` (test) test completed - * - `hook` (hook) hook execution started - * - `hook end` (hook) hook complete - * - `pass` (test) test passed - * - `fail` (test, err) test failed - * - `pending` (test) test pending - * - * @memberof Mocha + * @extends external:EventEmitter * @public * @class - * @param {Suite} [suite] Root suite + * @param {Suite} suite Root suite * @param {boolean} [delay] Whether or not to delay execution of root suite * until ready. */ @@ -72,16 +125,15 @@ function Runner(suite, delay) { this.started = false; this.total = suite.total(); this.failures = 0; - this.on('test end', function(test) { + this.on(constants.EVENT_TEST_END, function(test) { self.checkGlobals(test); }); - this.on('hook end', function(hook) { + this.on(constants.EVENT_HOOK_END, function(hook) { self.checkGlobals(hook); }); this._defaultGrep = /.*/; this.grep(this._defaultGrep); this.globals(this.globalProps().concat(extraGlobals())); - createStatsCollector(this); } /** @@ -102,7 +154,7 @@ inherits(Runner, EventEmitter); * with number of tests matched. * * @public - * @memberof Mocha.Runner + * @memberof Runner * @param {RegExp} re * @param {boolean} invert * @return {Runner} Runner instance. @@ -119,7 +171,7 @@ Runner.prototype.grep = function(re, invert) { * Returns the number of tests matching the grep search for the * given suite. * - * @memberof Mocha.Runner + * @memberof Runner * @public * @param {Suite} suite * @return {number} @@ -165,7 +217,7 @@ Runner.prototype.globalProps = function() { * Allow the given `arr` of globals. * * @public - * @memberof Mocha.Runner + * @memberof Runner * @param {Array} arr * @return {Runner} Runner instance. */ @@ -227,7 +279,7 @@ Runner.prototype.fail = function(test, err) { } ++this.failures; - test.state = 'failed'; + test.state = STATE_FAILED; if (!isError(err)) { err = thrown2Error(err); @@ -240,7 +292,7 @@ Runner.prototype.fail = function(test, err) { // some environments do not take kindly to monkeying with the stack } - this.emit('fail', test, err); + this.emit(constants.EVENT_TEST_FAIL, test, err); }; /** @@ -292,7 +344,7 @@ Runner.prototype.failHook = function(hook, err) { Runner.prototype.hook = function(name, fn) { var suite = this.suite; - var hooks = suite['_' + name]; + var hooks = suite.getHooks(name); var self = this; function next(i) { @@ -310,7 +362,7 @@ Runner.prototype.hook = function(name, fn) { hook.ctx.currentTest = self.test; } - self.emit('hook', hook); + self.emit(constants.EVENT_HOOK_BEGIN, hook); if (!hook.listeners('error').length) { hook.on('error', function(err) { @@ -325,7 +377,7 @@ Runner.prototype.hook = function(name, fn) { } if (err) { if (err instanceof Pending) { - if (name === 'beforeEach' || name === 'afterEach') { + if (name === HOOK_TYPE_BEFORE_EACH || name === HOOK_TYPE_AFTER_EACH) { self.test.pending = true; } else { suite.tests.forEach(function(test) { @@ -344,7 +396,7 @@ Runner.prototype.hook = function(name, fn) { return fn(err); } } - self.emit('hook end', hook); + self.emit(constants.EVENT_HOOK_END, hook); delete hook.ctx.currentTest; next(++i); }); @@ -487,7 +539,7 @@ Runner.prototype.runTests = function(suite, fn) { if (self.suite) { // call hookUp afterEach - self.hookUp('afterEach', function(err2, errSuite2) { + self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) { self.suite = orig; // some hooks may fail even now if (err2) { @@ -553,24 +605,24 @@ Runner.prototype.runTests = function(suite, fn) { self.fail(test, new Error('Pending test forbidden')); delete test.isPending; } else { - self.emit('pending', test); + self.emit(constants.EVENT_TEST_PENDING, test); } - self.emit('test end', test); + self.emit(constants.EVENT_TEST_END, test); return next(); } // execute test and hook(s) - self.emit('test', (self.test = test)); - self.hookDown('beforeEach', function(err, errSuite) { + self.emit(constants.EVENT_TEST_BEGIN, (self.test = test)); + self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) { if (test.isPending()) { if (self.forbidPending) { test.isPending = alwaysFalse; self.fail(test, new Error('Pending test forbidden')); delete test.isPending; } else { - self.emit('pending', test); + self.emit(constants.EVENT_TEST_PENDING, test); } - self.emit('test end', test); + self.emit(constants.EVENT_TEST_END, test); return next(); } if (err) { @@ -585,33 +637,33 @@ Runner.prototype.runTests = function(suite, fn) { self.fail(test, new Error('Pending test forbidden')); } else if (err instanceof Pending) { test.pending = true; - self.emit('pending', test); + self.emit(constants.EVENT_TEST_PENDING, test); } else if (retry < test.retries()) { var clonedTest = test.clone(); clonedTest.currentRetry(retry + 1); tests.unshift(clonedTest); - self.emit('retry', test, err); + self.emit(constants.EVENT_TEST_RETRY, test, err); // Early return + hook trigger so that it doesn't // increment the count wrong - return self.hookUp('afterEach', next); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); } else { self.fail(test, err); } - self.emit('test end', test); + self.emit(constants.EVENT_TEST_END, test); if (err instanceof Pending) { return next(); } - return self.hookUp('afterEach', next); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); } - test.state = 'passed'; - self.emit('pass', test); - self.emit('test end', test); - self.hookUp('afterEach', next); + test.state = STATE_PASSED; + self.emit(constants.EVENT_TEST_PASS, test); + self.emit(constants.EVENT_TEST_END, test); + self.hookUp(HOOK_TYPE_AFTER_EACH, next); }); }); } @@ -644,7 +696,7 @@ Runner.prototype.runSuite = function(suite, fn) { return fn(); } - this.emit('suite', (this.suite = suite)); + this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite)); function next(errSuite) { if (errSuite) { @@ -694,8 +746,8 @@ Runner.prototype.runSuite = function(suite, fn) { // remove reference to test delete self.test; - self.hook('afterAll', function() { - self.emit('suite end', suite); + self.hook(HOOK_TYPE_AFTER_ALL, function() { + self.emit(constants.EVENT_SUITE_END, suite); fn(errSuite); }); } @@ -703,7 +755,7 @@ Runner.prototype.runSuite = function(suite, fn) { this.nextSuite = next; - this.hook('beforeAll', function(err) { + this.hook(HOOK_TYPE_BEFORE_ALL, function(err) { if (err) { return done(); } @@ -748,9 +800,9 @@ Runner.prototype.uncaught = function(err) { this.fail(runnable, err); } else { // Can't recover from this failure - this.emit('start'); + this.emit(constants.EVENT_RUN_BEGIN); this.fail(runnable, err); - this.emit('end'); + this.emit(constants.EVENT_RUN_END); } return; @@ -770,14 +822,16 @@ Runner.prototype.uncaught = function(err) { this.fail(runnable, err); if (!alreadyPassed) { // recover from test - if (runnable.type === 'test') { - this.emit('test end', runnable); - this.hookUp('afterEach', this.next); + if (runnable.type === constants.EVENT_TEST_BEGIN) { + this.emit(constants.EVENT_TEST_END, runnable); + this.hookUp(HOOK_TYPE_AFTER_EACH, this.next); return; } // recover from hooks var errSuite = this.suite; + + // XXX how about a less awful way to determine this? // if hook failure is in afterEach block if (runnable.fullTitle().indexOf('after each') > -1) { return this.hookErr(err, errSuite, true); @@ -791,53 +845,15 @@ Runner.prototype.uncaught = function(err) { } // bail - this.emit('end'); + this.emit(constants.EVENT_RUN_END); }; -/** - * Cleans up the references to all the deferred functions - * (before/after/beforeEach/afterEach) and tests of a Suite. - * These must be deleted otherwise a memory leak can happen, - * as those functions may reference variables from closures, - * thus those variables can never be garbage collected as long - * as the deferred functions exist. - * - * @param {Suite} suite - */ -function cleanSuiteReferences(suite) { - function cleanArrReferences(arr) { - for (var i = 0; i < arr.length; i++) { - delete arr[i].fn; - } - } - - if (Array.isArray(suite._beforeAll)) { - cleanArrReferences(suite._beforeAll); - } - - if (Array.isArray(suite._beforeEach)) { - cleanArrReferences(suite._beforeEach); - } - - if (Array.isArray(suite._afterAll)) { - cleanArrReferences(suite._afterAll); - } - - if (Array.isArray(suite._afterEach)) { - cleanArrReferences(suite._afterEach); - } - - for (var i = 0; i < suite.tests.length; i++) { - delete suite.tests[i].fn; - } -} - /** * Run the root suite and invoke `fn(failures)` * on completion. * * @public - * @memberof Mocha.Runner + * @memberof Runner * @param {Function} fn * @return {Runner} Runner instance. */ @@ -857,26 +873,31 @@ Runner.prototype.run = function(fn) { filterOnly(rootSuite); } self.started = true; + if (self._delay) { + self.emit(constants.EVENT_DELAY_END); + } Runner.immediately(function() { - self.emit('start'); + self.emit(constants.EVENT_RUN_BEGIN); }); self.runSuite(rootSuite, function() { debug('finished running'); Runner.immediately(function() { - self.emit('end'); + self.emit(constants.EVENT_RUN_END); }); }); } - debug('start'); + debug(constants.EVENT_RUN_BEGIN); // references cleanup to avoid memory leaks - this.on('suite end', cleanSuiteReferences); + this.on(constants.EVENT_SUITE_END, function(suite) { + suite.cleanReferences(); + }); // callback - this.on('end', function() { - debug('end'); + this.on(constants.EVENT_RUN_END, function() { + debug(constants.EVENT_RUN_END); process.removeListener('uncaughtException', uncaught); fn(self.failures); }); @@ -887,8 +908,8 @@ Runner.prototype.run = function(fn) { if (this._delay) { // for reporters, I guess. // might be nice to debounce some dots while we wait. - this.emit('waiting', rootSuite); - rootSuite.once('run', start); + this.emit(constants.EVENT_DELAY_BEGIN, rootSuite); + rootSuite.once(EVENT_ROOT_SUITE_RUN, start); } else { start(); } @@ -899,7 +920,7 @@ Runner.prototype.run = function(fn) { /** * Cleanly abort execution. * - * @memberof Mocha.Runner + * @memberof Runner * @public * @return {Runner} Runner instance. */ @@ -1031,6 +1052,8 @@ function thrown2Error(err) { * Array of globals dependent on the environment. * * @return {Array} + * @deprecated + * @todo remove; long since unsupported * @private */ function extraGlobals() { @@ -1041,7 +1064,6 @@ function extraGlobals() { }); // 'errno' was renamed to process._errno in v0.9.11. - if (nodeVersion < 0x00090b) { return ['errno']; } @@ -1049,3 +1071,11 @@ function extraGlobals() { return []; } + +Runner.constants = constants; + +/** + * Node.js' `EventEmitter` + * @external EventEmitter + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter} + */ diff --git a/lib/stats-collector.js b/lib/stats-collector.js index 7a5b3fc61f..938778fb0e 100644 --- a/lib/stats-collector.js +++ b/lib/stats-collector.js @@ -2,14 +2,22 @@ /** * Provides a factory function for a {@link StatsCollector} object. - * @private * @module */ +var constants = require('./runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; + /** * Test statistics collector. * - * @private + * @public * @typedef {Object} StatsCollector * @property {number} suites - integer count of suites run. * @property {number} tests - integer count of tests run. @@ -48,31 +56,25 @@ function createStatsCollector(runner) { runner.stats = stats; - runner.once('start', function() { + runner.once(EVENT_RUN_BEGIN, function() { stats.start = new Date(); }); - - runner.on('suite', function(suite) { + runner.on(EVENT_SUITE_BEGIN, function(suite) { suite.root || stats.suites++; }); - - runner.on('pass', function() { + runner.on(EVENT_TEST_PASS, function() { stats.passes++; }); - - runner.on('fail', function() { + runner.on(EVENT_TEST_FAIL, function() { stats.failures++; }); - - runner.on('pending', function() { + runner.on(EVENT_TEST_PENDING, function() { stats.pending++; }); - - runner.on('test end', function() { + runner.on(EVENT_TEST_END, function() { stats.tests++; }); - - runner.once('end', function() { + runner.once(EVENT_RUN_END, function() { stats.end = new Date(); stats.duration = stats.end - stats.start; }); diff --git a/lib/suite.js b/lib/suite.js index 2cb3a39de9..e2fb75bd41 100644 --- a/lib/suite.js +++ b/lib/suite.js @@ -1,7 +1,4 @@ 'use strict'; -/** - * @module Suite - */ /** * Module dependencies. @@ -24,13 +21,12 @@ exports = module.exports = Suite; /** * Create a new `Suite` with the given `title` and parent `Suite`. * - * @memberof Mocha * @public - * @param {Suite} parent - * @param {string} title + * @param {Suite} parent - Parent suite (required!) + * @param {string} title - Title * @return {Suite} */ -exports.create = function(parent, title) { +Suite.create = function(parent, title) { var suite = new Suite(title, parent.ctx); suite.parent = parent; title = suite.fullTitle(); @@ -44,7 +40,6 @@ exports.create = function(parent, title) { * @public * @class * @extends EventEmitter - * @memberof Mocha * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter} * @param {string} title - Suite title. * @param {Context} parentContext - Parent context instance. @@ -80,6 +75,16 @@ function Suite(title, parentContext, isRoot) { this._onlyTests = []; this._onlySuites = []; this.delayed = false; + + this.on('newListener', function(event) { + if (deprecatedEvents[event]) { + utils.deprecate( + 'Event "' + + event + + '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm' + ); + } + }); } /** @@ -242,7 +247,7 @@ Suite.prototype.beforeAll = function(title, fn) { var hook = this._createHook(title, fn); this._beforeAll.push(hook); - this.emit('beforeAll', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, hook); return this; }; @@ -266,7 +271,7 @@ Suite.prototype.afterAll = function(title, fn) { var hook = this._createHook(title, fn); this._afterAll.push(hook); - this.emit('afterAll', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_ALL, hook); return this; }; @@ -290,7 +295,7 @@ Suite.prototype.beforeEach = function(title, fn) { var hook = this._createHook(title, fn); this._beforeEach.push(hook); - this.emit('beforeEach', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, hook); return this; }; @@ -314,7 +319,7 @@ Suite.prototype.afterEach = function(title, fn) { var hook = this._createHook(title, fn); this._afterEach.push(hook); - this.emit('afterEach', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_EACH, hook); return this; }; @@ -334,7 +339,7 @@ Suite.prototype.addSuite = function(suite) { suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); - this.emit('suite', suite); + this.emit(constants.EVENT_SUITE_ADD_SUITE, suite); return this; }; @@ -353,7 +358,7 @@ Suite.prototype.addTest = function(test) { test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); - this.emit('test', test); + this.emit(constants.EVENT_SUITE_ADD_TEST, test); return this; }; @@ -361,7 +366,7 @@ Suite.prototype.addTest = function(test) { * Return the full title generated by recursively concatenating the parent's * full title. * - * @memberof Mocha.Suite + * @memberof Suite * @public * @return {string} */ @@ -373,7 +378,7 @@ Suite.prototype.fullTitle = function() { * Return the title path generated by recursively concatenating the parent's * title path. * - * @memberof Mocha.Suite + * @memberof Suite * @public * @return {string} */ @@ -391,7 +396,7 @@ Suite.prototype.titlePath = function() { /** * Return the total number of tests. * - * @memberof Mocha.Suite + * @memberof Suite * @public * @return {number} */ @@ -421,9 +426,148 @@ Suite.prototype.eachTest = function(fn) { /** * This will run the root suite if we happen to be running in delayed mode. + * @private */ Suite.prototype.run = function run() { if (this.root) { - this.emit('run'); + this.emit(constants.EVENT_ROOT_SUITE_RUN); } }; + +/** + * Returns the array of hooks by hook name; see `HOOK_TYPE_*` constants. + * @private + */ +Suite.prototype.getHooks = function getHooks(name) { + return this['_' + name]; +}; + +/** + * Cleans up the references to all the deferred functions + * (before/after/beforeEach/afterEach) and tests of a Suite. + * These must be deleted otherwise a memory leak can happen, + * as those functions may reference variables from closures, + * thus those variables can never be garbage collected as long + * as the deferred functions exist. + * + * @private + */ +Suite.prototype.cleanReferences = function cleanReferences() { + function cleanArrReferences(arr) { + for (var i = 0; i < arr.length; i++) { + delete arr[i].fn; + } + } + + if (Array.isArray(this._beforeAll)) { + cleanArrReferences(this._beforeAll); + } + + if (Array.isArray(this._beforeEach)) { + cleanArrReferences(this._beforeEach); + } + + if (Array.isArray(this._afterAll)) { + cleanArrReferences(this._afterAll); + } + + if (Array.isArray(this._afterEach)) { + cleanArrReferences(this._afterEach); + } + + for (var i = 0; i < this.tests.length; i++) { + delete this.tests[i].fn; + } +}; + +var constants = utils.defineConstants( + /** + * {@link Suite}-related constants. + * @public + * @memberof Suite + * @alias constants + * @readonly + * @static + * @enum {string} + */ + { + /** + * Event emitted after a test file has been loaded Not emitted in browser. + */ + EVENT_FILE_POST_REQUIRE: 'post-require', + /** + * Event emitted before a test file has been loaded. In browser, this is emitted once an interface has been selected. + */ + EVENT_FILE_PRE_REQUIRE: 'pre-require', + /** + * Event emitted immediately after a test file has been loaded. Not emitted in browser. + */ + EVENT_FILE_REQUIRE: 'require', + /** + * Event emitted when `global.run()` is called (use with `delay` option) + */ + EVENT_ROOT_SUITE_RUN: 'run', + + /** + * Namespace for collection of a `Suite`'s "after all" hooks + */ + HOOK_TYPE_AFTER_ALL: 'afterAll', + /** + * Namespace for collection of a `Suite`'s "after each" hooks + */ + HOOK_TYPE_AFTER_EACH: 'afterEach', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_ALL: 'beforeAll', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_EACH: 'beforeEach', + + // the following events are all deprecated + + /** + * Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_ALL: 'afterAll', + /** + * Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_EACH: 'afterEach', + /** + * Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_ALL: 'beforeAll', + /** + * Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_EACH: 'beforeEach', + /** + * Emitted after a child `Suite` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_SUITE: 'suite', + /** + * Emitted after a `Test` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_TEST: 'test' + } +); + +/** + * @summary There are no known use cases for these events. + * @desc This is a `Set`-like object having all keys being the constant's string value and the value being `true`. + * @todo Remove eventually + * @type {Object} + * @ignore + */ +var deprecatedEvents = Object.keys(constants) + .filter(function(constant) { + return constant.substring(0, 15) === 'EVENT_SUITE_ADD'; + }) + .reduce(function(acc, constant) { + acc[constants[constant]] = true; + return acc; + }, utils.createMap()); + +Suite.constants = constants; diff --git a/lib/test.js b/lib/test.js index 6c6aeb6b7a..f32008a85b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -10,10 +10,11 @@ module.exports = Test; /** * Initialize a new `Test` with the given `title` and callback `fn`. * + * @public * @class * @extends Runnable - * @param {String} title - * @param {Function} fn + * @param {String} title - Test title (required) + * @param {Function} [fn] - Test callback. If omitted, the Test is considered "pending" */ function Test(title, fn) { if (!isString(title)) { diff --git a/lib/utils.js b/lib/utils.js index bf50ee8ee9..c6e7245bc2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,7 +1,8 @@ 'use strict'; /** - * @module + * Various utility functions used throughout Mocha's codebase. + * @module utils */ /** @@ -27,6 +28,8 @@ var ignore = ['node_modules', '.git']; exports.inherits = require('util').inherits; +var assign = (exports.assign = require('object.assign').getPolyfill()); + /** * Escape special characters in the given string of html. * @@ -741,3 +744,33 @@ exports.clamp = function clamp(value, range) { * @public */ exports.noop = function() {}; + +/** + * @summary Creates a map-like object. + * @desc A "map" is an object with no prototype, for our purposes. In some cases this would be more appropriate than a `Map`, especially if your environment doesn't support it. Recommended for use in Mocha's public APIs. + * @param {...*} [obj] - Arguments to `Object.assign()` + * @returns {Object} An object with no prototype, having `...obj` properties + * @public + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + */ +exports.createMap = function(obj) { + return assign.apply( + null, + [Object.create(null)].concat(Array.prototype.slice.call(arguments)) + ); +}; + +/** + * @summary Create a read-only map-like object. + * This differs from {@link module:utils.createMap createMap} only in that the argument must be non-empty, because the result is frozen. + * @see {@link module:utils.createMap createMap} + * @returns {Object} A frozen object with no prototype, having `...obj` properties + */ +exports.defineConstants = function(obj) { + if (type(obj) !== 'object' || !Object.keys(obj).length) { + throw new TypeError('Invalid argument; expected a non-empty object'); + } + return Object.freeze(exports.createMap(obj)); +}; diff --git a/package-scripts.js b/package-scripts.js index 561737dbd7..391b28be92 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -281,18 +281,27 @@ module.exports = { hiddenFromHelp: true }, preprocess: { - script: - 'md-magic --config ./scripts/markdown-magic.config.js --path docs/index.md', - description: 'Preprocess documenation', - hiddenFromHelp: true + default: { + script: + 'md-magic --config ./scripts/markdown-magic.config.js --path docs/index.md', + description: 'Preprocess documentation', + hiddenFromHelp: true + }, + api: { + script: + 'md-magic --config ./scripts/markdown-magic.config.js --path "docs/api-tutorials/*.md"', + description: 'Preprocess API documentation', + hiddenFromHelp: true + } }, watch: { script: 'nps docs.preprocess && eleventy --serve', description: 'Watch docs for changes & build' }, api: { - script: 'jsdoc -c jsdoc.conf.json && cp LICENSE docs/_dist/api', - description: 'build api docs' + script: + 'nps docs.preprocess.api && jsdoc -c jsdoc.conf.json && cp LICENSE docs/_dist/api', + description: 'Build API docs' } }, updateContributors: { diff --git a/scripts/markdown-magic.config.js b/scripts/markdown-magic.config.js index 40f90cd87a..ad05067979 100644 --- a/scripts/markdown-magic.config.js +++ b/scripts/markdown-magic.config.js @@ -11,8 +11,12 @@ const {execSync} = require('child_process'); const stripAnsi = require('strip-ansi'); const markdownToc = require('markdown-toc'); const path = require('path'); +const fs = require('fs'); exports.transforms = { + /** + * Takes STDOUT of some command and injects it into the markdown + */ usage: (content, options) => { const {executable} = options; const flag = options.flag || '--help'; @@ -27,11 +31,13 @@ exports.transforms = { ); return [header, output, footer].join('\n\n'); }, - // we can't use the builtin `TOC` plugin in markdown-magic - // because it's simply not flexible enough; we can't pad with newlines, - // nor can we provide a custom filter. the custom filter would be required - // since the `TOC` plugin supplies its own which means we can't use the - // `maxdepth` option, which we need! + /** + * We can't use the builtin `TOC` plugin in markdown-magic + * because it's simply not flexible enough; we can't pad with newlines, + * nor can we provide a custom filter. the custom filter would be required + * since the `TOC` plugin supplies its own which means we can't use the + * `maxdepth` option, which we need! + */ toc: (content, options, config) => { const IGNORED_HEADINGS_REGEXP = /Features|Table of Contents/i; return ( @@ -47,5 +53,56 @@ exports.transforms = { '\n' ); }, - manifest: require('markdown-magic-package-json') + manifest: require('markdown-magic-package-json'), + /** + * Inserts the contents of a file; takes same options as builtin CODE plugin, + * but does not fetch remote URLs, tries to replace relative paths, and + * formats in a way our markdown linter likes. + */ + file: (content, options, config) => { + let output; + if (!options.src) { + return false; + } + const fileDir = path.dirname(config.originalPath); + const filePath = path.join(fileDir, options.src); + const rootDir = path.join(__dirname, '..'); + const relativeDir = path.relative(path.dirname(filePath), rootDir); + + const syntax = options.syntax || path.extname(filePath).replace(/^./, ''); + try { + output = fs.readFileSync(filePath, 'utf8', (err, contents) => { + if (err) { + console.log(`FILE NOT FOUND: ${filePath}`); + throw err; + } + return contents; + }); + } catch (err) { + console.log(`FILE NOT FOUND: ${filePath}`); + throw err; + } + + // replace relative paths in `require()` to root with "mocha". + // might not work in the general case. not gonna parse an AST for this + // e.g. `require('../../lib/foo')` => `require('mocha/lib/foo')` + // also trim any trailing whitespace + output = output + .replace( + new RegExp(`require\\(['"]${relativeDir}(.*?)['"]\\)`, 'g'), + "require('mocha$1')" + ) + .trim(); + + let header = ''; + if (options.header) { + header = `\n${options.header}`; + } + + return ` +\`\`\`${syntax}${header} +${output} +\`\`\` +`; + } }; diff --git a/test/assertions.js b/test/assertions.js index 08f7885074..fac66ce94a 100644 --- a/test/assertions.js +++ b/test/assertions.js @@ -10,7 +10,7 @@ exports.mixinMochaAssertions = function(expect) { Object.prototype.toString.call(v) === '[object Object]' && typeof v.output === 'string' && typeof v.code === 'number' && - Object.keys(v).length === 2 + Array.isArray(v.args) ); } }) diff --git a/test/integration/events.spec.js b/test/integration/events.spec.js index dc08a57381..ef3aea7931 100644 --- a/test/integration/events.spec.js +++ b/test/integration/events.spec.js @@ -56,4 +56,23 @@ describe('event order', function() { ); }); }); + + describe('--delay test case', function() { + it('should assert --delay event order', function(done) { + runMochaJSON('runner/events-delay.fixture.js', ['--delay'], function( + err, + res + ) { + if (err) { + done(err); + return; + } + expect(res, 'to have passed') + .and('to have passed test count', 2) + .and('to have passed test order', 'test A', 'test B') + .and('to have failed test count', 0); + done(); + }); + }); + }); }); diff --git a/test/integration/fixtures/regression/1794/simple-ui.js b/test/integration/fixtures/regression/1794/simple-ui.fixture.js similarity index 60% rename from test/integration/fixtures/regression/1794/simple-ui.js rename to test/integration/fixtures/regression/1794/simple-ui.fixture.js index b27c623003..0e8c666b83 100644 --- a/test/integration/fixtures/regression/1794/simple-ui.js +++ b/test/integration/fixtures/regression/1794/simple-ui.fixture.js @@ -1,15 +1,22 @@ 'use strict'; -var path = '../../../../../lib/'; -var Mocha = require(path + 'mocha'); -var Test = require(path + 'test'); +var Mocha = require('../../../../../lib/mocha'); +var Test = Mocha.Test; +var EVENT_FILE_PRE_REQUIRE = Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE; /** * A simple UI that only exposes a single function: test */ module.exports = Mocha.interfaces['simple-ui'] = function(suite) { - suite.on('pre-require', function(context, file, mocha) { - var common = require(path + 'interfaces/common')([suite], context); + suite.on(EVENT_FILE_PRE_REQUIRE, function( + context, + file, + mocha + ) { + var common = require('../../../../../lib/interfaces/common')( + [suite], + context + ); context.run = mocha.options.delay && common.runWithSuite(suite); diff --git a/test/integration/fixtures/runner/events-bail.fixture.js b/test/integration/fixtures/runner/events-bail.fixture.js index 3a345f8169..7b060bd43c 100644 --- a/test/integration/fixtures/runner/events-bail.fixture.js +++ b/test/integration/fixtures/runner/events-bail.fixture.js @@ -1,16 +1,39 @@ 'use strict'; var Runner = require('../../../../lib/runner.js'); var assert = require('assert'); +var constants = Runner.constants; +var EVENT_HOOK_BEGIN = constants.EVENT_HOOK_BEGIN; +var EVENT_HOOK_END = constants.EVENT_HOOK_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; var emitOrder = [ - 'suite'/* incorrect order*/, 'start', 'suite', - 'hook', 'hook end', 'test', 'hook', 'hook end', 'fail', 'test end', 'hook', 'hook end', - 'hook', 'hook end', 'suite end', 'suite end', 'end' + EVENT_SUITE_BEGIN, // incorrect order + EVENT_RUN_BEGIN, + EVENT_SUITE_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_FAIL, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_SUITE_END, + EVENT_RUN_END ]; var realEmit = Runner.prototype.emit; Runner.prototype.emit = function(event, ...args) { - // console.log(`emit: ${event}`); assert.strictEqual(event, emitOrder.shift()); return realEmit.call(this, event, ...args); }; diff --git a/test/integration/fixtures/runner/events-basic.fixture.js b/test/integration/fixtures/runner/events-basic.fixture.js index 81c55a8bb4..7a352379ec 100644 --- a/test/integration/fixtures/runner/events-basic.fixture.js +++ b/test/integration/fixtures/runner/events-basic.fixture.js @@ -1,17 +1,53 @@ 'use strict'; var Runner = require('../../../../lib/runner.js'); var assert = require('assert'); +var constants = Runner.constants; +var EVENT_DELAY_BEGIN = constants.EVENT_DELAY_BEGIN; +var EVENT_DELAY_END = constants.EVENT_DELAY_END; +var EVENT_HOOK_BEGIN = constants.EVENT_HOOK_BEGIN; +var EVENT_HOOK_END = constants.EVENT_HOOK_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_RETRY = constants.EVENT_TEST_RETRY; var emitOrder = [ - 'suite'/* incorrect order*/, 'start', 'suite', - 'hook', 'hook end', 'test', 'hook', 'hook end', 'pass', 'test end', 'hook', 'hook end', - 'suite', 'test', 'hook', 'hook end', 'pass', 'test end', 'hook', 'hook end', - 'suite end', 'hook', 'hook end', 'suite end', 'suite end', 'end' + EVENT_SUITE_BEGIN, // incorrect order + EVENT_RUN_BEGIN, + EVENT_SUITE_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_PASS, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_BEGIN, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_PASS, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_SUITE_END, + EVENT_RUN_END ]; var realEmit = Runner.prototype.emit; Runner.prototype.emit = function(event, ...args) { - // console.log(`emit: ${event}`); assert.strictEqual(event, emitOrder.shift()); return realEmit.call(this, event, ...args); }; diff --git a/test/integration/fixtures/runner/events-delay.fixture.js b/test/integration/fixtures/runner/events-delay.fixture.js new file mode 100644 index 0000000000..9a50760884 --- /dev/null +++ b/test/integration/fixtures/runner/events-delay.fixture.js @@ -0,0 +1,71 @@ +'use strict'; +var Runner = require('../../../../lib/runner.js'); +var assert = require('assert'); +var constants = Runner.constants; +var EVENT_DELAY_BEGIN = constants.EVENT_DELAY_BEGIN; +var EVENT_DELAY_END = constants.EVENT_DELAY_END; +var EVENT_HOOK_BEGIN = constants.EVENT_HOOK_BEGIN; +var EVENT_HOOK_END = constants.EVENT_HOOK_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_RETRY = constants.EVENT_TEST_RETRY; + +var emitOrder = [ + EVENT_DELAY_BEGIN, + EVENT_DELAY_END, + EVENT_SUITE_BEGIN, // incorrect order + EVENT_RUN_BEGIN, + EVENT_SUITE_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_PASS, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_BEGIN, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_PASS, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_SUITE_END, + EVENT_RUN_END +]; + +var realEmit = Runner.prototype.emit; +Runner.prototype.emit = function(event, ...args) { + assert.strictEqual(event, emitOrder.shift()); + return realEmit.call(this, event, ...args); +}; + +setTimeout(function() { + + describe('suite A', function() { + before('before', function() {}); + beforeEach('beforeEach', function() {}); + it('test A', function() {}); + describe('suite B', function() { + it('test B', function() {}); + }); + afterEach('afterEach', function() {}); + after('after', function() {}); + }); + + run(); +}, 100); diff --git a/test/integration/fixtures/runner/events-retries.fixture.js b/test/integration/fixtures/runner/events-retries.fixture.js index a4547c8fa8..f8936aebfa 100644 --- a/test/integration/fixtures/runner/events-retries.fixture.js +++ b/test/integration/fixtures/runner/events-retries.fixture.js @@ -1,17 +1,46 @@ 'use strict'; var Runner = require('../../../../lib/runner.js'); var assert = require('assert'); +var constants = Runner.constants; +var EVENT_HOOK_BEGIN = constants.EVENT_HOOK_BEGIN; +var EVENT_HOOK_END = constants.EVENT_HOOK_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_RETRY = constants.EVENT_TEST_RETRY; var emitOrder = [ - 'suite'/* incorrect order*/, 'start', 'suite', - 'hook', 'hook end', 'test', 'hook', 'hook end', 'retry', 'hook', 'hook end', - 'test', 'hook', 'hook end', 'fail', 'test end', 'hook', 'hook end', 'hook', 'hook end', - 'suite end', 'suite end', 'end' + EVENT_SUITE_BEGIN, // incorrect order + EVENT_RUN_BEGIN, + EVENT_SUITE_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_RETRY, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_BEGIN, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_TEST_FAIL, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_SUITE_END, + EVENT_RUN_END ]; var realEmit = Runner.prototype.emit; Runner.prototype.emit = function(event, ...args) { - // console.log(`emit: ${event}`); assert.strictEqual(event, emitOrder.shift()); return realEmit.call(this, event, ...args); }; diff --git a/test/integration/fixtures/simple-reporter.js b/test/integration/fixtures/simple-reporter.js index d066a84304..a80cf66ba9 100644 --- a/test/integration/fixtures/simple-reporter.js +++ b/test/integration/fixtures/simple-reporter.js @@ -1,28 +1,57 @@ 'use strict'; -var baseReporter = require('../../../lib/reporters/base'); -module.exports = simplereporter; +const Mocha = require('../../..'); +const { + EVENT_RUN_BEGIN, + EVENT_RUN_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_SUITE_BEGIN, + EVENT_SUITE_END +} = Mocha.Runner.constants; -function simplereporter(runner) { - baseReporter.call(this, runner); +// this reporter outputs test results, indenting two spaces per suite +class MyReporter { + constructor(runner) { + this._indents = 0; + const stats = runner.stats; - runner.on('suite', function(suite) { - console.log("on('suite') called"); - }); + runner + .once(EVENT_RUN_BEGIN, () => { + console.log('start'); + }) + .on(EVENT_SUITE_BEGIN, () => { + this.increaseIndent(); + }) + .on(EVENT_SUITE_END, () => { + this.decreaseIndent(); + }) + .on(EVENT_TEST_PASS, test => { + // Test#fullTitle() returns the suite name(s) + // prepended to the test title + console.log(`${this.indent()}pass: ${test.fullTitle()}`); + }) + .on(EVENT_TEST_FAIL, (test, err) => { + console.log( + `${this.indent()}fail: ${test.fullTitle()} - error: ${err.message}` + ); + }) + .once(EVENT_RUN_END, () => { + console.log(`end: ${stats.passes}/${stats.passes + stats.failures} ok`); + }); + } - runner.on('fail', function(test, err) { - console.log("on('fail') called"); - }); + indent() { + return Array(this._indents).join(' '); + } - runner.on('pass', function(test) { - console.log("on('pass') called"); - }); + increaseIndent() { + this._indents++; + } - runner.on('test end', function(test, err) { - console.log("on('test end') called"); - }); - - runner.on('end', function() { - console.log("on('end') called"); - }); + decreaseIndent() { + this._indents--; + } } + +module.exports = MyReporter; diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 760ae41b90..b2e98434c7 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -3,7 +3,7 @@ var format = require('util').format; var spawn = require('cross-spawn').spawn; var path = require('path'); -var baseReporter = require('../../lib/reporters/base'); +var Base = require('../../lib/reporters/base'); var DEFAULT_FIXTURE = resolveFixturePath('__default__'); var MOCHA_EXECUTABLE = require.resolve('../../bin/mocha'); @@ -95,7 +95,8 @@ module.exports = { fn( new Error( format( - 'Failed to parse JSON reporter output from result:\n\n%O', + 'Failed to parse JSON reporter output.\nArgs: %O\nResult:\n\n%O', + args, res ) ) @@ -137,7 +138,7 @@ module.exports = { /** * regular expression used for splitting lines based on new line / dot symbol. */ - splitRegExp: new RegExp('[\\n' + baseReporter.symbols.dot + ']+'), + splitRegExp: new RegExp('[\\n' + Base.symbols.dot + ']+'), /** * Invokes the mocha binary. Accepts an array of additional command line args @@ -253,7 +254,8 @@ function _spawnMochaWithListeners(args, fn, opts) { mocha.on('close', function(code) { fn(null, { output: output.split('\n').join('\n'), - code: code + code: code, + args: args }); }); diff --git a/test/integration/regression.spec.js b/test/integration/regression.spec.js index 566ccc7cd8..b122311383 100644 --- a/test/integration/regression.spec.js +++ b/test/integration/regression.spec.js @@ -31,7 +31,7 @@ describe('regressions', function() { 'fixtures', 'regression', '1794', - 'simple-ui.js' + 'simple-ui.fixture.js' ); var args = ['--require', simpleUiPath, '--ui', 'simple-ui']; run('regression/1794/issue-1794.fixture.js', args, function(err, res) { diff --git a/test/jsapi/index.js b/test/jsapi/index.js index beac56864b..231c52fedf 100644 --- a/test/jsapi/index.js +++ b/test/jsapi/index.js @@ -21,10 +21,6 @@ mocha.addFile('test/unit/duration.spec.js'); mocha.addFile('test/unit/globals.spec.js'); mocha.addFile('test/unit/timeout.spec.js'); -mocha - .run(function() { - console.log('done'); - }) - .on('pass', function(test) { - // console.log('... %s', test.title); - }); +mocha.run(function() { + console.log('done'); +}); diff --git a/test/reporters/landing.spec.js b/test/reporters/landing.spec.js index 1fee291e41..e561c23d55 100644 --- a/test/reporters/landing.spec.js +++ b/test/reporters/landing.spec.js @@ -1,7 +1,11 @@ 'use strict'; -var reporters = require('../../').reporters; +var Mocha = require('../..'); +var reporters = Mocha.reporters; var Landing = reporters.Landing; +var constants = Mocha.Runnable.constants; +var STATE_FAILED = constants.STATE_FAILED; +var STATE_PASSED = constants.STATE_PASSED; var Base = reporters.Base; var createMockRunner = require('./helpers').createMockRunner; @@ -67,7 +71,7 @@ describe('Landing reporter', function() { describe('if test has failed', function() { it('should write expected landing strip', function() { var test = { - state: 'failed' + state: STATE_FAILED }; runner = createMockRunner('test end', 'test end', null, null, test); runner.total = 12; @@ -79,7 +83,7 @@ describe('Landing reporter', function() { describe('if test has not failed', function() { it('should write expected landing strip', function() { var test = { - state: 'success' + state: STATE_PASSED }; runner = createMockRunner('test end', 'test end', null, null, test); diff --git a/test/reporters/xunit.spec.js b/test/reporters/xunit.spec.js index b3e463c88c..35555968a1 100644 --- a/test/reporters/xunit.spec.js +++ b/test/reporters/xunit.spec.js @@ -7,6 +7,9 @@ var assert = require('assert'); var createStatsCollector = require('../../lib/stats-collector'); var EventEmitter = require('events').EventEmitter; var reporters = require('../../').reporters; +var constants = require('../../lib/runnable').constants; +var STATE_FAILED = constants.STATE_FAILED; +var STATE_PASSED = constants.STATE_PASSED; var XUnit = reporters.XUnit; describe('XUnit reporter', function() { @@ -211,7 +214,7 @@ describe('XUnit reporter', function() { it('should write expected tag with error details', function() { var xunit = new XUnit({on: function() {}, once: function() {}}); var expectedTest = { - state: 'failed', + state: STATE_FAILED, title: expectedTitle, parent: { fullTitle: function() { @@ -332,7 +335,7 @@ describe('XUnit reporter', function() { var generateTest = function(passed) { var t = { title: expectedTitle + count, - state: passed ? 'passed' : 'failed', + state: passed ? STATE_PASSED : STATE_FAILED, isPending: function() { return false; }, diff --git a/test/unit/runnable.spec.js b/test/unit/runnable.spec.js index f0c02ec0b8..94df5f56b4 100644 --- a/test/unit/runnable.spec.js +++ b/test/unit/runnable.spec.js @@ -1,11 +1,12 @@ 'use strict'; -var mocha = require('../../lib/mocha'); -var utils = mocha.utils; -var Runnable = mocha.Runnable; -var Suite = mocha.Suite; +var Mocha = require('../../lib/mocha'); +var utils = Mocha.utils; +var Runnable = Mocha.Runnable; +var Suite = Mocha.Suite; var sinon = require('sinon'); var Pending = require('../../lib/pending'); +var STATE_FAILED = Runnable.constants.STATE_FAILED; describe('Runnable(title, fn)', function() { describe('#timeout(ms)', function() { @@ -682,7 +683,7 @@ describe('Runnable(title, fn)', function() { it('should return `true` if test has failed', function() { var runnable = new Runnable('foo', function() {}); // runner sets the state - runnable.state = 'failed'; + runnable.state = STATE_FAILED; runnable.run(function() { expect(runnable.isFailed(), 'to be false'); }); diff --git a/test/unit/runner.spec.js b/test/unit/runner.spec.js index 71a89b6af0..6c04f93f31 100644 --- a/test/unit/runner.spec.js +++ b/test/unit/runner.spec.js @@ -1,12 +1,17 @@ 'use strict'; +var Mocha = require('../../lib/mocha'); +var Suite = Mocha.Suite; +var Runner = Mocha.Runner; +var Test = Mocha.Test; +var Runnable = Mocha.Runnable; +var Hook = Mocha.Hook; var path = require('path'); -var mocha = require('../../lib/mocha'); -var Suite = mocha.Suite; -var Runner = mocha.Runner; -var Test = mocha.Test; -var Hook = mocha.Hook; -var noop = mocha.utils.noop; +var noop = Mocha.utils.noop; +var EVENT_TEST_FAIL = Runner.constants.EVENT_TEST_FAIL; +var EVENT_TEST_RETRY = Runner.constants.EVENT_TEST_RETRY; +var EVENT_RUN_END = Runner.constants.EVENT_RUN_END; +var STATE_FAILED = Runnable.constants.STATE_FAILED; describe('Runner', function() { var suite; @@ -103,7 +108,7 @@ describe('Runner', function() { var test = new Test('im a test', noop); runner.checkGlobals(); global.foo = 'bar'; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_test, 'to be', test); expect(_err, 'to have message', 'global leak detected: foo'); delete global.foo; @@ -116,7 +121,7 @@ describe('Runner', function() { var doneCalled = false; runner.globals('good'); global.bad = 1; - runner.on('fail', function() { + runner.on(EVENT_TEST_FAIL, function() { delete global.bad; done(); doneCalled = true; @@ -157,7 +162,7 @@ describe('Runner', function() { runner.checkGlobals(); global.foo = 'bar'; global.bar = 'baz'; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_test, 'to be', test); expect(_err, 'to have message', 'global leaks detected: foo, bar'); delete global.foo; @@ -191,7 +196,7 @@ describe('Runner', function() { global.foo = 'bar'; global.bar = 'baz'; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_test.title, 'to be', 'im a test about lions'); expect(_err, 'to have message', 'global leak detected: bar'); delete global.foo; @@ -202,7 +207,9 @@ describe('Runner', function() { it('should emit "fail" when a global beginning with d is introduced', function(done) { global.derp = 'bar'; - runner.on('fail', function() { + runner.on(EVENT_TEST_FAIL, function(test, err) { + expect(test.title, 'to be', 'herp'); + expect(err.message, 'to be', 'global leak detected: derp'); delete global.derp; done(); }); @@ -236,13 +243,13 @@ describe('Runner', function() { it('should set test.state to "failed"', function() { var test = new Test('some test', noop); runner.fail(test, 'some error'); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); }); it('should emit "fail"', function(done) { var test = new Test('some other test', noop); var err = {}; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_test, 'to be', test); expect(_err, 'to be an', Error); expect(_err, 'not to be', {}); @@ -254,7 +261,7 @@ describe('Runner', function() { it('should emit a helpful message when failed with a string', function(done) { var test = new Test('helpful test', noop); var err = 'string'; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'to be an', Error); expect( _err, @@ -269,7 +276,7 @@ describe('Runner', function() { it('should emit a the error when failed with an Error instance', function(done) { var test = new Test('a test', noop); var err = new Error('an error message'); - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'to be an', Error); expect(_err, 'to have message', 'an error message'); done(); @@ -280,7 +287,7 @@ describe('Runner', function() { it('should emit the error when failed with an Error-like object', function(done) { var test = new Test('a test', noop); var err = {message: 'an error message'}; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'not to be an', Error); expect(_err.message, 'to be', 'an error message'); done(); @@ -291,7 +298,7 @@ describe('Runner', function() { it('should emit a helpful message when failed with an Object', function(done) { var test = new Test('a test', noop); var err = {x: 1}; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'to be an', Error); expect( _err, @@ -306,7 +313,7 @@ describe('Runner', function() { it('should emit a helpful message when failed with an Array', function(done) { var test = new Test('a test', noop); var err = [1, 2]; - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'to be an', Error); expect( _err, @@ -330,7 +337,7 @@ describe('Runner', function() { }); var test = new Test('a test', noop); - runner.on('fail', function(_test, _err) { + runner.on(EVENT_TEST_FAIL, function(_test, _err) { expect(_err, 'to have message', 'not evil'); done(); }); @@ -376,7 +383,7 @@ describe('Runner', function() { var hook = new Hook(); hook.parent = suite; var err = new Error('error'); - runner.on('fail', function(_hook, _err) { + runner.on(EVENT_TEST_FAIL, function(_hook, _err) { expect(_hook, 'to be', hook); expect(_err, 'to be', err); done(); @@ -389,7 +396,7 @@ describe('Runner', function() { hook.parent = suite; var err = new Error('error'); suite.bail(false); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { throw new Error('"end" was emit, but the bail is false'); }); runner.failHook(hook, err); @@ -412,7 +419,7 @@ describe('Runner', function() { suite.retries(retries); suite.addTest(test); - runner.on('retry', function(testClone, testErr) { + runner.on(EVENT_TEST_RETRY, function(testClone, testErr) { retryableFails += 1; expect(testClone.title, 'to be', test.title); expect(testErr, 'to be', err); @@ -491,7 +498,7 @@ describe('Runner', function() { // Fake stack-trace err.stack = stack.join('\n'); - runner.on('fail', function(_hook, _err) { + runner.on(EVENT_TEST_FAIL, function(_hook, _err) { expect(_err.stack, 'to be', stack.slice(0, 3).join('\n')); done(); }); @@ -509,7 +516,7 @@ describe('Runner', function() { // Add --stack-trace option runner.fullStackTrace = true; - runner.on('fail', function(_hook, _err) { + runner.on(EVENT_TEST_FAIL, function(_hook, _err) { expect(_err.stack, 'to be', stack.join('\n')); done(); }); diff --git a/test/unit/suite.spec.js b/test/unit/suite.spec.js index fa57898a51..971208b9b9 100644 --- a/test/unit/suite.spec.js +++ b/test/unit/suite.spec.js @@ -1,8 +1,10 @@ 'use strict'; -var mocha = require('../../lib/mocha'); -var Suite = mocha.Suite; -var Test = mocha.Test; +var Mocha = require('../../lib/mocha'); +var Suite = Mocha.Suite; +var Test = Mocha.Test; +var sinon = require('sinon'); +var utils = Mocha.utils; function supportsFunctionNames() { // eslint-disable-next-line no-extra-parens @@ -10,6 +12,15 @@ function supportsFunctionNames() { } describe('Suite', function() { + var sandbox; + beforeEach(function() { + sandbox = sinon.createSandbox(); + }); + + afterEach(function() { + sandbox.restore(); + }); + describe('.clone()', function() { beforeEach(function() { this.suite = new Suite('To be cloned'); @@ -497,7 +508,11 @@ describe('Suite', function() { }); }); - describe('initialization', function() { + describe('constructor', function() { + beforeEach(function() { + sandbox.stub(utils, 'deprecate'); + }); + /* eslint no-new: off */ it("should throw an error if the title isn't a string", function() { expect(function() { @@ -514,6 +529,13 @@ describe('Suite', function() { new Suite('Bdd suite', 'root'); }, 'not to throw'); }); + + it('should report listened-for deprecated events as deprecated', function() { + new Suite('foo').on(Suite.constants.EVENT_SUITE_ADD_TEST, function() {}); + expect(utils.deprecate, 'to have all calls satisfying', [ + /Event "[^"]+" is deprecated/i + ]); + }); }); describe('timeout()', function() { diff --git a/test/unit/throw.spec.js b/test/unit/throw.spec.js index 66021d3d38..2dc3c8a759 100644 --- a/test/unit/throw.spec.js +++ b/test/unit/throw.spec.js @@ -2,9 +2,13 @@ /* eslint no-throw-literal: off */ -var Suite = require('../../lib/suite'); -var Test = require('../../lib/test'); -var Runner = require('../../lib/runner'); +var Mocha = require('../../lib/mocha'); +var Suite = Mocha.Suite; +var Test = Mocha.Test; +var Runnable = Mocha.Runnable; +var Runner = Mocha.Runner; +var EVENT_RUN_END = Runner.constants.EVENT_RUN_END; +var STATE_FAILED = Runnable.constants.STATE_FAILED; describe('a test that throws', function() { var suite; @@ -33,9 +37,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -47,9 +51,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -63,9 +67,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -79,9 +83,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -93,9 +97,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -111,9 +115,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -127,9 +131,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -141,9 +145,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); @@ -157,9 +161,9 @@ describe('a test that throws', function() { }); suite.addTest(test); runner = new Runner(suite); - runner.on('end', function() { + runner.on(EVENT_RUN_END, function() { expect(runner.failures, 'to be', 1); - expect(test.state, 'to be', 'failed'); + expect(test.state, 'to be', STATE_FAILED); done(); }); runner.run(); diff --git a/test/unit/utils.spec.js b/test/unit/utils.spec.js index fc52f4ef85..bd0360362d 100644 --- a/test/unit/utils.spec.js +++ b/test/unit/utils.spec.js @@ -705,4 +705,24 @@ describe('lib/utils', function() { expect(process.emitWarning, 'was not called'); }); }); + + describe('createMap', function() { + it('should return an object with a null prototype', function() { + expect(Object.getPrototypeOf(utils.createMap()), 'to be', null); + }); + + it('should add props to the object', function() { + expect(utils.createMap({foo: 'bar'}), 'to exhaustively satisfy', { + foo: 'bar' + }); + }); + + it('should add props from all object parameters to the object', function() { + expect( + utils.createMap({foo: 'bar'}, {bar: 'baz'}), + 'to exhaustively satisfy', + {foo: 'bar', bar: 'baz'} + ); + }); + }); });