@reporters/mocha 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/index.js +148 -58
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -39,6 +39,8 @@ class Test {
39
39
  this.parent = parent;
40
40
  this.root = !parent;
41
41
  this.children = [];
42
+ this.kind = 'test';
43
+ this.started = false;
42
44
  this.#mochaOptions = mochaOptions;
43
45
 
44
46
  this.body = '';
@@ -48,15 +50,19 @@ class Test {
48
50
  this._afterEach = [];
49
51
  }
50
52
 
51
- applyTestEvent(event, passed) {
53
+ applyStartEvent(event) {
52
54
  this.title = event.data.name;
53
55
  this.file = event.data.file;
56
+ this.nesting = event.data.nesting;
57
+ }
58
+
59
+ applyTestEvent(event, passed) {
60
+ this.applyStartEvent(event);
54
61
  this.pending = Boolean(event.data.skip || event.data.todo);
55
62
  this.duration = event.data.details?.duration_ms;
56
63
  const error = event.data.details?.error;
57
64
  this.err = error?.cause instanceof Error ? error.cause : error;
58
65
  this.passed = passed;
59
- this.nesting = event.data.nesting;
60
66
  }
61
67
 
62
68
  get state() {
@@ -90,17 +96,25 @@ class Test {
90
96
  return this.titlePath().join(' ');
91
97
  }
92
98
 
93
- finalize() {
94
- this.suites = this._suites;
95
- this.tests = this._tests;
99
+ markSuite() {
100
+ this.kind = 'suite';
96
101
  }
97
102
 
98
- get _suites() {
99
- return this.children.filter((child) => child.children.length > 0);
103
+ beginSuite() {
104
+ this.markSuite();
105
+ this.started = true;
100
106
  }
101
107
 
102
- get _tests() {
103
- return this.children.filter((child) => child.children.length === 0);
108
+ get isSuite() {
109
+ return this.kind === 'suite';
110
+ }
111
+
112
+ get suites() {
113
+ return this.children.filter((child) => child.isSuite);
114
+ }
115
+
116
+ get tests() {
117
+ return this.children.filter((child) => !child.isSuite);
104
118
  }
105
119
  }
106
120
 
@@ -118,11 +132,16 @@ class Runner extends EventEmitter {
118
132
 
119
133
  #reporter;
120
134
 
121
- #root = new Test();
135
+ #mochaOptions = loadOptions([]);
122
136
 
123
- #current = this.#root;
137
+ #root = new Test(null, this.#mochaOptions);
124
138
 
125
- #mochaOptions = loadOptions([]);
139
+ #activeNodes = [];
140
+
141
+ constructor() {
142
+ super();
143
+ this.#root.markSuite();
144
+ }
126
145
 
127
146
  async init() {
128
147
  const reporterName = this.#mochaOptions.reporter ?? 'spec';
@@ -135,10 +154,12 @@ class Runner extends EventEmitter {
135
154
  try {
136
155
  Reporter = await import(reporterName).then((m) => m.default || m);
137
156
  } catch (err) {
157
+ // eslint-disable-next-line no-console
138
158
  console.error(err);
139
159
  }
140
160
  }
141
161
  if (!Reporter) {
162
+ // eslint-disable-next-line no-console
142
163
  console.error(new Error(`invalid reporter "${reporterName}"`));
143
164
  return;
144
165
  }
@@ -147,72 +168,144 @@ class Runner extends EventEmitter {
147
168
  }
148
169
 
149
170
  get suite() {
150
- return this.#current;
171
+ return this.#root;
151
172
  }
152
173
 
153
174
  end() {
154
175
  if (!this.#reporter) {
155
176
  return;
156
177
  }
178
+ this.#closeActiveSuites();
157
179
  this.stats.end = new Date();
158
180
  this.stats.duration = this.stats.end - this.stats.start;
159
- this.#report();
181
+ this.#startRootSuite();
182
+ this.emit(EVENT_SUITE_END, this.#root);
160
183
  this.emit(EVENT_RUN_END);
161
184
  if (typeof this.#reporter.done === 'function') {
162
185
  this.#reporter.done(this.stats.failures, () => {});
163
186
  }
164
187
  }
165
188
 
166
- #report(suite = this.#root) {
167
- /* Not like mocha, node:test runs tests as soon as they are encountered
168
- so the exact structure is unknown until all suites end */
169
- this.emit(EVENT_SUITE_BEGIN, suite);
170
- suite.finalize();
171
- for (const s of suite.suites) {
172
- this.#report(s);
189
+ #startRootSuite() {
190
+ if (this.#root.started) {
191
+ return;
192
+ }
193
+ this.#root.beginSuite();
194
+ this.emit(EVENT_SUITE_BEGIN, this.#root);
195
+ }
196
+
197
+ #findParent(nesting) {
198
+ if (nesting === 0) {
199
+ return this.#root;
200
+ }
201
+ return this.#activeNodes[nesting - 1] ?? this.#root;
202
+ }
203
+
204
+ #matchesNode(node, event) {
205
+ return node
206
+ && node.nesting === event.data.nesting
207
+ && node.title === event.data.name;
208
+ }
209
+
210
+ #ensureNode(event) {
211
+ const { nesting } = event.data;
212
+ const existingNode = this.#activeNodes[nesting];
213
+ if (this.#matchesNode(existingNode, event)) {
214
+ return existingNode;
173
215
  }
174
- for (const test of suite.tests) {
175
- this.emit(EVENT_TEST_BEGIN, test);
176
- if (test.pending) {
177
- this.emit(EVENT_TEST_PENDING, test);
178
- } else if (!test.passed) {
179
- this.emit(EVENT_TEST_FAIL, test, test.err);
180
- } else {
181
- this.emit(EVENT_TEST_PASS, test);
216
+
217
+ const parent = this.#findParent(nesting);
218
+ const node = new Test(parent, this.#mochaOptions);
219
+ node.applyStartEvent(event);
220
+ parent.children.push(node);
221
+ this.#activeNodes[nesting] = node;
222
+ this.#activeNodes.length = nesting + 1;
223
+ return node;
224
+ }
225
+
226
+ #openSuite(node) {
227
+ if (node.started) {
228
+ return;
229
+ }
230
+ node.beginSuite();
231
+ this.emit(EVENT_SUITE_BEGIN, node);
232
+ }
233
+
234
+ #ensureOpenAncestors(nesting) {
235
+ for (let i = 0; i < nesting; i += 1) {
236
+ const node = this.#activeNodes[i];
237
+ if (node) {
238
+ this.#openSuite(node);
182
239
  }
183
- this.emit(EVENT_TEST_END, test);
184
240
  }
185
- this.emit(EVENT_SUITE_END, suite);
186
241
  }
187
242
 
188
- addChild(event, passed) {
189
- const current = this.#current;
190
- this.#current = new Test(current);
191
- this.#current.applyTestEvent(event, passed);
192
- current.children.push(this.#current);
243
+ #trimActiveNodes() {
244
+ while (
245
+ this.#activeNodes.length > 0
246
+ && this.#activeNodes[this.#activeNodes.length - 1] === undefined
247
+ ) {
248
+ this.#activeNodes.pop();
249
+ }
250
+ }
251
+
252
+ #reportTest(node) {
253
+ this.stats.tests += 1;
254
+ this.emit(EVENT_TEST_BEGIN, node);
255
+ if (node.pending) {
256
+ this.stats.pending += 1;
257
+ this.emit(EVENT_TEST_PENDING, node);
258
+ } else if (!node.passed) {
259
+ this.stats.failures += 1;
260
+ this.emit(EVENT_TEST_FAIL, node, node.err);
261
+ } else {
262
+ this.stats.passes += 1;
263
+ this.emit(EVENT_TEST_PASS, node);
264
+ }
265
+ this.emit(EVENT_TEST_END, node);
266
+ }
267
+
268
+ #closeSuite(node) {
269
+ this.stats.suites += 1;
270
+ this.#openSuite(node);
271
+ this.emit(EVENT_SUITE_END, node);
272
+ }
273
+
274
+ #completeNode(event, passed) {
275
+ const node = this.#ensureNode(event);
276
+ this.#ensureOpenAncestors(event.data.nesting);
277
+ node.applyTestEvent(event, passed);
278
+
279
+ if (event.data.details?.type === 'suite' || node.children.length > 0) {
280
+ this.#closeSuite(node);
281
+ } else {
282
+ this.#reportTest(node);
283
+ }
284
+
285
+ this.#activeNodes[event.data.nesting] = undefined;
286
+ this.#trimActiveNodes();
287
+ }
288
+
289
+ onTestStart(event) {
290
+ this.#startRootSuite();
291
+ this.#ensureOpenAncestors(event.data.nesting);
292
+ this.#ensureNode(event);
193
293
  }
194
294
 
195
- isNewTest(event) {
196
- return this.#current.title !== event.data.name || this.#current.nesting !== event.data.nesting;
295
+ onTestComplete(event, passed) {
296
+ this.#startRootSuite();
297
+ this.#completeNode(event, passed);
197
298
  }
198
299
 
199
- childCompleted(event, passed) {
200
- this.#current.applyTestEvent(event, passed);
201
- if (this.#current?.nesting === event.data.nesting) {
202
- if (this.#current.children.length > 0) {
203
- this.stats.suites += 1;
204
- } else if (this.#current.pending) {
205
- this.stats.tests += 1;
206
- this.stats.pending += 1;
207
- } else if (!this.#current.passed) {
208
- this.stats.tests += 1;
209
- this.stats.failures += 1;
210
- } else {
211
- this.stats.tests += 1;
212
- this.stats.passes += 1;
300
+ #closeActiveSuites() {
301
+ for (let i = this.#activeNodes.length - 1; i >= 0; i -= 1) {
302
+ const node = this.#activeNodes[i];
303
+ if (node && node.isSuite) {
304
+ this.#closeSuite(node);
305
+ this.#activeNodes[i] = undefined;
213
306
  }
214
- this.#current = this.#current.parent;
215
307
  }
308
+ this.#trimActiveNodes();
216
309
  }
217
310
  }
218
311
 
@@ -223,14 +316,11 @@ module.exports = async function mochaReporter(source) {
223
316
  for await (const event of source) {
224
317
  switch (event.type) {
225
318
  case 'test:start':
226
- runner.addChild(event, false);
319
+ runner.onTestStart(event);
227
320
  break;
228
321
  case 'test:pass':
229
322
  case 'test:fail': {
230
- if (runner.isNewTest(event)) {
231
- runner.addChild(event, event.type === 'test:pass');
232
- }
233
- runner.childCompleted(event, event.type === 'test:pass');
323
+ runner.onTestComplete(event, event.type === 'test:pass');
234
324
  break;
235
325
  }
236
326
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reporters/mocha",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "use any mocha reporter with `node:test`",
5
5
  "type": "commonjs",
6
6
  "keywords": [