@reporters/mocha 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/index.js +237 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@reporters/mocha)  [](https://codecov.io/gh/MoLow/reporters)
|
|
2
|
+
|
|
3
|
+
# Mocha reporters for `node:test`
|
|
4
|
+
Use this custom reporter to use [mocha reporters](https://mochajs.org/#reporters) with `node:test`.
|
|
5
|
+
Both built-in and custom mocha reporters such as [mochawesome](https://www.npmjs.com/package/mochawesome) are supported.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @reporters/mocha
|
|
11
|
+
```
|
|
12
|
+
or
|
|
13
|
+
```bash
|
|
14
|
+
yarn add --dev @reporters/mocha
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Specify the desired mocha reporter inside the [mocha configuration file](https://mochajs.org/#configuring-mocha-nodejs), e.g. `.mocharc.js`:
|
|
20
|
+
```js
|
|
21
|
+
module.exports = {
|
|
22
|
+
reporter: '@reporters/mocha'
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run the tests with `node:test`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node --test --test-reporter=@reporters/mocha
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
/* eslint-disable no-underscore-dangle */
|
|
3
|
+
/* eslint-disable max-classes-per-file */
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { EventEmitter } = require('node:events');
|
|
8
|
+
const Mocha = require('mocha');
|
|
9
|
+
const { loadOptions } = require('mocha/lib/cli');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
EVENT_RUN_BEGIN,
|
|
13
|
+
EVENT_RUN_END,
|
|
14
|
+
EVENT_TEST_FAIL,
|
|
15
|
+
EVENT_TEST_PENDING,
|
|
16
|
+
EVENT_TEST_PASS,
|
|
17
|
+
EVENT_TEST_BEGIN,
|
|
18
|
+
EVENT_TEST_END,
|
|
19
|
+
EVENT_SUITE_BEGIN,
|
|
20
|
+
EVENT_SUITE_END,
|
|
21
|
+
} = Mocha.Runner.constants;
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
STATE_FAILED,
|
|
25
|
+
STATE_PASSED,
|
|
26
|
+
STATE_PENDING,
|
|
27
|
+
} = Mocha.Runnable.constants;
|
|
28
|
+
|
|
29
|
+
class Test {
|
|
30
|
+
#mochaOptions;
|
|
31
|
+
|
|
32
|
+
constructor(parent = null, mochaOptions = {}) {
|
|
33
|
+
this.title = '';
|
|
34
|
+
this.file = '';
|
|
35
|
+
this.pending = false;
|
|
36
|
+
this.duration = 0;
|
|
37
|
+
this.err = null;
|
|
38
|
+
this.nesting = 0;
|
|
39
|
+
this.parent = parent;
|
|
40
|
+
this.root = !parent;
|
|
41
|
+
this.children = [];
|
|
42
|
+
this.#mochaOptions = mochaOptions;
|
|
43
|
+
|
|
44
|
+
this.body = '';
|
|
45
|
+
this._beforeAll = [];
|
|
46
|
+
this._beforeEach = [];
|
|
47
|
+
this._afterAll = [];
|
|
48
|
+
this._afterEach = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
applyTestEvent(event, passed) {
|
|
52
|
+
this.title = event.data.name;
|
|
53
|
+
this.file = event.data.file;
|
|
54
|
+
this.pending = Boolean(event.data.skip || event.data.todo);
|
|
55
|
+
this.duration = event.data.details?.duration_ms;
|
|
56
|
+
this.err = event.data.details?.error;
|
|
57
|
+
this.passed = passed;
|
|
58
|
+
this.nesting = event.data.nesting;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get state() {
|
|
62
|
+
if (this.pending) {
|
|
63
|
+
return STATE_PENDING;
|
|
64
|
+
}
|
|
65
|
+
if (this.passed) {
|
|
66
|
+
return STATE_PASSED;
|
|
67
|
+
}
|
|
68
|
+
return STATE_FAILED;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isPending() {
|
|
72
|
+
return this.pending;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
slow() {
|
|
76
|
+
return this.#mochaOptions.slow ?? 75;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
currentRetry() {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
titlePath() {
|
|
84
|
+
const parentTitle = this.parent?.titlePath() ?? [];
|
|
85
|
+
return parentTitle.concat(this.title);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fullTitle() {
|
|
89
|
+
return this.titlePath().join(' ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
finalize() {
|
|
93
|
+
this.suites = this._suites;
|
|
94
|
+
this.tests = this._tests;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get _suites() {
|
|
98
|
+
return this.children.filter((child) => child.children.length > 0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get _tests() {
|
|
102
|
+
return this.children.filter((child) => child.children.length === 0);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class Runner extends EventEmitter {
|
|
107
|
+
stats = {
|
|
108
|
+
suites: 0,
|
|
109
|
+
tests: 0,
|
|
110
|
+
passes: 0,
|
|
111
|
+
pending: 0,
|
|
112
|
+
failures: 0,
|
|
113
|
+
start: new Date(),
|
|
114
|
+
end: null,
|
|
115
|
+
duration: null,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
#reporter;
|
|
119
|
+
|
|
120
|
+
#root = new Test();
|
|
121
|
+
|
|
122
|
+
#current = this.#root;
|
|
123
|
+
|
|
124
|
+
#mochaOptions = loadOptions([]);
|
|
125
|
+
|
|
126
|
+
async init() {
|
|
127
|
+
const reporterName = this.#mochaOptions.reporter ?? 'spec';
|
|
128
|
+
let Reporter;
|
|
129
|
+
if (typeof reporterName === 'function') {
|
|
130
|
+
Reporter = reporterName;
|
|
131
|
+
} else if (Mocha.reporters[reporterName]) {
|
|
132
|
+
Reporter = Mocha.reporters[reporterName];
|
|
133
|
+
} else {
|
|
134
|
+
try {
|
|
135
|
+
Reporter = await import(reporterName).then((m) => m.default || m);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!Reporter) {
|
|
141
|
+
console.error(new Error(`invalid reporter "${reporterName}"`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.#reporter = new Reporter(this, this.#mochaOptions);
|
|
145
|
+
this.emit(EVENT_RUN_BEGIN);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
end() {
|
|
149
|
+
if (!this.#reporter) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.stats.end = new Date();
|
|
153
|
+
this.stats.duration = this.stats.end - this.stats.start;
|
|
154
|
+
this.#report();
|
|
155
|
+
this.emit(EVENT_RUN_END);
|
|
156
|
+
if (typeof this.#reporter.done === 'function') {
|
|
157
|
+
this.#reporter.done(this.stats.failures, () => {});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#report(suite = this.#root) {
|
|
162
|
+
/* Not like mocha, node:test runs tests as soon as they are encountered
|
|
163
|
+
so the exact structure is unkown until all suites end */
|
|
164
|
+
this.emit(EVENT_SUITE_BEGIN, suite);
|
|
165
|
+
suite.finalize();
|
|
166
|
+
for (const s of suite.suites) {
|
|
167
|
+
this.#report(s);
|
|
168
|
+
}
|
|
169
|
+
for (const test of suite.tests) {
|
|
170
|
+
this.emit(EVENT_TEST_BEGIN, test);
|
|
171
|
+
if (test.pending) {
|
|
172
|
+
this.emit(EVENT_TEST_PENDING, test);
|
|
173
|
+
} else if (!test.passed) {
|
|
174
|
+
this.emit(EVENT_TEST_FAIL, test, test.err);
|
|
175
|
+
} else {
|
|
176
|
+
this.emit(EVENT_TEST_PASS, test);
|
|
177
|
+
}
|
|
178
|
+
this.emit(EVENT_TEST_END, test);
|
|
179
|
+
}
|
|
180
|
+
this.emit(EVENT_SUITE_END, suite);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addChild(event, passed) {
|
|
184
|
+
const current = this.#current;
|
|
185
|
+
this.#current = new Test(current);
|
|
186
|
+
this.#current.applyTestEvent(event, passed);
|
|
187
|
+
current.children.push(this.#current);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
isNewTest(event) {
|
|
191
|
+
return this.#current.title !== event.data.name || this.#current.nesting !== event.data.nesting;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
childCompleted(event, passed) {
|
|
195
|
+
this.#current.applyTestEvent(event, passed);
|
|
196
|
+
if (this.#current?.nesting === event.data.nesting) {
|
|
197
|
+
if (this.#current.children.length > 0) {
|
|
198
|
+
this.stats.suites += 1;
|
|
199
|
+
} else if (this.#current.pending) {
|
|
200
|
+
this.stats.tests += 1;
|
|
201
|
+
this.stats.pending += 1;
|
|
202
|
+
} else if (!this.#current.passed) {
|
|
203
|
+
this.stats.tests += 1;
|
|
204
|
+
this.stats.failures += 1;
|
|
205
|
+
} else {
|
|
206
|
+
this.stats.tests += 1;
|
|
207
|
+
this.stats.passes += 1;
|
|
208
|
+
}
|
|
209
|
+
this.#current = this.#current.parent;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = async function mochaReporter(source) {
|
|
215
|
+
const runner = new Runner();
|
|
216
|
+
await runner.init();
|
|
217
|
+
|
|
218
|
+
for await (const event of source) {
|
|
219
|
+
switch (event.type) {
|
|
220
|
+
case 'test:start':
|
|
221
|
+
runner.addChild(event, false);
|
|
222
|
+
break;
|
|
223
|
+
case 'test:pass':
|
|
224
|
+
case 'test:fail': {
|
|
225
|
+
if (runner.isNewTest(event)) {
|
|
226
|
+
runner.addChild(event, event.type === 'test:pass');
|
|
227
|
+
}
|
|
228
|
+
runner.childCompleted(event, event.type === 'test:pass');
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
default:
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
runner.end();
|
|
237
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reporters/mocha",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "use any mocha reporter with `node:test`",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"node:test",
|
|
8
|
+
"test",
|
|
9
|
+
"mocha",
|
|
10
|
+
"reporter",
|
|
11
|
+
"reporters"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
"./index.js"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test-reporter=spec --test-reporter-destination=stdout --test-reporter=../github/index.js --test-reporter-destination=stdout --test"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/MoLow/reporters/issues"
|
|
21
|
+
},
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"homepage": "https://github.com/MoLow/reporters/tree/main/packages/mocha",
|
|
24
|
+
"repository": "https://github.com/MoLow/reporters.git",
|
|
25
|
+
"author": "Moshe Atlow",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"mocha": "^10.3.0"
|
|
29
|
+
}
|
|
30
|
+
}
|