Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use constants for event names instead of string literals #3655

Merged
merged 1 commit into from Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {
boneskull marked this conversation as resolved.
Show resolved Hide resolved
this._indents = 0;
const stats = runner.stats;

boneskull marked this conversation as resolved.
Show resolved Hide resolved
runner
.once(EVENT_RUN_BEGIN, () => {
console.log('start');
})
.on(EVENT_SUITE_BEGIN, () => {
this.increaseIndent();
boneskull marked this conversation as resolved.
Show resolved Hide resolved
})
.on(EVENT_SUITE_END, () => {
this.decreaseIndent();
boneskull marked this conversation as resolved.
Show resolved Hide resolved
})
.on(EVENT_TEST_PASS, test => {
// Test#fullTitle() returns the suite name(s)
// prepended to the test title
console.log(`${this.indent()}pass: ${test.fullTitle()}`);
boneskull marked this conversation as resolved.
Show resolved Hide resolved
})
.on(EVENT_TEST_FAIL, (test, err) => {
console.log(
`${this.indent()}fail: ${test.fullTitle()} - error: ${err.message}`
);
})
boneskull marked this conversation as resolved.
Show resolved Hide resolved
boneskull marked this conversation as resolved.
Show resolved Hide resolved
.once(EVENT_RUN_END, () => {
console.log(`end: ${stats.passes}/${stats.passes + stats.failures} ok`);
});
boneskull marked this conversation as resolved.
Show resolved Hide resolved
}

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].
Copy link
Contributor

@plroebuck plroebuck Jan 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add spaces on both sides of dbl-hyphens so spelling can be checked more easily

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never heard of that issue before, can you explain more?

I can say with certainty that I don't love double-hyphens! Maybe there's a smart hyphen transformation...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer:
It's often useful--but not necessary--for
changed to:
It's often useful -- but not necessary -- for
or
It's often useful (but not necessary) for


## 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 |
| -------------------- | ----------- | --------------- | ------------------------------------------------------------------------------------------- |
boneskull marked this conversation as resolved.
Show resolved Hide resolved
| `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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the Wiki page poo-poo'd in favor of this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to consolidate tutorials on the documentation site. It is painful to have to go to separate sites for information.

I am not thrilled about how the tutorial is buried in the navigation, but #3663 should allow us to move the "Tutorials" nav section to the top.

[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) {
boneskull marked this conversation as resolved.
Show resolved Hide resolved
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');
boneskull marked this conversation as resolved.
Show resolved Hide resolved
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);
boneskull marked this conversation as resolved.
Show resolved Hide resolved
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