@testomatio/reporter 2.7.2-beta.1 → 2.7.2

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.
@@ -24,12 +24,21 @@ export class VitestReporter {
24
24
  tests: (TestData & {
25
25
  status: string;
26
26
  })[];
27
+ _finalized: boolean;
28
+ _finalizing: boolean;
27
29
  onInit(): void;
28
30
  /**
29
31
  * @param {VitestTestFile[] | undefined} files // array with results;
30
32
  * @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors
31
33
  */
32
34
  onFinished(files: VitestTestFile[] | undefined, errors: unknown[] | undefined): Promise<void>;
35
+ /**
36
+ * Vitest 4+ reporter API callback.
37
+ *
38
+ * @param {Array<unknown> | undefined} testModules
39
+ * @param {unknown[] | undefined} errors
40
+ */
41
+ onTestRunEnd(testModules: Array<unknown> | undefined, errors: unknown[] | undefined): Promise<void>;
33
42
  #private;
34
43
  }
35
44
  import { Client as TestomatioClient } from '../client.js';
@@ -26,9 +26,13 @@ class VitestReporter {
26
26
  * @type {(TestData & {status: string})[]} tests
27
27
  */
28
28
  this.tests = [];
29
+ this._finalized = false;
30
+ this._finalizing = false;
29
31
  }
30
32
  // on run start
31
33
  onInit() {
34
+ this._finalized = false;
35
+ this._finalizing = false;
32
36
  this.client.createRun();
33
37
  }
34
38
  /**
@@ -36,33 +40,57 @@ class VitestReporter {
36
40
  * @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors
37
41
  */
38
42
  async onFinished(files, errors) {
39
- if (!files || !files.length)
40
- console.info('No tests executed');
41
- files.forEach(file => {
42
- // task could be test or suite
43
- file.tasks.forEach(taskOrSuite => {
44
- if (taskOrSuite.type === 'test') {
45
- const test = taskOrSuite;
46
- this.tests.push(this.#getDataFromTest(test));
47
- }
48
- else if (taskOrSuite.type === 'suite') {
49
- const suite = taskOrSuite;
50
- this.#processTasksOfSuite(suite);
51
- }
52
- else {
53
- throw new Error('Unprocessed case. Unknown task type');
54
- }
43
+ if (this._finalized || this._finalizing)
44
+ return;
45
+ this._finalizing = true;
46
+ try {
47
+ this.tests = [];
48
+ if (!files || !files.length) {
49
+ console.info('No tests executed');
50
+ return;
51
+ }
52
+ files.forEach(file => {
53
+ // task could be test or suite
54
+ getTasks(file).forEach(taskOrSuite => {
55
+ if (taskOrSuite.type === 'test') {
56
+ const test = taskOrSuite;
57
+ this.tests.push(this.#getDataFromTest(test));
58
+ }
59
+ else if (taskOrSuite.type === 'suite') {
60
+ const suite = taskOrSuite;
61
+ this.#processTasksOfSuite(suite);
62
+ }
63
+ else {
64
+ throw new Error('Unprocessed case. Unknown task type');
65
+ }
66
+ });
55
67
  });
56
- });
57
- debug(this.tests.length, 'tests collected');
58
- // send tests to Testomat.io
59
- for (const test of this.tests) {
60
- await this.client.addTestRun(test.status, test);
68
+ debug(this.tests.length, 'tests collected');
69
+ // send tests to Testomat.io
70
+ for (const test of this.tests) {
71
+ await this.client.addTestRun(test.status, test);
72
+ }
73
+ console.log('finished');
74
+ if (errors.length)
75
+ console.error('Vitest adapter errors:', errors);
76
+ await this.client.updateRunStatus(getRunStatusFromResults(files));
77
+ this._finalized = true;
78
+ }
79
+ finally {
80
+ this._finalizing = false;
61
81
  }
62
- console.log('finished');
63
- if (errors.length)
64
- console.error('Vitest adapter errors:', errors);
65
- await this.client.updateRunStatus(getRunStatusFromResults(files));
82
+ }
83
+ /**
84
+ * Vitest 4+ reporter API callback.
85
+ *
86
+ * @param {Array<unknown> | undefined} testModules
87
+ * @param {unknown[] | undefined} errors
88
+ */
89
+ async onTestRunEnd(testModules, errors) {
90
+ const files = (testModules || [])
91
+ .map(module => module && ( /** @type {any} */(module).task || module))
92
+ .filter(Boolean);
93
+ await this.onFinished(files, errors);
66
94
  }
67
95
  /* non-used listeners
68
96
  onUserConsoleLog(log) {}
@@ -81,7 +109,7 @@ class VitestReporter {
81
109
  * @param {VitestSuite} suite
82
110
  */
83
111
  #processTasksOfSuite(suite) {
84
- suite.tasks.forEach(taskOrSuite => {
112
+ getTasks(suite).forEach(taskOrSuite => {
85
113
  if (taskOrSuite.type === 'test') {
86
114
  const test = taskOrSuite;
87
115
  this.tests.push(this.#getDataFromTest(test));
@@ -105,12 +133,12 @@ class VitestReporter {
105
133
  #getDataFromTest(test) {
106
134
  return {
107
135
  error: test.result?.errors ? test.result.errors[0] : undefined,
108
- file: test.file.name,
136
+ file: test.file?.name || test.file?.filepath || '',
109
137
  logs: test.logs ? transformLogsToString(test.logs) : '',
110
138
  meta: test.meta,
111
139
  // @ts-ignore - STATUS values are string literals but type system sees them as string
112
140
  status: getTestStatus(test),
113
- suite_title: test.suite.name || test.file?.name,
141
+ suite_title: test.suite?.name || test.file?.name || test.file?.filepath,
114
142
  test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(test.name),
115
143
  time: test.result?.duration || 0,
116
144
  title: test.name,
@@ -159,9 +187,10 @@ function getTestStatus(test) {
159
187
  return constants_js_1.STATUS.FAILED;
160
188
  if (test.result?.state === 'pass')
161
189
  return constants_js_1.STATUS.PASSED;
162
- if (!test.result && test.mode === 'skip')
190
+ if (test.result?.state === 'skip' || (!test.result && test.mode === 'skip'))
163
191
  return constants_js_1.STATUS.SKIPPED;
164
192
  console.error(picocolors_1.default.red('Unprocessed case for defining test status. Contact dev team. Test:'), test);
193
+ return constants_js_1.STATUS.SKIPPED;
165
194
  }
166
195
  /**
167
196
  * @param {VitestTestLogs[]} logs
@@ -179,6 +208,23 @@ function transformLogsToString(logs) {
179
208
  });
180
209
  return logsStr;
181
210
  }
211
+ /**
212
+ * Supports both old and new Vitest task tree shapes.
213
+ *
214
+ * @param {any} node
215
+ * @returns {any[]}
216
+ */
217
+ function getTasks(node) {
218
+ if (!node)
219
+ return [];
220
+ if (Array.isArray(node.tasks))
221
+ return node.tasks;
222
+ if (Array.isArray(node.children))
223
+ return node.children;
224
+ if (node.task)
225
+ return [node.task];
226
+ return [];
227
+ }
182
228
  module.exports = VitestReporter;
183
229
 
184
230
  module.exports.VitestReporter = VitestReporter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.7.2-beta.1",
3
+ "version": "2.7.2",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -23,10 +23,14 @@ class VitestReporter {
23
23
  * @type {(TestData & {status: string})[]} tests
24
24
  */
25
25
  this.tests = [];
26
+ this._finalized = false;
27
+ this._finalizing = false;
26
28
  }
27
29
 
28
30
  // on run start
29
31
  onInit() {
32
+ this._finalized = false;
33
+ this._finalizing = false;
30
34
  this.client.createRun();
31
35
  }
32
36
 
@@ -35,34 +39,59 @@ class VitestReporter {
35
39
  * @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors
36
40
  */
37
41
  async onFinished(files, errors) {
38
- if (!files || !files.length) console.info('No tests executed');
42
+ if (this._finalized || this._finalizing) return;
43
+ this._finalizing = true;
44
+
45
+ try {
46
+ this.tests = [];
47
+ if (!files || !files.length) {
48
+ console.info('No tests executed');
49
+ return;
50
+ }
39
51
 
40
- files.forEach(file => {
41
- // task could be test or suite
42
- file.tasks.forEach(taskOrSuite => {
43
- if (taskOrSuite.type === 'test') {
44
- const test = taskOrSuite;
45
- this.tests.push(this.#getDataFromTest(test));
46
- } else if (taskOrSuite.type === 'suite') {
47
- const suite = taskOrSuite;
48
- this.#processTasksOfSuite(suite);
49
- } else {
50
- throw new Error('Unprocessed case. Unknown task type');
51
- }
52
+ files.forEach(file => {
53
+ // task could be test or suite
54
+ getTasks(file).forEach(taskOrSuite => {
55
+ if (taskOrSuite.type === 'test') {
56
+ const test = taskOrSuite;
57
+ this.tests.push(this.#getDataFromTest(test));
58
+ } else if (taskOrSuite.type === 'suite') {
59
+ const suite = taskOrSuite;
60
+ this.#processTasksOfSuite(suite);
61
+ } else {
62
+ throw new Error('Unprocessed case. Unknown task type');
63
+ }
64
+ });
52
65
  });
53
- });
54
66
 
55
- debug(this.tests.length, 'tests collected');
67
+ debug(this.tests.length, 'tests collected');
56
68
 
57
- // send tests to Testomat.io
58
- for (const test of this.tests) {
59
- await this.client.addTestRun(test.status, test);
60
- }
69
+ // send tests to Testomat.io
70
+ for (const test of this.tests) {
71
+ await this.client.addTestRun(test.status, test);
72
+ }
61
73
 
62
- console.log('finished');
63
- if (errors.length) console.error('Vitest adapter errors:', errors);
74
+ console.log('finished');
75
+ if (errors.length) console.error('Vitest adapter errors:', errors);
64
76
 
65
- await this.client.updateRunStatus(getRunStatusFromResults(files));
77
+ await this.client.updateRunStatus(getRunStatusFromResults(files));
78
+ this._finalized = true;
79
+ } finally {
80
+ this._finalizing = false;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Vitest 4+ reporter API callback.
86
+ *
87
+ * @param {Array<unknown> | undefined} testModules
88
+ * @param {unknown[] | undefined} errors
89
+ */
90
+ async onTestRunEnd(testModules, errors) {
91
+ const files = (testModules || [])
92
+ .map(module => module && (/** @type {any} */ (module).task || module))
93
+ .filter(Boolean);
94
+ await this.onFinished(files, errors);
66
95
  }
67
96
 
68
97
  /* non-used listeners
@@ -83,7 +112,7 @@ class VitestReporter {
83
112
  * @param {VitestSuite} suite
84
113
  */
85
114
  #processTasksOfSuite(suite) {
86
- suite.tasks.forEach(taskOrSuite => {
115
+ getTasks(suite).forEach(taskOrSuite => {
87
116
  if (taskOrSuite.type === 'test') {
88
117
  const test = taskOrSuite;
89
118
  this.tests.push(this.#getDataFromTest(test));
@@ -106,12 +135,12 @@ class VitestReporter {
106
135
  #getDataFromTest(test) {
107
136
  return {
108
137
  error: test.result?.errors ? test.result.errors[0] : undefined,
109
- file: test.file.name,
138
+ file: test.file?.name || test.file?.filepath || '',
110
139
  logs: test.logs ? transformLogsToString(test.logs) : '',
111
140
  meta: test.meta,
112
141
  // @ts-ignore - STATUS values are string literals but type system sees them as string
113
142
  status: getTestStatus(test),
114
- suite_title: test.suite.name || test.file?.name,
143
+ suite_title: test.suite?.name || test.file?.name || test.file?.filepath,
115
144
  test_id: getTestomatIdFromTestTitle(test.name),
116
145
  time: test.result?.duration || 0,
117
146
  title: test.name,
@@ -162,8 +191,9 @@ function getRunStatusFromResults(files) {
162
191
  function getTestStatus(test) {
163
192
  if (test.result?.state === 'fail') return STATUS.FAILED;
164
193
  if (test.result?.state === 'pass') return STATUS.PASSED;
165
- if (!test.result && test.mode === 'skip') return STATUS.SKIPPED;
194
+ if (test.result?.state === 'skip' || (!test.result && test.mode === 'skip')) return STATUS.SKIPPED;
166
195
  console.error(pc.red('Unprocessed case for defining test status. Contact dev team. Test:'), test);
196
+ return STATUS.SKIPPED;
167
197
  }
168
198
 
169
199
  /**
@@ -180,5 +210,19 @@ function transformLogsToString(logs) {
180
210
  return logsStr;
181
211
  }
182
212
 
213
+ /**
214
+ * Supports both old and new Vitest task tree shapes.
215
+ *
216
+ * @param {any} node
217
+ * @returns {any[]}
218
+ */
219
+ function getTasks(node) {
220
+ if (!node) return [];
221
+ if (Array.isArray(node.tasks)) return node.tasks;
222
+ if (Array.isArray(node.children)) return node.children;
223
+ if (node.task) return [node.task];
224
+ return [];
225
+ }
226
+
183
227
  export default VitestReporter;
184
228
  export { VitestReporter };