Skip to content

Commit

Permalink
refactor: use constants for event names instead of string literals
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
boneskull committed Feb 4, 2019
1 parent 29aa611 commit 52b9a5f
Show file tree
Hide file tree
Showing 53 changed files with 1,185 additions and 380 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -4,4 +4,5 @@ mocha.js
docs/
out/
!lib/mocha.js
test/integration/fixtures
!.*.js
13 changes: 10 additions & 3 deletions .wallaby.js
Expand Up @@ -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;
Expand All @@ -53,6 +59,7 @@ module.exports = () => {
return execFile.apply(this, arguments);
};
require('./test/setup');
}
},
debug: true
};
};
120 changes: 120 additions & 0 deletions 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:

<!-- AUTO-GENERATED-CONTENT:START (file:src=../../test/integration/fixtures/simple-reporter.js&header=// my-reporter.js)' -->

```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;
```

<!-- AUTO-GENERATED-CONTENT:END -->

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
5 changes: 5 additions & 0 deletions docs/api-tutorials/jsdoc.tutorials.json
@@ -0,0 +1,5 @@
{
"custom-reporter": {
"title": "Create a Custom Reporter"
}
}
35 changes: 18 additions & 17 deletions 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
}
}
3 changes: 2 additions & 1 deletion lib/browser/growl.js
Expand Up @@ -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.
Expand Down Expand Up @@ -53,7 +54,7 @@ exports.notify = function(runner) {
.catch(notPermitted);
};

runner.once('end', sendNotification);
runner.once(EVENT_RUN_END, sendNotification);
};

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/growl.js
Expand Up @@ -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
Expand Down Expand Up @@ -41,7 +42,7 @@ exports.isCapable = () => {
* @param {Runner} runner - Runner instance.
*/
exports.notify = runner => {
runner.once('end', () => {
runner.once(EVENT_RUN_END, () => {
display(runner);
});
};
Expand Down
4 changes: 3 additions & 1 deletion 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:
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/interfaces/exports.js
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion 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:
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion 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:
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 12 additions & 7 deletions lib/mocha.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
};
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions lib/reporters/base.js
Expand Up @@ -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`.
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down

0 comments on commit 52b9a5f

Please sign in to comment.