Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.mjs
verify-esm.js
64 changes: 62 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mocha-multi

A bit of a hack to get multiple reporters working with mocha

[![Build Status](https://travis-ci.org/glenjamin/mocha-multi.svg?branch=master)](https://travis-ci.org/glenjamin/mocha-multi)
[![mocha-multi](https://circleci.com/gh/glenjamin/mocha-multi.svg?style=shield)](https://app.circleci.com/pipelines/github/glenjamin/mocha-multi)
[![NPM version](https://img.shields.io/npm/v/mocha-multi.svg)](https://www.npmjs.com/package/mocha-multi)

Usage
Expand Down Expand Up @@ -46,7 +46,7 @@ You may specify the desired reporters (and their options) by passing `reporterOp

For example: the following config is the equivalent of setting `multi='spec=- Progress=/tmp/mocha-multi.Progress.out'`, with the addition of passing the `verbose: true` option to the Progress reporter.

```sh
```javascript
var reporterOptions = {
Progress: {
stdout: "/tmp/mocha-multi.Progress.out",
Expand All @@ -70,6 +70,66 @@ mocha.run(function onRun(failures){

The options will be passed as the second argument to the reporter constructor.

Using mocha-multi programmatically with custom reporters built in ESM
---------------------------------------------------------------------

To load a custom reporter built in ESM, you must pass the class name into the mocha-multi reporter options. When using reporter names passed in as strings, Mocha attempts to load them as CommonJS modules, using require, which won't work. Here is an example loading an ESM custom reporter programmatically.

**mocha-run-esm-reporter.mjs**:

```javascript
import Mocha from 'mocha';

// These lines make "require" available
// see https://www.kindacode.com/article/node-js-how-to-use-import-and-require-in-the-same-file/
import { createRequire } from 'module';
global.require = createRequire(import.meta.url);

const Reporter = (await import('./custom-esm-reporter.mjs')).default;

const mocha = new Mocha({
reporter: "mocha-multi",
reporterOptions: {
spec: "-",
customEsmReporter: {
"constructorFn": Reporter,
"stdout": "/tmp/custom-esm-reporter.stdout",
"options": {
"option1": "value1",
"option2": "value2"
}
}
}
});

// this is required to load the globals (describe, it, etc) in the test files
mocha.suite.emit('pre-require', global, 'nofile', mocha);

// dynamic import works for both cjs and esm.
await import('./test/dummy-spec.js');
await import('./test/dummy-spec.mjs');

// require only works for cjs, not for esm.
// require('./test/dummy-spec.js');
// require('./test/dummy-spec.mjs');


const suiteRun = mocha.run();

process.on('exit', (code) => {
process.exit(suiteRun.stats.failures);
});
```

To run programmatically, just use node:

```
$ node mocha-run-esm-reporter.mjs
```

(Note that CommonJS reporters can also be loaded in this manner)


How it works
------------

Expand Down
53 changes: 53 additions & 0 deletions custom-esm-reporter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

// These lines make "require" available
// see https://www.kindacode.com/article/node-js-how-to-use-import-and-require-in-the-same-file/
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

const Mocha = require('mocha');

const logger = console;

const {
EVENT_RUN_BEGIN,
EVENT_RUN_END,
EVENT_TEST_BEGIN,
EVENT_TEST_FAIL,
EVENT_TEST_PASS,
EVENT_TEST_PENDING,
EVENT_TEST_END,
} = Mocha.Runner.constants;

class CustomEsmReporter {
constructor(runner, reporterOptionsWrapper) {
logger.log(reporterOptionsWrapper);
this.options = reporterOptionsWrapper.reporterOptions;
runner
.once(EVENT_RUN_BEGIN, () => {
logger.log('Starting the run');
logger.log('option1: ' + this.options.option1);
logger.log('option2: ' + this.options.option2);
})
.on(EVENT_TEST_BEGIN, () => {
logger.info('Starting test');
})
.on(EVENT_TEST_PASS, (test) => {
logger.log(`Finished test ${test.title}: pass`);
})
.on(EVENT_TEST_FAIL, (test) => {
logger.log(`Finished test ${test.title}: fail`);
})
.on(EVENT_TEST_PENDING, (test) => {
logger.log(`Finished test ${test.title}: pending/skipped`);
})
.on(EVENT_TEST_END, () => {
logger.log('EVENT_TEST_END');
})
.once(EVENT_RUN_END, async () => {
logger.log('EVENT_RUN_END');
});
}
}

export default CustomEsmReporter;
27 changes: 19 additions & 8 deletions mocha-multi.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,16 @@ function safeRequire(module) {
}
}

function resolveReporter(name) {
function resolveReporter(name, constructorFn) {
// Cribbed from Mocha.prototype.reporter()
const reporter = (
safeRequire(`mocha/lib/reporters/${name}`) ||
const reporter = typeof constructorFn === 'function' ?
constructorFn :
(
safeRequire(`mocha/lib/reporters/${name}`) ||
safeRequire(name) ||
safeRequire(path.resolve(process.cwd(), name)) ||
bombOut('invalid_reporter', name)
);
);
debug("Resolved reporter '%s' into '%s'", name, util.inspect(reporter));
return reporter;
}
Expand Down Expand Up @@ -170,15 +172,19 @@ function createRunnerShim(runner, stream) {
const shim = new (require('events').EventEmitter)();

function addDelegate(prop) {
defineGetter(shim, prop,
defineGetter(
shim,
prop,
() => {
const property = runner[prop];
if (typeof property === 'function') {
return property.bind(runner);
}
return property;
},
() => runner[prop]);
// eslint-disable-next-line node/no-unsupported-features/es-syntax
() => runner[prop],
);
}

addDelegate('grepTotal');
Expand Down Expand Up @@ -217,7 +223,13 @@ function initReportersAndStreams(runner, setup, multiOptions) {
debug("Shimming runner into reporter '%s' %j", reporter, options);

return withReplacedStdout(stream, () => {
const Reporter = resolveReporter(reporter);
const constructorFn = multiOptions &&
multiOptions.reporterOptions &&
multiOptions.reporterOptions[reporter] &&
multiOptions.reporterOptions[reporter].constructorFn ?
multiOptions.reporterOptions[reporter].constructorFn :
null;
const Reporter = resolveReporter(reporter, constructorFn);
return {
stream,
reporter: new Reporter(shim, assign({}, multiOptions, {
Expand All @@ -238,7 +250,6 @@ function promiseProgress(items, fn) {
return Promise.all(items);
}


/**
* Override done to allow done processing for any reporters that have a done method.
*/
Expand Down
43 changes: 43 additions & 0 deletions mocha-run-esm-reporter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Mocha from 'mocha';

// These lines make "require" available
// see https://www.kindacode.com/article/node-js-how-to-use-import-and-require-in-the-same-file/
import { createRequire } from 'module';
global.require = createRequire(import.meta.url);

// you can use import, but dynamic import sometimes helps with transpilers
const Reporter = (await import('./custom-esm-reporter.mjs')).default;

const mocha = new Mocha({
reporter: "mocha-multi",
reporterOptions: {
spec: "-",
customEsmReporter: {
"constructorFn": Reporter,
//"stdout": "/tmp/custom-esm-reporter.stdout",
"stdout": process.env.MOCHA_MULTI_TMP_STDOUT || "/tmp/custom-esm-reporter.stdout",
"options": {
"option1": "value1",
"option2": "value2"
}
}
}
});

// this is required to load the globals (describe, it, etc) in the test files
mocha.suite.emit('pre-require', global, 'nofile', mocha);

// dynamic import works for both cjs and esm.
await import('./test/dummy-spec.mjs');
await import('./test/dummy-spec.js');

// require only works for cjs, not for esm.
// require('./test/dummy-spec.js');
// require('./test/dummy-spec.mjs');


const suiteRun = mocha.run();

process.on('exit', (code) => {
process.exit(suiteRun.stats.failures);
});
Loading