vitest 3.2.0-beta.2 → 3.2.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/LICENSE.md +29 -0
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +2 -2
- package/dist/chunks/{base.DwtwORaC.js → base.Cg0miDlQ.js} +11 -14
- package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.CYdenmiT.js} +4 -6
- package/dist/chunks/{cac.I9MLYfT-.js → cac.6rXCxFY1.js} +76 -143
- package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.Cej3MBjA.js} +1460 -1344
- package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
- package/dist/chunks/{console.K1NMVOSc.js → console.CtFJOzRO.js} +25 -45
- package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
- package/dist/chunks/{coverage.0iPg4Wrz.js → coverage.DVF1vEu8.js} +4 -12
- package/dist/chunks/{coverage.OGU09Jbh.js → coverage.EIiagJJP.js} +578 -993
- package/dist/chunks/{creator.DGAdZ4Hj.js → creator.GK6I-cL4.js} +39 -83
- package/dist/chunks/date.Bq6ZW5rf.js +73 -0
- package/dist/chunks/{defaults.DSxsTG0h.js → defaults.B7q_naMc.js} +2 -1
- package/dist/chunks/{env.Dq0hM4Xv.js → env.D4Lgay0q.js} +1 -1
- package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
- package/dist/chunks/{execute.JlGHLJZT.js → execute.B7h3T_Hc.js} +126 -217
- package/dist/chunks/{git.DXfdBEfR.js → git.BVQ8w_Sw.js} +1 -3
- package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.MAmajcmJ.d.ts} +5 -1
- package/dist/chunks/{globals.CpxW8ccg.js → globals.DEHgCU4V.js} +7 -6
- package/dist/chunks/{index.CV36oG_L.js → index.BZ0g1JD2.js} +430 -625
- package/dist/chunks/{index.DswW_LEs.js → index.BbB8_kAK.js} +25 -24
- package/dist/chunks/{index.CmC5OK9L.js → index.CIyJn3t1.js} +38 -82
- package/dist/chunks/{index.CfXMNXHg.js → index.CdQS2e2Q.js} +4 -2
- package/dist/chunks/{index.DFXFpH3w.js → index.CmSc2RE5.js} +85 -105
- package/dist/chunks/index.D3XRDfWc.js +213 -0
- package/dist/chunks/{inspector.DbDkSkFn.js → inspector.C914Efll.js} +4 -1
- package/dist/chunks/{node.3xsWotC9.js → node.fjCdwEIl.js} +1 -1
- package/dist/chunks/{reporters.d.CLC9rhKy.d.ts → reporters.d.C1ogPriE.d.ts} +47 -9
- package/dist/chunks/{rpc.D9_013TY.js → rpc.Iovn4oWe.js} +10 -19
- package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.Dd85QTll.js} +27 -31
- package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.Dd054P77.js} +16 -42
- package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.DRKU1-1g.js} +163 -186
- package/dist/chunks/{utils.BfxieIyZ.js → utils.CAioKnHs.js} +9 -14
- package/dist/chunks/{utils.CgTj3MsC.js → utils.XdZDrNZV.js} +6 -13
- package/dist/chunks/{vi.BFR5YIgu.js → vi.bdSIJ99Y.js} +137 -263
- package/dist/chunks/{vite.d.CBZ3M_ru.d.ts → vite.d.DqE4-hhK.d.ts} +3 -1
- package/dist/chunks/{vm.C1HHjtNS.js → vm.BThCzidc.js} +164 -212
- package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.DvqK5Vmu.d.ts} +1 -1
- package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.tQu2eJQy.d.ts} +5 -3
- package/dist/cli.js +5 -5
- package/dist/config.cjs +3 -1
- package/dist/config.d.ts +7 -6
- package/dist/config.js +3 -3
- package/dist/coverage.d.ts +4 -4
- package/dist/coverage.js +7 -7
- package/dist/environments.d.ts +6 -2
- package/dist/environments.js +1 -1
- package/dist/execute.d.ts +9 -3
- package/dist/execute.js +1 -1
- package/dist/index.d.ts +28 -15
- package/dist/index.js +5 -5
- package/dist/node.d.ts +18 -10
- package/dist/node.js +17 -17
- package/dist/reporters.d.ts +4 -4
- package/dist/reporters.js +4 -4
- package/dist/runners.d.ts +6 -3
- package/dist/runners.js +59 -80
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +39 -41
- package/dist/workers/forks.js +6 -4
- package/dist/workers/runVmTests.js +20 -21
- package/dist/workers/threads.js +4 -4
- package/dist/workers/vmForks.js +6 -6
- package/dist/workers/vmThreads.js +6 -6
- package/dist/workers.d.ts +4 -4
- package/dist/workers.js +10 -10
- package/package.json +21 -19
- package/dist/chunks/date.CDOsz-HY.js +0 -53
- package/dist/chunks/index.CK1YOQaa.js +0 -143
|
@@ -4,8 +4,8 @@ import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } f
|
|
|
4
4
|
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
5
5
|
import { isAbsolute, relative, dirname, basename, resolve, normalize } from 'pathe';
|
|
6
6
|
import c from 'tinyrainbow';
|
|
7
|
-
import { i as isTTY } from './env.
|
|
8
|
-
import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.
|
|
7
|
+
import { i as isTTY } from './env.D4Lgay0q.js';
|
|
8
|
+
import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.DRKU1-1g.js';
|
|
9
9
|
import { stripVTControlCharacters } from 'node:util';
|
|
10
10
|
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
11
11
|
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
@@ -40,9 +40,7 @@ const labelDefaultColors = [
|
|
|
40
40
|
];
|
|
41
41
|
function getCols(delta = 0) {
|
|
42
42
|
let length = process.stdout?.columns;
|
|
43
|
-
if (!length || Number.isNaN(length))
|
|
44
|
-
length = 30;
|
|
45
|
-
}
|
|
43
|
+
if (!length || Number.isNaN(length)) length = 30;
|
|
46
44
|
return Math.max(length + delta, 0);
|
|
47
45
|
}
|
|
48
46
|
function errorBanner(message) {
|
|
@@ -53,9 +51,8 @@ function divider(text, left, right, color) {
|
|
|
53
51
|
const c = color || ((text) => text);
|
|
54
52
|
if (text) {
|
|
55
53
|
const textLength = stripVTControlCharacters(text).length;
|
|
56
|
-
if (left == null && right != null)
|
|
57
|
-
|
|
58
|
-
} else {
|
|
54
|
+
if (left == null && right != null) left = cols - textLength - right;
|
|
55
|
+
else {
|
|
59
56
|
left = left ?? Math.floor((cols - textLength) / 2);
|
|
60
57
|
right = cols - textLength - left;
|
|
61
58
|
}
|
|
@@ -66,9 +63,7 @@ function divider(text, left, right, color) {
|
|
|
66
63
|
return F_LONG_DASH.repeat(cols);
|
|
67
64
|
}
|
|
68
65
|
function formatTestPath(root, path) {
|
|
69
|
-
if (isAbsolute(path))
|
|
70
|
-
path = relative(root, path);
|
|
71
|
-
}
|
|
66
|
+
if (isAbsolute(path)) path = relative(root, path);
|
|
72
67
|
const dir = dirname(path);
|
|
73
68
|
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
74
69
|
const base = basename(path, ext);
|
|
@@ -76,22 +71,11 @@ function formatTestPath(root, path) {
|
|
|
76
71
|
}
|
|
77
72
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
78
73
|
const summary = [];
|
|
79
|
-
if (snapshots.added) {
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
if (snapshots.
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (snapshots.updated) {
|
|
86
|
-
summary.push(c.bold(c.green(`${snapshots.updated} updated `)));
|
|
87
|
-
}
|
|
88
|
-
if (snapshots.filesRemoved) {
|
|
89
|
-
if (snapshots.didUpdate) {
|
|
90
|
-
summary.push(c.bold(c.green(`${snapshots.filesRemoved} files removed `)));
|
|
91
|
-
} else {
|
|
92
|
-
summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
74
|
+
if (snapshots.added) summary.push(c.bold(c.green(`${snapshots.added} written`)));
|
|
75
|
+
if (snapshots.unmatched) summary.push(c.bold(c.red(`${snapshots.unmatched} failed`)));
|
|
76
|
+
if (snapshots.updated) summary.push(c.bold(c.green(`${snapshots.updated} updated `)));
|
|
77
|
+
if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.filesRemoved} files removed `)));
|
|
78
|
+
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
95
79
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
96
80
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
97
81
|
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
|
|
@@ -100,11 +84,8 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
100
84
|
});
|
|
101
85
|
}
|
|
102
86
|
if (snapshots.unchecked) {
|
|
103
|
-
if (snapshots.didUpdate) {
|
|
104
|
-
|
|
105
|
-
} else {
|
|
106
|
-
summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
107
|
-
}
|
|
87
|
+
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
88
|
+
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
108
89
|
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
109
90
|
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
110
91
|
uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
@@ -116,9 +97,7 @@ function countTestErrors(tasks) {
|
|
|
116
97
|
return tasks.reduce((c, i) => c + (i.result?.errors?.length || 0), 0);
|
|
117
98
|
}
|
|
118
99
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
119
|
-
if (tasks.length === 0) {
|
|
120
|
-
return c.dim(`no ${name}`);
|
|
121
|
-
}
|
|
100
|
+
if (tasks.length === 0) return c.dim(`no ${name}`);
|
|
122
101
|
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
123
102
|
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
124
103
|
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
@@ -131,41 +110,25 @@ function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
|
131
110
|
].filter(Boolean).join(c.dim(" | ")) + (showTotal ? c.gray(` (${tasks.length})`) : "");
|
|
132
111
|
}
|
|
133
112
|
function getStateSymbol(task) {
|
|
134
|
-
if (task.mode === "skip" || task.mode === "todo")
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
if (!task.result) {
|
|
138
|
-
return pending$1;
|
|
139
|
-
}
|
|
113
|
+
if (task.mode === "skip" || task.mode === "todo") return skipped;
|
|
114
|
+
if (!task.result) return pending$1;
|
|
140
115
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
141
|
-
if (task.type === "suite")
|
|
142
|
-
return pointer;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (task.result.state === "pass") {
|
|
146
|
-
return task.meta?.benchmark ? benchmarkPass : testPass;
|
|
147
|
-
}
|
|
148
|
-
if (task.result.state === "fail") {
|
|
149
|
-
return task.type === "suite" ? suiteFail : taskFail;
|
|
116
|
+
if (task.type === "suite") return pointer;
|
|
150
117
|
}
|
|
118
|
+
if (task.result.state === "pass") return task.meta?.benchmark ? benchmarkPass : testPass;
|
|
119
|
+
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
151
120
|
return " ";
|
|
152
121
|
}
|
|
153
122
|
function formatTimeString(date) {
|
|
154
123
|
return date.toTimeString().split(" ")[0];
|
|
155
124
|
}
|
|
156
125
|
function formatTime(time) {
|
|
157
|
-
if (time > 1e3) {
|
|
158
|
-
return `${(time / 1e3).toFixed(2)}s`;
|
|
159
|
-
}
|
|
126
|
+
if (time > 1e3) return `${(time / 1e3).toFixed(2)}s`;
|
|
160
127
|
return `${Math.round(time)}ms`;
|
|
161
128
|
}
|
|
162
129
|
function formatProjectName(project, suffix = " ") {
|
|
163
|
-
if (!project?.name)
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
if (!c.isColorSupported) {
|
|
167
|
-
return `|${project.name}|${suffix}`;
|
|
168
|
-
}
|
|
130
|
+
if (!project?.name) return "";
|
|
131
|
+
if (!c.isColorSupported) return `|${project.name}|${suffix}`;
|
|
169
132
|
let background = project.color && c[`bg${capitalize(project.color)}`];
|
|
170
133
|
if (!background) {
|
|
171
134
|
const index = project.name.split("").reduce((acc, v, idx) => acc + v.charCodeAt(0) + idx, 0);
|
|
@@ -182,9 +145,7 @@ function padSummaryTitle(str) {
|
|
|
182
145
|
}
|
|
183
146
|
function truncateString(text, maxLength) {
|
|
184
147
|
const plainText = stripVTControlCharacters(text);
|
|
185
|
-
if (plainText.length <= maxLength)
|
|
186
|
-
return text;
|
|
187
|
-
}
|
|
148
|
+
if (plainText.length <= maxLength) return text;
|
|
188
149
|
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
189
150
|
}
|
|
190
151
|
function capitalize(text) {
|
|
@@ -222,11 +183,11 @@ class BaseReporter {
|
|
|
222
183
|
watchFilters;
|
|
223
184
|
failedUnwatchedFiles = [];
|
|
224
185
|
isTTY;
|
|
225
|
-
ctx =
|
|
186
|
+
ctx = void 0;
|
|
226
187
|
renderSucceed = false;
|
|
227
188
|
verbose = false;
|
|
228
|
-
_filesInWatchMode = new Map();
|
|
229
|
-
_timeStart = formatTimeString(new Date());
|
|
189
|
+
_filesInWatchMode = /* @__PURE__ */ new Map();
|
|
190
|
+
_timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
230
191
|
constructor(options = {}) {
|
|
231
192
|
this.isTTY = options.isTTY ?? isTTY;
|
|
232
193
|
}
|
|
@@ -246,67 +207,48 @@ class BaseReporter {
|
|
|
246
207
|
}
|
|
247
208
|
onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
|
|
248
209
|
this.end = performance$1.now();
|
|
249
|
-
if (!files.length && !errors.length)
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
this.reportSummary(files, errors);
|
|
253
|
-
}
|
|
210
|
+
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
211
|
+
else this.reportSummary(files, errors);
|
|
254
212
|
}
|
|
255
213
|
onTestCaseResult(testCase) {
|
|
256
|
-
if (testCase.result().state === "failed")
|
|
257
|
-
this.logFailedTask(testCase.task);
|
|
258
|
-
}
|
|
214
|
+
if (testCase.result().state === "failed") this.logFailedTask(testCase.task);
|
|
259
215
|
}
|
|
260
216
|
onTestSuiteResult(testSuite) {
|
|
261
|
-
if (testSuite.state() === "failed")
|
|
262
|
-
this.logFailedTask(testSuite.task);
|
|
263
|
-
}
|
|
217
|
+
if (testSuite.state() === "failed") this.logFailedTask(testSuite.task);
|
|
264
218
|
}
|
|
265
219
|
onTestModuleEnd(testModule) {
|
|
266
|
-
if (testModule.state() === "failed")
|
|
267
|
-
this.logFailedTask(testModule.task);
|
|
268
|
-
}
|
|
220
|
+
if (testModule.state() === "failed") this.logFailedTask(testModule.task);
|
|
269
221
|
this.printTestModule(testModule);
|
|
270
222
|
}
|
|
271
223
|
logFailedTask(task) {
|
|
272
|
-
if (this.ctx.config.silent === "passed-only")
|
|
273
|
-
for (const log of task.logs || []) {
|
|
274
|
-
this.onUserConsoleLog(log, "failed");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
224
|
+
if (this.ctx.config.silent === "passed-only") for (const log of task.logs || []) this.onUserConsoleLog(log, "failed");
|
|
277
225
|
}
|
|
278
226
|
printTestModule(testModule) {
|
|
279
227
|
const moduleState = testModule.state();
|
|
280
|
-
if (moduleState === "queued" || moduleState === "pending")
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
228
|
+
if (moduleState === "queued" || moduleState === "pending") return;
|
|
283
229
|
let testsCount = 0;
|
|
284
230
|
let failedCount = 0;
|
|
285
231
|
let skippedCount = 0;
|
|
232
|
+
// delaying logs to calculate the test stats first
|
|
233
|
+
// which minimizes the amount of for loops
|
|
286
234
|
const logs = [];
|
|
287
235
|
const originalLog = this.log.bind(this);
|
|
288
236
|
this.log = (msg) => logs.push(msg);
|
|
289
237
|
const visit = (suiteState, children) => {
|
|
290
|
-
for (const child of children) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
if (this.ctx.config.hideSkippedTests && suiteState === "skipped") {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
this.printTestCase(moduleState, child);
|
|
309
|
-
}
|
|
238
|
+
for (const child of children) if (child.type === "suite") {
|
|
239
|
+
const suiteState = child.state();
|
|
240
|
+
// Skipped suites are hidden when --hideSkippedTests, print otherwise
|
|
241
|
+
if (!this.ctx.config.hideSkippedTests || suiteState !== "skipped") this.printTestSuite(child);
|
|
242
|
+
visit(suiteState, child.children);
|
|
243
|
+
} else {
|
|
244
|
+
const testResult = child.result();
|
|
245
|
+
testsCount++;
|
|
246
|
+
if (testResult.state === "failed") failedCount++;
|
|
247
|
+
else if (testResult.state === "skipped") skippedCount++;
|
|
248
|
+
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
249
|
+
// Skipped suites are hidden when --hideSkippedTests
|
|
250
|
+
continue;
|
|
251
|
+
this.printTestCase(moduleState, child);
|
|
310
252
|
}
|
|
311
253
|
};
|
|
312
254
|
try {
|
|
@@ -326,51 +268,34 @@ class BaseReporter {
|
|
|
326
268
|
const { duration, retryCount, repeatCount } = test.diagnostic() || {};
|
|
327
269
|
const padding = this.getTestIndentation(test.task);
|
|
328
270
|
let suffix = this.getDurationPrefix(test.task);
|
|
329
|
-
if (retryCount != null && retryCount > 0) {
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
if (repeatCount != null && repeatCount > 0) {
|
|
333
|
-
suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
334
|
-
}
|
|
271
|
+
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
272
|
+
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
335
273
|
if (testResult.state === "failed") {
|
|
336
274
|
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
|
|
275
|
+
// print short errors, full errors will be at the end in summary
|
|
337
276
|
testResult.errors.forEach((error) => {
|
|
338
277
|
const message = this.formatShortError(error);
|
|
339
|
-
if (message) {
|
|
340
|
-
this.log(c.red(` ${padding}${message}`));
|
|
341
|
-
}
|
|
278
|
+
if (message) this.log(c.red(` ${padding}${message}`));
|
|
342
279
|
});
|
|
343
|
-
} else if (duration && duration > this.ctx.config.slowTestThreshold) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${c.dim(c.gray(` [${testResult.note}]`))}`);
|
|
347
|
-
} else if (this.renderSucceed || moduleState === "failed") {
|
|
348
|
-
this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
349
|
-
}
|
|
280
|
+
} else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
281
|
+
else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (testResult.state === "skipped" && testResult.note) this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${c.dim(c.gray(` [${testResult.note}]`))}`);
|
|
282
|
+
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
350
283
|
}
|
|
351
284
|
getModuleLog(testModule, counts) {
|
|
352
285
|
let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
|
|
353
|
-
if (counts.failed) {
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
if (counts.skipped) {
|
|
357
|
-
state += c.dim(" | ") + c.yellow(`${counts.skipped} skipped`);
|
|
358
|
-
}
|
|
286
|
+
if (counts.failed) state += c.dim(" | ") + c.red(`${counts.failed} failed`);
|
|
287
|
+
if (counts.skipped) state += c.dim(" | ") + c.yellow(`${counts.skipped} skipped`);
|
|
359
288
|
let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(testModule.task);
|
|
360
289
|
const diagnostic = testModule.diagnostic();
|
|
361
|
-
if (diagnostic.heap != null) {
|
|
362
|
-
suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
363
|
-
}
|
|
290
|
+
if (diagnostic.heap != null) suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
364
291
|
let title = getStateSymbol(testModule.task);
|
|
365
|
-
if (testModule.meta().typecheck) {
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
if (testModule.project.name) {
|
|
369
|
-
title += ` ${formatProjectName(testModule.project, "")}`;
|
|
370
|
-
}
|
|
292
|
+
if (testModule.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
|
|
293
|
+
if (testModule.project.name) title += ` ${formatProjectName(testModule.project, "")}`;
|
|
371
294
|
return ` ${title} ${testModule.task.name} ${suffix}`;
|
|
372
295
|
}
|
|
373
|
-
printTestSuite(_suite) {
|
|
296
|
+
printTestSuite(_suite) {
|
|
297
|
+
// Suite name is included in getTestName by default
|
|
298
|
+
}
|
|
374
299
|
getTestName(test, separator) {
|
|
375
300
|
return getTestName(test, separator);
|
|
376
301
|
}
|
|
@@ -380,33 +305,37 @@ class BaseReporter {
|
|
|
380
305
|
getTestIndentation(_test) {
|
|
381
306
|
return " ";
|
|
382
307
|
}
|
|
308
|
+
printAnnotations(test, console, padding = 0) {
|
|
309
|
+
const annotations = test.annotations();
|
|
310
|
+
if (!annotations.length) return;
|
|
311
|
+
const PADDING = " ".repeat(padding);
|
|
312
|
+
annotations.forEach(({ location, type, message }) => {
|
|
313
|
+
if (location) {
|
|
314
|
+
const file = relative(test.project.config.root, location.file);
|
|
315
|
+
this[console](`${PADDING}${c.blue(F_POINTER)} ${c.gray(`${file}:${location.line}:${location.column}`)} ${c.bold(type)}`);
|
|
316
|
+
} else this[console](`${PADDING}${c.blue(F_POINTER)} ${c.bold(type)}`);
|
|
317
|
+
this[console](`${PADDING} ${c.blue(F_DOWN_RIGHT)} ${message}`);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
383
320
|
getDurationPrefix(task) {
|
|
384
|
-
if (!task.result?.duration)
|
|
385
|
-
return "";
|
|
386
|
-
}
|
|
321
|
+
if (!task.result?.duration) return "";
|
|
387
322
|
const color = task.result.duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green;
|
|
388
323
|
return color(` ${Math.round(task.result.duration)}${c.dim("ms")}`);
|
|
389
324
|
}
|
|
390
325
|
onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
|
|
391
326
|
const failed = errors.length > 0 || hasFailed(files);
|
|
392
|
-
if (failed)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
|
|
396
|
-
} else {
|
|
397
|
-
this.log(withLabel("green", "PASS", "Waiting for file changes..."));
|
|
398
|
-
}
|
|
327
|
+
if (failed) this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
|
|
328
|
+
else if (this.ctx.isCancelling) this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
|
|
329
|
+
else this.log(withLabel("green", "PASS", "Waiting for file changes..."));
|
|
399
330
|
const hints = [c.dim("press ") + c.bold("h") + c.dim(" to show help")];
|
|
400
|
-
if (hasFailedSnapshot(files))
|
|
401
|
-
|
|
402
|
-
} else {
|
|
403
|
-
hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
|
|
404
|
-
}
|
|
331
|
+
if (hasFailedSnapshot(files)) hints.unshift(c.dim("press ") + c.bold(c.yellow("u")) + c.dim(" to update snapshot"));
|
|
332
|
+
else hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
|
|
405
333
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
406
334
|
}
|
|
407
335
|
onWatcherRerun(files, trigger) {
|
|
408
336
|
this.watchFilters = files;
|
|
409
337
|
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
338
|
+
// Update re-run count for each file
|
|
410
339
|
files.forEach((filepath) => {
|
|
411
340
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
412
341
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
@@ -418,40 +347,26 @@ class BaseReporter {
|
|
|
418
347
|
}
|
|
419
348
|
this.ctx.logger.clearFullScreen();
|
|
420
349
|
this.log(withLabel("blue", "RERUN", banner));
|
|
421
|
-
if (this.ctx.configOverride.project)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (this.ctx.filenamePattern) {
|
|
425
|
-
this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
426
|
-
}
|
|
427
|
-
if (this.ctx.configOverride.testNamePattern) {
|
|
428
|
-
this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
429
|
-
}
|
|
350
|
+
if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
351
|
+
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
352
|
+
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
430
353
|
this.log("");
|
|
431
|
-
for (const testModule of this.failedUnwatchedFiles)
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
this._timeStart = formatTimeString(new Date());
|
|
354
|
+
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
355
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
435
356
|
this.start = performance$1.now();
|
|
436
357
|
}
|
|
437
358
|
onUserConsoleLog(log, taskState) {
|
|
438
|
-
if (!this.shouldLog(log, taskState))
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
359
|
+
if (!this.shouldLog(log, taskState)) return;
|
|
441
360
|
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
|
|
442
361
|
const write = (msg) => output.write(msg);
|
|
443
362
|
let headerText = "unknown test";
|
|
444
|
-
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) :
|
|
445
|
-
if (task)
|
|
446
|
-
|
|
447
|
-
} else if (log.taskId && log.taskId !== "__vitest__unknown_test__") {
|
|
448
|
-
headerText = log.taskId;
|
|
449
|
-
}
|
|
363
|
+
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
364
|
+
if (task) headerText = getFullName(task, c.dim(" > "));
|
|
365
|
+
else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
|
|
450
366
|
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
|
|
451
367
|
if (log.origin) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
368
|
+
// browser logs don't have an extra end of line at the end like Node.js does
|
|
369
|
+
if (log.browser) write("\n");
|
|
455
370
|
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
456
371
|
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
457
372
|
const highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
@@ -468,16 +383,10 @@ class BaseReporter {
|
|
|
468
383
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
469
384
|
}
|
|
470
385
|
shouldLog(log, taskState) {
|
|
471
|
-
if (this.ctx.config.silent === true)
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
if (this.ctx.config.silent === "passed-only" && taskState !== "failed") {
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
386
|
+
if (this.ctx.config.silent === true) return false;
|
|
387
|
+
if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
477
388
|
const shouldLog = this.ctx.config.onConsoleLog?.(log.content, log.type);
|
|
478
|
-
if (shouldLog === false)
|
|
479
|
-
return shouldLog;
|
|
480
|
-
}
|
|
389
|
+
if (shouldLog === false) return shouldLog;
|
|
481
390
|
return true;
|
|
482
391
|
}
|
|
483
392
|
onServerRestart(reason) {
|
|
@@ -485,11 +394,8 @@ class BaseReporter {
|
|
|
485
394
|
}
|
|
486
395
|
reportSummary(files, errors) {
|
|
487
396
|
this.printErrorsSummary(files, errors);
|
|
488
|
-
if (this.ctx.config.mode === "benchmark")
|
|
489
|
-
|
|
490
|
-
} else {
|
|
491
|
-
this.reportTestSummary(files, errors);
|
|
492
|
-
}
|
|
397
|
+
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
398
|
+
else this.reportTestSummary(files, errors);
|
|
493
399
|
}
|
|
494
400
|
reportTestSummary(files, errors) {
|
|
495
401
|
this.log();
|
|
@@ -500,26 +406,22 @@ class BaseReporter {
|
|
|
500
406
|
const title = index === 0 ? "Snapshots" : "";
|
|
501
407
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
502
408
|
}
|
|
503
|
-
if (snapshotOutput.length > 1)
|
|
504
|
-
this.log();
|
|
505
|
-
}
|
|
409
|
+
if (snapshotOutput.length > 1) this.log();
|
|
506
410
|
this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
|
|
507
411
|
this.log(padSummaryTitle("Tests"), getStateString$1(tests));
|
|
508
412
|
if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
|
|
509
413
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
510
414
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
511
415
|
}
|
|
512
|
-
if (errors.length) {
|
|
513
|
-
this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
514
|
-
}
|
|
416
|
+
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
515
417
|
this.log(padSummaryTitle("Start at"), this._timeStart);
|
|
516
418
|
const collectTime = sum(files, (file) => file.collectDuration);
|
|
517
419
|
const testsTime = sum(files, (file) => file.result?.duration);
|
|
518
420
|
const setupTime = sum(files, (file) => file.setupDuration);
|
|
519
|
-
if (this.watchFilters)
|
|
520
|
-
|
|
521
|
-
} else {
|
|
421
|
+
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
422
|
+
else {
|
|
522
423
|
const blobs = this.ctx.state.blobs;
|
|
424
|
+
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
523
425
|
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
524
426
|
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
525
427
|
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
@@ -535,9 +437,7 @@ class BaseReporter {
|
|
|
535
437
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
536
438
|
].filter(Boolean).join(", ");
|
|
537
439
|
this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
|
|
538
|
-
if (blobs?.executionTimes) {
|
|
539
|
-
this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
540
|
-
}
|
|
440
|
+
if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
541
441
|
}
|
|
542
442
|
this.log();
|
|
543
443
|
}
|
|
@@ -548,7 +448,7 @@ class BaseReporter {
|
|
|
548
448
|
const failedTests = tests.filter((i) => i.result?.state === "fail");
|
|
549
449
|
const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
|
|
550
450
|
let current = 1;
|
|
551
|
-
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`,
|
|
451
|
+
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
|
|
552
452
|
if (failedSuites.length) {
|
|
553
453
|
this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`);
|
|
554
454
|
this.printTaskErrors(failedSuites, errorDivider);
|
|
@@ -568,9 +468,7 @@ class BaseReporter {
|
|
|
568
468
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
569
469
|
for (const bench of topBenches) {
|
|
570
470
|
const group = bench.suite || bench.file;
|
|
571
|
-
if (!group)
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
471
|
+
if (!group) continue;
|
|
574
472
|
const groupName = getFullName(group, c.dim(" > "));
|
|
575
473
|
const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
576
474
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
@@ -584,35 +482,28 @@ class BaseReporter {
|
|
|
584
482
|
}
|
|
585
483
|
printTaskErrors(tasks, errorDivider) {
|
|
586
484
|
const errorsQueue = [];
|
|
587
|
-
for (const task of tasks)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
if (previous) {
|
|
601
|
-
previous[1].push(task);
|
|
602
|
-
} else {
|
|
603
|
-
errorsQueue.push([error, [task]]);
|
|
604
|
-
}
|
|
485
|
+
for (const task of tasks)
|
|
486
|
+
// Merge identical errors
|
|
487
|
+
task.result?.errors?.forEach((error) => {
|
|
488
|
+
let previous;
|
|
489
|
+
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
490
|
+
if (i[0]?.stack !== error.stack) return false;
|
|
491
|
+
const currentProjectName = task?.projectName || task.file?.projectName || "";
|
|
492
|
+
const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
|
|
493
|
+
const currentAnnotations = task.type === "test" && task.annotations;
|
|
494
|
+
const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
|
|
495
|
+
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
605
496
|
});
|
|
606
|
-
|
|
497
|
+
if (previous) previous[1].push(task);
|
|
498
|
+
else errorsQueue.push([error, [task]]);
|
|
499
|
+
});
|
|
607
500
|
for (const [error, tasks] of errorsQueue) {
|
|
608
501
|
for (const task of tasks) {
|
|
609
502
|
const filepath = task?.filepath || "";
|
|
610
503
|
const projectName = task?.projectName || task.file?.projectName || "";
|
|
611
504
|
const project = this.ctx.projects.find((p) => p.name === projectName);
|
|
612
505
|
let name = getFullName(task, c.dim(" > "));
|
|
613
|
-
if (filepath) {
|
|
614
|
-
name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
615
|
-
}
|
|
506
|
+
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
616
507
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
617
508
|
}
|
|
618
509
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
@@ -622,10 +513,24 @@ class BaseReporter {
|
|
|
622
513
|
screenshotPaths,
|
|
623
514
|
task: tasks[0]
|
|
624
515
|
});
|
|
516
|
+
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
517
|
+
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
518
|
+
this.printAnnotations(test, "error", 1);
|
|
519
|
+
this.error();
|
|
520
|
+
}
|
|
625
521
|
errorDivider();
|
|
626
522
|
}
|
|
627
523
|
}
|
|
628
524
|
}
|
|
525
|
+
function deepEqual(a, b) {
|
|
526
|
+
if (a === b) return true;
|
|
527
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
528
|
+
const keysA = Object.keys(a);
|
|
529
|
+
const keysB = Object.keys(b);
|
|
530
|
+
if (keysA.length !== keysB.length) return false;
|
|
531
|
+
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
629
534
|
function sum(items, cb) {
|
|
630
535
|
return items.reduce((total, next) => {
|
|
631
536
|
return total + Math.max(cb(next) || 0, 0);
|
|
@@ -639,9 +544,10 @@ class BasicReporter extends BaseReporter {
|
|
|
639
544
|
}
|
|
640
545
|
onInit(ctx) {
|
|
641
546
|
super.onInit(ctx);
|
|
642
|
-
ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\
|
|
547
|
+
ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\nRemove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`);
|
|
643
548
|
}
|
|
644
549
|
reportSummary(files, errors) {
|
|
550
|
+
// non-tty mode doesn't add a new line
|
|
645
551
|
this.ctx.logger.log();
|
|
646
552
|
return super.reportSummary(files, errors);
|
|
647
553
|
}
|
|
@@ -762,9 +668,7 @@ class BlobReporter {
|
|
|
762
668
|
this.options = options;
|
|
763
669
|
}
|
|
764
670
|
onInit(ctx) {
|
|
765
|
-
if (ctx.config.watch)
|
|
766
|
-
throw new Error("Blob reporter is not supported in watch mode");
|
|
767
|
-
}
|
|
671
|
+
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
768
672
|
this.ctx = ctx;
|
|
769
673
|
this.start = performance.now();
|
|
770
674
|
}
|
|
@@ -777,9 +681,7 @@ class BlobReporter {
|
|
|
777
681
|
}
|
|
778
682
|
const modules = this.ctx.projects.map((project) => {
|
|
779
683
|
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
780
|
-
if (!mod[1].file)
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
684
|
+
if (!mod[1].file) return null;
|
|
783
685
|
return [
|
|
784
686
|
mod[0],
|
|
785
687
|
mod[1].file,
|
|
@@ -803,25 +705,20 @@ class BlobReporter {
|
|
|
803
705
|
async function writeBlob(content, filename) {
|
|
804
706
|
const report = stringify(content);
|
|
805
707
|
const dir = dirname(filename);
|
|
806
|
-
if (!existsSync(dir)) {
|
|
807
|
-
await mkdir(dir, { recursive: true });
|
|
808
|
-
}
|
|
708
|
+
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
809
709
|
await writeFile(filename, report, "utf-8");
|
|
810
710
|
}
|
|
811
711
|
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
712
|
+
// using process.cwd() because --merge-reports can only be used in CLI
|
|
812
713
|
const resolvedDir = resolve(process.cwd(), blobsDirectory);
|
|
813
714
|
const blobsFiles = await readdir(resolvedDir);
|
|
814
715
|
const promises = blobsFiles.map(async (filename) => {
|
|
815
716
|
const fullPath = resolve(resolvedDir, filename);
|
|
816
717
|
const stats = await stat(fullPath);
|
|
817
|
-
if (!stats.isFile()) {
|
|
818
|
-
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
819
|
-
}
|
|
718
|
+
if (!stats.isFile()) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
820
719
|
const content = await readFile(fullPath, "utf-8");
|
|
821
720
|
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
822
|
-
if (!version) {
|
|
823
|
-
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
824
|
-
}
|
|
721
|
+
if (!version) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
825
722
|
return {
|
|
826
723
|
version,
|
|
827
724
|
files,
|
|
@@ -833,23 +730,16 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
833
730
|
};
|
|
834
731
|
});
|
|
835
732
|
const blobs = await Promise.all(promises);
|
|
836
|
-
if (!blobs.length) {
|
|
837
|
-
throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
838
|
-
}
|
|
733
|
+
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
839
734
|
const versions = new Set(blobs.map((blob) => blob.version));
|
|
840
|
-
if (versions.size > 1) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (!versions.has(currentVersion)) {
|
|
844
|
-
throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
845
|
-
}
|
|
735
|
+
if (versions.size > 1) throw new Error(`vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received\n\n${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`);
|
|
736
|
+
if (!versions.has(currentVersion)) throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
737
|
+
// fake module graph - it is used to check if module is imported, but we don't use values inside
|
|
846
738
|
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
847
739
|
blobs.forEach((blob) => {
|
|
848
740
|
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
849
741
|
const project = projects[projectName];
|
|
850
|
-
if (!project)
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
742
|
+
if (!project) return;
|
|
853
743
|
moduleIds.forEach(([moduleId, file, url]) => {
|
|
854
744
|
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
855
745
|
moduleNode.url = url;
|
|
@@ -888,7 +778,7 @@ class WindowRenderer {
|
|
|
888
778
|
options;
|
|
889
779
|
streams;
|
|
890
780
|
buffer = [];
|
|
891
|
-
renderInterval =
|
|
781
|
+
renderInterval = void 0;
|
|
892
782
|
renderScheduled = false;
|
|
893
783
|
windowHeight = 0;
|
|
894
784
|
finished = false;
|
|
@@ -903,6 +793,7 @@ class WindowRenderer {
|
|
|
903
793
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
904
794
|
};
|
|
905
795
|
this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
|
|
796
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
906
797
|
this.options.logger.onTerminalCleanup(() => {
|
|
907
798
|
this.flushBuffer();
|
|
908
799
|
this.stop();
|
|
@@ -939,10 +830,9 @@ class WindowRenderer {
|
|
|
939
830
|
}
|
|
940
831
|
}
|
|
941
832
|
flushBuffer() {
|
|
942
|
-
if (this.buffer.length === 0)
|
|
943
|
-
return this.render();
|
|
944
|
-
}
|
|
833
|
+
if (this.buffer.length === 0) return this.render();
|
|
945
834
|
let current;
|
|
835
|
+
// Concatenate same types into a single render
|
|
946
836
|
for (const next of this.buffer.splice(0)) {
|
|
947
837
|
if (!current) {
|
|
948
838
|
current = next;
|
|
@@ -955,9 +845,7 @@ class WindowRenderer {
|
|
|
955
845
|
}
|
|
956
846
|
current.message += next.message;
|
|
957
847
|
}
|
|
958
|
-
if (current)
|
|
959
|
-
this.render(current?.message, current?.type);
|
|
960
|
-
}
|
|
848
|
+
if (current) this.render(current?.message, current?.type);
|
|
961
849
|
}
|
|
962
850
|
render(message, type = "output") {
|
|
963
851
|
if (this.finished) {
|
|
@@ -967,44 +855,30 @@ class WindowRenderer {
|
|
|
967
855
|
const windowContent = this.options.getWindow();
|
|
968
856
|
const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
969
857
|
let padding = this.windowHeight - rowCount;
|
|
970
|
-
if (padding > 0 && message)
|
|
971
|
-
padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
972
|
-
}
|
|
858
|
+
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
973
859
|
this.write(SYNC_START);
|
|
974
860
|
this.clearWindow();
|
|
975
|
-
if (message)
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
if (padding > 0) {
|
|
979
|
-
this.write("\n".repeat(padding));
|
|
980
|
-
}
|
|
861
|
+
if (message) this.write(message, type);
|
|
862
|
+
if (padding > 0) this.write("\n".repeat(padding));
|
|
981
863
|
this.write(windowContent.join("\n"));
|
|
982
864
|
this.write(SYNC_END);
|
|
983
865
|
this.windowHeight = rowCount + Math.max(0, padding);
|
|
984
866
|
}
|
|
985
867
|
clearWindow() {
|
|
986
|
-
if (this.windowHeight === 0)
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
868
|
+
if (this.windowHeight === 0) return;
|
|
989
869
|
this.write(CLEAR_LINE);
|
|
990
|
-
for (let i = 1; i < this.windowHeight; i++) {
|
|
991
|
-
this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
|
|
992
|
-
}
|
|
870
|
+
for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
|
|
993
871
|
this.windowHeight = 0;
|
|
994
872
|
}
|
|
995
873
|
interceptStream(stream, type) {
|
|
996
874
|
const original = stream.write;
|
|
875
|
+
// @ts-expect-error -- not sure how 2 overloads should be typed
|
|
997
876
|
stream.write = (chunk, _, callback) => {
|
|
998
|
-
if (chunk)
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
type,
|
|
1004
|
-
message: chunk.toString()
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
877
|
+
if (chunk) if (this.finished) this.write(chunk.toString(), type);
|
|
878
|
+
else this.buffer.push({
|
|
879
|
+
type,
|
|
880
|
+
message: chunk.toString()
|
|
881
|
+
});
|
|
1008
882
|
callback?.();
|
|
1009
883
|
};
|
|
1010
884
|
return function restore() {
|
|
@@ -1039,13 +913,13 @@ class SummaryReporter {
|
|
|
1039
913
|
tests = emptyCounters();
|
|
1040
914
|
maxParallelTests = 0;
|
|
1041
915
|
/** Currently running test modules, may include finished test modules too */
|
|
1042
|
-
runningModules = new Map();
|
|
916
|
+
runningModules = /* @__PURE__ */ new Map();
|
|
1043
917
|
/** ID of finished `this.runningModules` that are currently being shown */
|
|
1044
|
-
finishedModules = new Map();
|
|
918
|
+
finishedModules = /* @__PURE__ */ new Map();
|
|
1045
919
|
startTime = "";
|
|
1046
920
|
currentTime = 0;
|
|
1047
921
|
duration = 0;
|
|
1048
|
-
durationInterval =
|
|
922
|
+
durationInterval = void 0;
|
|
1049
923
|
onInit(ctx, options = {}) {
|
|
1050
924
|
this.ctx = ctx;
|
|
1051
925
|
this.options = {
|
|
@@ -1077,6 +951,7 @@ class SummaryReporter {
|
|
|
1077
951
|
clearInterval(this.durationInterval);
|
|
1078
952
|
}
|
|
1079
953
|
onTestModuleQueued(module) {
|
|
954
|
+
// When new test module starts, take the place of previously finished test module, if any
|
|
1080
955
|
if (this.finishedModules.size) {
|
|
1081
956
|
const finished = this.finishedModules.keys().next().value;
|
|
1082
957
|
this.removeTestModule(finished);
|
|
@@ -1098,9 +973,7 @@ class SummaryReporter {
|
|
|
1098
973
|
}
|
|
1099
974
|
onHookStart(options) {
|
|
1100
975
|
const stats = this.getHookStats(options);
|
|
1101
|
-
if (!stats)
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
976
|
+
if (!stats) return;
|
|
1104
977
|
const hook = {
|
|
1105
978
|
name: options.name,
|
|
1106
979
|
visible: false,
|
|
@@ -1116,20 +989,15 @@ class SummaryReporter {
|
|
|
1116
989
|
}
|
|
1117
990
|
onHookEnd(options) {
|
|
1118
991
|
const stats = this.getHookStats(options);
|
|
1119
|
-
if (stats?.hook?.name !== options.name)
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
992
|
+
if (stats?.hook?.name !== options.name) return;
|
|
1122
993
|
stats.hook.onFinish();
|
|
1123
994
|
stats.hook.visible = false;
|
|
1124
995
|
}
|
|
1125
996
|
onTestCaseReady(test) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
997
|
+
// Track slow running tests only on verbose mode
|
|
998
|
+
if (!this.options.verbose) return;
|
|
1129
999
|
const stats = this.runningModules.get(test.module.id);
|
|
1130
|
-
if (!stats || stats.tests.has(test.id))
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1000
|
+
if (!stats || stats.tests.has(test.id)) return;
|
|
1133
1001
|
const slowTest = {
|
|
1134
1002
|
name: test.name,
|
|
1135
1003
|
visible: false,
|
|
@@ -1147,53 +1015,42 @@ class SummaryReporter {
|
|
|
1147
1015
|
}
|
|
1148
1016
|
onTestCaseResult(test) {
|
|
1149
1017
|
const stats = this.runningModules.get(test.module.id);
|
|
1150
|
-
if (!stats)
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1018
|
+
if (!stats) return;
|
|
1153
1019
|
stats.tests.get(test.id)?.onFinish();
|
|
1154
1020
|
stats.tests.delete(test.id);
|
|
1155
1021
|
stats.completed++;
|
|
1156
1022
|
const result = test.result();
|
|
1157
|
-
if (result?.state === "passed")
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
this.tests.failed++;
|
|
1161
|
-
} else if (!result?.state || result?.state === "skipped") {
|
|
1162
|
-
this.tests.skipped++;
|
|
1163
|
-
}
|
|
1023
|
+
if (result?.state === "passed") this.tests.passed++;
|
|
1024
|
+
else if (result?.state === "failed") this.tests.failed++;
|
|
1025
|
+
else if (!result?.state || result?.state === "skipped") this.tests.skipped++;
|
|
1164
1026
|
this.renderer.schedule();
|
|
1165
1027
|
}
|
|
1166
1028
|
onTestModuleEnd(module) {
|
|
1167
1029
|
const state = module.state();
|
|
1168
1030
|
this.modules.completed++;
|
|
1169
|
-
if (state === "passed")
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
} else if (module.task.mode === "todo" && state === "skipped") {
|
|
1174
|
-
this.modules.todo++;
|
|
1175
|
-
} else if (state === "skipped") {
|
|
1176
|
-
this.modules.skipped++;
|
|
1177
|
-
}
|
|
1031
|
+
if (state === "passed") this.modules.passed++;
|
|
1032
|
+
else if (state === "failed") this.modules.failed++;
|
|
1033
|
+
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1034
|
+
else if (state === "skipped") this.modules.skipped++;
|
|
1178
1035
|
const left = this.modules.total - this.modules.completed;
|
|
1179
|
-
if
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
} else {
|
|
1036
|
+
// Keep finished tests visible in summary for a while if there are more tests left.
|
|
1037
|
+
// When a new test starts in onTestModuleQueued it will take this ones place.
|
|
1038
|
+
// This reduces flickering by making summary more stable.
|
|
1039
|
+
if (left > this.maxParallelTests) this.finishedModules.set(module.id, setTimeout(() => {
|
|
1184
1040
|
this.removeTestModule(module.id);
|
|
1185
|
-
}
|
|
1041
|
+
}, FINISHED_TEST_CLEANUP_TIME_MS).unref());
|
|
1042
|
+
else
|
|
1043
|
+
// Run is about to end as there are less tests left than whole run had parallel at max.
|
|
1044
|
+
// Remove finished test immediately.
|
|
1045
|
+
this.removeTestModule(module.id);
|
|
1186
1046
|
this.renderer.schedule();
|
|
1187
1047
|
}
|
|
1188
1048
|
getHookStats({ entity }) {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1049
|
+
// Track slow running hooks only on verbose mode
|
|
1050
|
+
if (!this.options.verbose) return;
|
|
1192
1051
|
const module = entity.type === "module" ? entity : entity.module;
|
|
1193
1052
|
const stats = this.runningModules.get(module.id);
|
|
1194
|
-
if (!stats)
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1053
|
+
if (!stats) return;
|
|
1197
1054
|
return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
1198
1055
|
}
|
|
1199
1056
|
createSummary() {
|
|
@@ -1209,14 +1066,10 @@ class SummaryReporter {
|
|
|
1209
1066
|
const elapsed = this.currentTime - task.startTime;
|
|
1210
1067
|
const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
1211
1068
|
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
1212
|
-
if (task.hook?.visible) {
|
|
1213
|
-
summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
1214
|
-
}
|
|
1069
|
+
if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
1215
1070
|
}
|
|
1216
1071
|
}
|
|
1217
|
-
if (this.runningModules.size > 0)
|
|
1218
|
-
summary.push("");
|
|
1219
|
-
}
|
|
1072
|
+
if (this.runningModules.size > 0) summary.push("");
|
|
1220
1073
|
summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
|
|
1221
1074
|
summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
|
|
1222
1075
|
summary.push(padSummaryTitle("Start at") + this.startTime);
|
|
@@ -1226,16 +1079,14 @@ class SummaryReporter {
|
|
|
1226
1079
|
}
|
|
1227
1080
|
startTimers() {
|
|
1228
1081
|
const start = performance.now();
|
|
1229
|
-
this.startTime = formatTimeString(new Date());
|
|
1082
|
+
this.startTime = formatTimeString(/* @__PURE__ */ new Date());
|
|
1230
1083
|
this.durationInterval = setInterval(() => {
|
|
1231
1084
|
this.currentTime = performance.now();
|
|
1232
1085
|
this.duration = this.currentTime - start;
|
|
1233
1086
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1234
1087
|
}
|
|
1235
1088
|
removeTestModule(id) {
|
|
1236
|
-
if (!id)
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1089
|
+
if (!id) return;
|
|
1239
1090
|
const testFile = this.runningModules.get(id);
|
|
1240
1091
|
testFile?.hook?.onFinish();
|
|
1241
1092
|
testFile?.tests?.forEach((test) => test.onFinish());
|
|
@@ -1263,12 +1114,8 @@ function getStateString(entry) {
|
|
|
1263
1114
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1264
1115
|
}
|
|
1265
1116
|
function sortRunningModules(a, b) {
|
|
1266
|
-
if ((a.projectName || "") > (b.projectName || ""))
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
if ((a.projectName || "") < (b.projectName || "")) {
|
|
1270
|
-
return -1;
|
|
1271
|
-
}
|
|
1117
|
+
if ((a.projectName || "") > (b.projectName || "")) return 1;
|
|
1118
|
+
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1272
1119
|
return a.filename.localeCompare(b.filename);
|
|
1273
1120
|
}
|
|
1274
1121
|
function initializeStats(module) {
|
|
@@ -1278,7 +1125,7 @@ function initializeStats(module) {
|
|
|
1278
1125
|
filename: module.task.name,
|
|
1279
1126
|
projectName: module.project.name,
|
|
1280
1127
|
projectColor: module.project.color,
|
|
1281
|
-
tests: new Map(),
|
|
1128
|
+
tests: /* @__PURE__ */ new Map(),
|
|
1282
1129
|
typecheck: !!module.task.meta.typecheck
|
|
1283
1130
|
};
|
|
1284
1131
|
}
|
|
@@ -1292,14 +1139,14 @@ class DefaultReporter extends BaseReporter {
|
|
|
1292
1139
|
summary: true,
|
|
1293
1140
|
...options
|
|
1294
1141
|
};
|
|
1295
|
-
if (!this.isTTY)
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
if (this.options.summary) {
|
|
1299
|
-
this.summary = new SummaryReporter();
|
|
1300
|
-
}
|
|
1142
|
+
if (!this.isTTY) this.options.summary = false;
|
|
1143
|
+
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1301
1144
|
}
|
|
1302
1145
|
onTestRunStart(specifications) {
|
|
1146
|
+
if (this.isTTY) {
|
|
1147
|
+
if (this.renderSucceed === void 0) this.renderSucceed = !!this.renderSucceed;
|
|
1148
|
+
if (this.renderSucceed !== true) this.renderSucceed = specifications.length <= 1;
|
|
1149
|
+
}
|
|
1303
1150
|
this.summary?.onTestRunStart(specifications);
|
|
1304
1151
|
}
|
|
1305
1152
|
onTestModuleQueued(file) {
|
|
@@ -1329,16 +1176,6 @@ class DefaultReporter extends BaseReporter {
|
|
|
1329
1176
|
super.onInit(ctx);
|
|
1330
1177
|
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1331
1178
|
}
|
|
1332
|
-
onPathsCollected(paths = []) {
|
|
1333
|
-
if (this.isTTY) {
|
|
1334
|
-
if (this.renderSucceed === undefined) {
|
|
1335
|
-
this.renderSucceed = !!this.renderSucceed;
|
|
1336
|
-
}
|
|
1337
|
-
if (this.renderSucceed !== true) {
|
|
1338
|
-
this.renderSucceed = paths.length <= 1;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
1179
|
onTestRunEnd() {
|
|
1343
1180
|
this.summary?.onTestRunEnd();
|
|
1344
1181
|
}
|
|
@@ -1346,8 +1183,8 @@ class DefaultReporter extends BaseReporter {
|
|
|
1346
1183
|
|
|
1347
1184
|
class DotReporter extends BaseReporter {
|
|
1348
1185
|
renderer;
|
|
1349
|
-
tests = new Map();
|
|
1350
|
-
finishedTests = new Set();
|
|
1186
|
+
tests = /* @__PURE__ */ new Map();
|
|
1187
|
+
finishedTests = /* @__PURE__ */ new Set();
|
|
1351
1188
|
onInit(ctx) {
|
|
1352
1189
|
super.onInit(ctx);
|
|
1353
1190
|
if (this.isTTY) {
|
|
@@ -1359,9 +1196,7 @@ class DotReporter extends BaseReporter {
|
|
|
1359
1196
|
}
|
|
1360
1197
|
}
|
|
1361
1198
|
printTestModule(testModule) {
|
|
1362
|
-
if (!this.isTTY)
|
|
1363
|
-
super.printTestModule(testModule);
|
|
1364
|
-
}
|
|
1199
|
+
if (!this.isTTY) super.printTestModule(testModule);
|
|
1365
1200
|
}
|
|
1366
1201
|
onWatcherRerun(files, trigger) {
|
|
1367
1202
|
this.tests.clear();
|
|
@@ -1378,14 +1213,12 @@ class DotReporter extends BaseReporter {
|
|
|
1378
1213
|
super.onFinished(files, errors);
|
|
1379
1214
|
}
|
|
1380
1215
|
onTestModuleCollected(module) {
|
|
1381
|
-
for (const test of module.children.allTests())
|
|
1382
|
-
|
|
1383
|
-
|
|
1216
|
+
for (const test of module.children.allTests())
|
|
1217
|
+
// Dot reporter marks pending tests as running
|
|
1218
|
+
this.onTestCaseReady(test);
|
|
1384
1219
|
}
|
|
1385
1220
|
onTestCaseReady(test) {
|
|
1386
|
-
if (this.finishedTests.has(test.id))
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1221
|
+
if (this.finishedTests.has(test.id)) return;
|
|
1389
1222
|
this.tests.set(test.id, test.result().state || "run");
|
|
1390
1223
|
this.renderer?.schedule();
|
|
1391
1224
|
}
|
|
@@ -1397,23 +1230,16 @@ class DotReporter extends BaseReporter {
|
|
|
1397
1230
|
}
|
|
1398
1231
|
onTestModuleEnd(testModule) {
|
|
1399
1232
|
super.onTestModuleEnd(testModule);
|
|
1400
|
-
if (!this.isTTY)
|
|
1401
|
-
return;
|
|
1402
|
-
}
|
|
1233
|
+
if (!this.isTTY) return;
|
|
1403
1234
|
const columns = this.ctx.logger.getColumns();
|
|
1404
|
-
if (this.tests.size < columns)
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1235
|
+
if (this.tests.size < columns) return;
|
|
1407
1236
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
1408
|
-
if (finishedTests.length < columns)
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1237
|
+
if (finishedTests.length < columns) return;
|
|
1238
|
+
// Remove finished tests from state and render them in static output
|
|
1411
1239
|
const states = [];
|
|
1412
1240
|
let count = 0;
|
|
1413
1241
|
for (const [id, state] of finishedTests) {
|
|
1414
|
-
if (count++ >= columns)
|
|
1415
|
-
break;
|
|
1416
|
-
}
|
|
1242
|
+
if (count++ >= columns) break;
|
|
1417
1243
|
this.tests.delete(id);
|
|
1418
1244
|
states.push(state);
|
|
1419
1245
|
}
|
|
@@ -1424,6 +1250,7 @@ class DotReporter extends BaseReporter {
|
|
|
1424
1250
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
1425
1251
|
}
|
|
1426
1252
|
}
|
|
1253
|
+
// These are compared with reference equality in formatTests
|
|
1427
1254
|
const pass = {
|
|
1428
1255
|
char: "·",
|
|
1429
1256
|
color: c.green
|
|
@@ -1463,6 +1290,7 @@ function formatTests(states) {
|
|
|
1463
1290
|
continue;
|
|
1464
1291
|
}
|
|
1465
1292
|
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1293
|
+
// Start tracking new group
|
|
1466
1294
|
count = 1;
|
|
1467
1295
|
currentIcon = icon;
|
|
1468
1296
|
}
|
|
@@ -1470,6 +1298,7 @@ function formatTests(states) {
|
|
|
1470
1298
|
return output;
|
|
1471
1299
|
}
|
|
1472
1300
|
|
|
1301
|
+
// use Logger with custom Console to capture entire error printing
|
|
1473
1302
|
function capturePrintError(error, ctx, options) {
|
|
1474
1303
|
let output = "";
|
|
1475
1304
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
@@ -1499,12 +1328,13 @@ function printError(error, ctx, logger, options) {
|
|
|
1499
1328
|
screenshotPaths: options.screenshotPaths,
|
|
1500
1329
|
printProperties: options.verbose,
|
|
1501
1330
|
parseErrorStacktrace(error) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
}
|
|
1331
|
+
// browser stack trace needs to be processed differently,
|
|
1332
|
+
// so there is a separate method for that
|
|
1333
|
+
if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
|
|
1334
|
+
// node.js stack trace already has correct source map locations
|
|
1505
1335
|
return parseErrorStacktrace(error, {
|
|
1506
1336
|
frameFilter: project.config.onStackTrace,
|
|
1507
|
-
ignoreStackEntries: options.fullStack ? [] :
|
|
1337
|
+
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1508
1338
|
});
|
|
1509
1339
|
}
|
|
1510
1340
|
});
|
|
@@ -1513,12 +1343,10 @@ function printErrorInner(error, project, options) {
|
|
|
1513
1343
|
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1514
1344
|
const logger = options.logger;
|
|
1515
1345
|
let e = error;
|
|
1516
|
-
if (isPrimitive(e)) {
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1346
|
+
if (isPrimitive(e)) e = {
|
|
1347
|
+
message: String(error).split(/\n/g)[0],
|
|
1348
|
+
stack: String(error)
|
|
1349
|
+
};
|
|
1522
1350
|
if (!e) {
|
|
1523
1351
|
const error = new Error("unknown error");
|
|
1524
1352
|
e = {
|
|
@@ -1526,6 +1354,7 @@ function printErrorInner(error, project, options) {
|
|
|
1526
1354
|
stack: error.stack
|
|
1527
1355
|
};
|
|
1528
1356
|
}
|
|
1357
|
+
// Error may have occurred even before the configuration was resolved
|
|
1529
1358
|
if (!project) {
|
|
1530
1359
|
printErrorMessage(e, logger);
|
|
1531
1360
|
return;
|
|
@@ -1538,22 +1367,17 @@ function printErrorInner(error, project, options) {
|
|
|
1538
1367
|
return false;
|
|
1539
1368
|
}
|
|
1540
1369
|
});
|
|
1541
|
-
if (type)
|
|
1542
|
-
printErrorType(type, project.vitest);
|
|
1543
|
-
}
|
|
1370
|
+
if (type) printErrorType(type, project.vitest);
|
|
1544
1371
|
printErrorMessage(e, logger);
|
|
1545
1372
|
if (options.screenshotPaths?.length) {
|
|
1546
1373
|
const length = options.screenshotPaths.length;
|
|
1547
1374
|
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1548
1375
|
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1549
|
-
if (!e.diff)
|
|
1550
|
-
logger.error();
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
if (e.codeFrame) {
|
|
1554
|
-
logger.error(`${e.codeFrame}\n`);
|
|
1376
|
+
if (!e.diff) logger.error();
|
|
1555
1377
|
}
|
|
1378
|
+
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1556
1379
|
if ("__vitest_rollup_error__" in e) {
|
|
1380
|
+
// https://github.com/vitejs/vite/blob/95020ab49e12d143262859e095025cf02423c1d9/packages/vite/src/node/server/middlewares/error.ts#L25-L36
|
|
1557
1381
|
const err = e.__vitest_rollup_error__;
|
|
1558
1382
|
logger.error([
|
|
1559
1383
|
err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
|
|
@@ -1561,12 +1385,11 @@ function printErrorInner(error, project, options) {
|
|
|
1561
1385
|
err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
|
|
1562
1386
|
].filter(Boolean).join("\n"));
|
|
1563
1387
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
if (e.frame)
|
|
1568
|
-
|
|
1569
|
-
} else {
|
|
1388
|
+
// E.g. AssertionError from assert does not set showDiff but has both actual and expected properties
|
|
1389
|
+
if (e.diff) logger.error(`\n${e.diff}\n`);
|
|
1390
|
+
// if the error provide the frame
|
|
1391
|
+
if (e.frame) logger.error(c.yellow(e.frame));
|
|
1392
|
+
else {
|
|
1570
1393
|
const errorProperties = printProperties ? getErrorProperties(e) : {};
|
|
1571
1394
|
printStack(logger, project, stacks, nearest, errorProperties, (s) => {
|
|
1572
1395
|
if (showCodeFrame && s === nearest && nearest) {
|
|
@@ -1578,15 +1401,12 @@ function printErrorInner(error, project, options) {
|
|
|
1578
1401
|
const testPath = e.VITEST_TEST_PATH;
|
|
1579
1402
|
const testName = e.VITEST_TEST_NAME;
|
|
1580
1403
|
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (afterEnvTeardown) {
|
|
1588
|
-
logger.error(c.red("This error was caught after test environment was torn down. Make sure to cancel any running tasks before test finishes:" + "\n- cancel timeouts using clearTimeout and clearInterval" + "\n- wait for promises to resolve using the await keyword"));
|
|
1589
|
-
}
|
|
1404
|
+
// testName has testPath inside
|
|
1405
|
+
if (testPath) logger.error(c.red(`This error originated in "${c.bold(relative(project.config.root, testPath))}" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.`));
|
|
1406
|
+
if (testName) logger.error(c.red(`The latest test that might've caused the error is "${c.bold(testName)}". It might mean one of the following:
|
|
1407
|
+
- The error was thrown, while Vitest was running this test.
|
|
1408
|
+
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1409
|
+
if (afterEnvTeardown) logger.error(c.red("This error was caught after test environment was torn down. Make sure to cancel any running tasks before test finishes:\n- cancel timeouts using clearTimeout and clearInterval\n- wait for promises to resolve using the await keyword"));
|
|
1590
1410
|
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1591
1411
|
e.cause.name = `Caused by: ${e.cause.name}`;
|
|
1592
1412
|
printErrorInner(e.cause, project, {
|
|
@@ -1595,18 +1415,15 @@ function printErrorInner(error, project, options) {
|
|
|
1595
1415
|
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1596
1416
|
});
|
|
1597
1417
|
}
|
|
1598
|
-
handleImportOutsideModuleError(e.stack ||
|
|
1418
|
+
handleImportOutsideModuleError(e.stack || "", logger);
|
|
1599
1419
|
return { nearest };
|
|
1600
1420
|
}
|
|
1601
1421
|
function printErrorType(type, ctx) {
|
|
1602
1422
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
1603
1423
|
}
|
|
1604
1424
|
const skipErrorProperties = new Set([
|
|
1605
|
-
"nameStr",
|
|
1606
|
-
"stack",
|
|
1607
1425
|
"cause",
|
|
1608
1426
|
"stacks",
|
|
1609
|
-
"stackStr",
|
|
1610
1427
|
"type",
|
|
1611
1428
|
"showDiff",
|
|
1612
1429
|
"ok",
|
|
@@ -1625,41 +1442,35 @@ const skipErrorProperties = new Set([
|
|
|
1625
1442
|
"VITEST_TEST_NAME",
|
|
1626
1443
|
"VITEST_TEST_PATH",
|
|
1627
1444
|
"VITEST_AFTER_ENV_TEARDOWN",
|
|
1445
|
+
"__vitest_rollup_error__",
|
|
1628
1446
|
...Object.getOwnPropertyNames(Error.prototype),
|
|
1629
1447
|
...Object.getOwnPropertyNames(Object.prototype)
|
|
1630
1448
|
]);
|
|
1631
1449
|
function getErrorProperties(e) {
|
|
1632
1450
|
const errorObject = Object.create(null);
|
|
1633
|
-
if (e.name === "AssertionError")
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
errorObject[key] = e[key];
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1451
|
+
if (e.name === "AssertionError") return errorObject;
|
|
1452
|
+
for (const key of Object.getOwnPropertyNames(e))
|
|
1453
|
+
// print the original stack if it was ever changed manually by the user
|
|
1454
|
+
if (key === "stack" && e[key] != null && typeof e[key] !== "string") errorObject[key] = e[key];
|
|
1455
|
+
else if (key !== "stack" && !skipErrorProperties.has(key)) errorObject[key] = e[key];
|
|
1641
1456
|
return errorObject;
|
|
1642
1457
|
}
|
|
1643
1458
|
const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
|
|
1644
1459
|
function handleImportOutsideModuleError(stack, logger) {
|
|
1645
|
-
if (!esmErrors.some((e) => stack.includes(e)))
|
|
1646
|
-
return;
|
|
1647
|
-
}
|
|
1460
|
+
if (!esmErrors.some((e) => stack.includes(e))) return;
|
|
1648
1461
|
const path = normalize(stack.split("\n")[0].trim());
|
|
1649
1462
|
let name = path.split("/node_modules/").pop() || "";
|
|
1650
|
-
if (name?.startsWith("@"))
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
}
|
|
1655
|
-
if (name) {
|
|
1656
|
-
printModuleWarningForPackage(logger, path, name);
|
|
1657
|
-
} else {
|
|
1658
|
-
printModuleWarningForSourceCode(logger, path);
|
|
1659
|
-
}
|
|
1463
|
+
if (name?.startsWith("@")) name = name.split("/").slice(0, 2).join("/");
|
|
1464
|
+
else name = name.split("/")[0];
|
|
1465
|
+
if (name) printModuleWarningForPackage(logger, path, name);
|
|
1466
|
+
else printModuleWarningForSourceCode(logger, path);
|
|
1660
1467
|
}
|
|
1661
1468
|
function printModuleWarningForPackage(logger, path, name) {
|
|
1662
|
-
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package.
|
|
1469
|
+
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package ${c.bold(`"${name}"`)} asking them to ship the file in .mjs extension or add "type": "module" in their package.json.
|
|
1470
|
+
|
|
1471
|
+
As a temporary workaround you can try to inline the package by updating your config:
|
|
1472
|
+
|
|
1473
|
+
` + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
|
|
1663
1474
|
test: {
|
|
1664
1475
|
server: {
|
|
1665
1476
|
deps: {
|
|
@@ -1672,19 +1483,18 @@ function printModuleWarningForPackage(logger, path, name) {
|
|
|
1672
1483
|
}\n`)));
|
|
1673
1484
|
}
|
|
1674
1485
|
function printModuleWarningForSourceCode(logger, path) {
|
|
1675
|
-
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package.
|
|
1486
|
+
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. To fix this issue, change the file extension to .mjs or add "type": "module" in your package.json.`));
|
|
1676
1487
|
}
|
|
1677
1488
|
function printErrorMessage(error, logger) {
|
|
1678
|
-
const errorName = error.name ||
|
|
1489
|
+
const errorName = error.name || "Unknown Error";
|
|
1679
1490
|
if (!error.message) {
|
|
1680
1491
|
logger.error(error);
|
|
1681
1492
|
return;
|
|
1682
1493
|
}
|
|
1683
|
-
if (error.message.length > 5e3)
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
}
|
|
1494
|
+
if (error.message.length > 5e3)
|
|
1495
|
+
// Protect against infinite stack trace in tinyrainbow
|
|
1496
|
+
logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
|
|
1497
|
+
else logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
|
|
1688
1498
|
}
|
|
1689
1499
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1690
1500
|
for (const frame of stack) {
|
|
@@ -1693,9 +1503,7 @@ function printStack(logger, project, stack, highlight, errorProperties, onStack)
|
|
|
1693
1503
|
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1694
1504
|
onStack?.(frame);
|
|
1695
1505
|
}
|
|
1696
|
-
if (stack.length)
|
|
1697
|
-
logger.error();
|
|
1698
|
-
}
|
|
1506
|
+
if (stack.length) logger.error();
|
|
1699
1507
|
if (hasProperties(errorProperties)) {
|
|
1700
1508
|
logger.error(c.red(c.dim(divider())));
|
|
1701
1509
|
const propertiesString = inspect(errorProperties);
|
|
@@ -1703,9 +1511,8 @@ function printStack(logger, project, stack, highlight, errorProperties, onStack)
|
|
|
1703
1511
|
}
|
|
1704
1512
|
}
|
|
1705
1513
|
function hasProperties(obj) {
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1514
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
1515
|
+
for (const _key in obj) return true;
|
|
1709
1516
|
return false;
|
|
1710
1517
|
}
|
|
1711
1518
|
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
@@ -1720,15 +1527,13 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1720
1527
|
count += lines[i].length + nl;
|
|
1721
1528
|
if (count >= start) {
|
|
1722
1529
|
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1723
|
-
if (j < 0 || j >= lines.length)
|
|
1724
|
-
continue;
|
|
1725
|
-
}
|
|
1530
|
+
if (j < 0 || j >= lines.length) continue;
|
|
1726
1531
|
const lineLength = lines[j].length;
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
}
|
|
1532
|
+
// too long, maybe it's a minified file, skip for codeframe
|
|
1533
|
+
if (stripVTControlCharacters(lines[j]).length > 200) return "";
|
|
1730
1534
|
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
|
|
1731
1535
|
if (j === i) {
|
|
1536
|
+
// push underline
|
|
1732
1537
|
const pad = start - (count - lineLength) + (nl - 1);
|
|
1733
1538
|
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1734
1539
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
@@ -1743,9 +1548,7 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1743
1548
|
break;
|
|
1744
1549
|
}
|
|
1745
1550
|
}
|
|
1746
|
-
if (indent)
|
|
1747
|
-
res = res.map((line) => " ".repeat(indent) + line);
|
|
1748
|
-
}
|
|
1551
|
+
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1749
1552
|
return res.join("\n");
|
|
1750
1553
|
}
|
|
1751
1554
|
function lineNo(no = "") {
|
|
@@ -1753,50 +1556,64 @@ function lineNo(no = "") {
|
|
|
1753
1556
|
}
|
|
1754
1557
|
|
|
1755
1558
|
class GithubActionsReporter {
|
|
1756
|
-
ctx =
|
|
1559
|
+
ctx = void 0;
|
|
1560
|
+
options;
|
|
1561
|
+
constructor(options = {}) {
|
|
1562
|
+
this.options = options;
|
|
1563
|
+
}
|
|
1757
1564
|
onInit(ctx) {
|
|
1758
1565
|
this.ctx = ctx;
|
|
1759
1566
|
}
|
|
1567
|
+
onTestCaseAnnotate(testCase, annotation) {
|
|
1568
|
+
if (!annotation.location) return;
|
|
1569
|
+
const type = getTitle(annotation.type);
|
|
1570
|
+
const formatted = formatMessage({
|
|
1571
|
+
command: getType(annotation.type),
|
|
1572
|
+
properties: {
|
|
1573
|
+
file: annotation.location.file,
|
|
1574
|
+
line: String(annotation.location.line),
|
|
1575
|
+
column: String(annotation.location.column),
|
|
1576
|
+
...type && { title: type }
|
|
1577
|
+
},
|
|
1578
|
+
message: stripVTControlCharacters(annotation.message)
|
|
1579
|
+
});
|
|
1580
|
+
this.ctx.logger.log(`\n${formatted}`);
|
|
1581
|
+
}
|
|
1760
1582
|
onFinished(files = [], errors = []) {
|
|
1583
|
+
// collect all errors and associate them with projects
|
|
1761
1584
|
const projectErrors = new Array();
|
|
1762
|
-
for (const error of errors) {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1585
|
+
for (const error of errors) projectErrors.push({
|
|
1586
|
+
project: this.ctx.getRootProject(),
|
|
1587
|
+
title: "Unhandled error",
|
|
1588
|
+
error
|
|
1589
|
+
});
|
|
1769
1590
|
for (const file of files) {
|
|
1770
1591
|
const tasks = getTasks(file);
|
|
1771
1592
|
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1772
1593
|
for (const task of tasks) {
|
|
1773
|
-
if (task.result?.state !== "fail")
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1594
|
+
if (task.result?.state !== "fail") continue;
|
|
1776
1595
|
const title = getFullName(task, " > ");
|
|
1777
|
-
for (const error of task.result?.errors ?? []) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1596
|
+
for (const error of task.result?.errors ?? []) projectErrors.push({
|
|
1597
|
+
project,
|
|
1598
|
+
title,
|
|
1599
|
+
error,
|
|
1600
|
+
file
|
|
1601
|
+
});
|
|
1785
1602
|
}
|
|
1786
1603
|
}
|
|
1604
|
+
const onWritePath = this.options.onWritePath ?? defaultOnWritePath;
|
|
1605
|
+
// format errors via `printError`
|
|
1787
1606
|
for (const { project, title, error, file } of projectErrors) {
|
|
1788
1607
|
const result = capturePrintError(error, this.ctx, {
|
|
1789
1608
|
project,
|
|
1790
1609
|
task: file
|
|
1791
1610
|
});
|
|
1792
1611
|
const stack = result?.nearest;
|
|
1793
|
-
if (!stack)
|
|
1794
|
-
continue;
|
|
1795
|
-
}
|
|
1612
|
+
if (!stack) continue;
|
|
1796
1613
|
const formatted = formatMessage({
|
|
1797
1614
|
command: "error",
|
|
1798
1615
|
properties: {
|
|
1799
|
-
file: stack.file,
|
|
1616
|
+
file: onWritePath(stack.file),
|
|
1800
1617
|
title,
|
|
1801
1618
|
line: String(stack.line),
|
|
1802
1619
|
column: String(stack.column)
|
|
@@ -1807,6 +1624,25 @@ class GithubActionsReporter {
|
|
|
1807
1624
|
}
|
|
1808
1625
|
}
|
|
1809
1626
|
}
|
|
1627
|
+
const BUILT_IN_TYPES = [
|
|
1628
|
+
"notice",
|
|
1629
|
+
"error",
|
|
1630
|
+
"warning"
|
|
1631
|
+
];
|
|
1632
|
+
function getTitle(type) {
|
|
1633
|
+
if (BUILT_IN_TYPES.includes(type)) return void 0;
|
|
1634
|
+
return type;
|
|
1635
|
+
}
|
|
1636
|
+
function getType(type) {
|
|
1637
|
+
if (BUILT_IN_TYPES.includes(type)) return type;
|
|
1638
|
+
return "notice";
|
|
1639
|
+
}
|
|
1640
|
+
function defaultOnWritePath(path) {
|
|
1641
|
+
return path;
|
|
1642
|
+
}
|
|
1643
|
+
// workflow command formatting based on
|
|
1644
|
+
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
|
|
1645
|
+
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1810
1646
|
function formatMessage({ command, properties, message }) {
|
|
1811
1647
|
let result = `::${command}`;
|
|
1812
1648
|
Object.entries(properties).forEach(([k, v], i) => {
|
|
@@ -1871,9 +1707,7 @@ class JsonReporter {
|
|
|
1871
1707
|
for (const file of files) {
|
|
1872
1708
|
const tests = getTests([file]);
|
|
1873
1709
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1874
|
-
if (startTime === Number.POSITIVE_INFINITY)
|
|
1875
|
-
startTime = this.start;
|
|
1876
|
-
}
|
|
1710
|
+
if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
|
|
1877
1711
|
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime);
|
|
1878
1712
|
const assertionResults = tests.map((t) => {
|
|
1879
1713
|
const ancestorTitles = [];
|
|
@@ -1894,9 +1728,7 @@ class JsonReporter {
|
|
|
1894
1728
|
meta: t.meta
|
|
1895
1729
|
};
|
|
1896
1730
|
});
|
|
1897
|
-
if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued"))
|
|
1898
|
-
this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report." + "This is likely an internal bug in Vitest." + "Please report it to https://github.com/vitest-dev/vitest/issues");
|
|
1899
|
-
}
|
|
1731
|
+
if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued")) this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report.This is likely an internal bug in Vitest.Please report it to https://github.com/vitest-dev/vitest/issues");
|
|
1900
1732
|
const hasFailedTests = tests.some((t) => t.result?.state === "fail");
|
|
1901
1733
|
testResults.push({
|
|
1902
1734
|
assertionResults,
|
|
@@ -1938,14 +1770,10 @@ class JsonReporter {
|
|
|
1938
1770
|
if (outputFile) {
|
|
1939
1771
|
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1940
1772
|
const outputDirectory = dirname(reportFile);
|
|
1941
|
-
if (!existsSync(outputDirectory)) {
|
|
1942
|
-
await promises.mkdir(outputDirectory, { recursive: true });
|
|
1943
|
-
}
|
|
1773
|
+
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1944
1774
|
await promises.writeFile(reportFile, report, "utf-8");
|
|
1945
1775
|
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1946
|
-
} else
|
|
1947
|
-
this.ctx.logger.log(report);
|
|
1948
|
-
}
|
|
1776
|
+
} else this.ctx.logger.log(report);
|
|
1949
1777
|
}
|
|
1950
1778
|
}
|
|
1951
1779
|
|
|
@@ -1967,25 +1795,24 @@ class IndentedLogger {
|
|
|
1967
1795
|
|
|
1968
1796
|
function flattenTasks$1(task, baseName = "") {
|
|
1969
1797
|
const base = baseName ? `${baseName} > ` : "";
|
|
1970
|
-
if (task.type === "suite") {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
name: `${base}${task.name}`
|
|
1976
|
-
}];
|
|
1977
|
-
}
|
|
1798
|
+
if (task.type === "suite") return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
|
|
1799
|
+
else return [{
|
|
1800
|
+
...task,
|
|
1801
|
+
name: `${base}${task.name}`
|
|
1802
|
+
}];
|
|
1978
1803
|
}
|
|
1804
|
+
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1979
1805
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1980
1806
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1981
1807
|
value = String(value || "").replace(regex, "");
|
|
1982
1808
|
{
|
|
1809
|
+
// remove everything discouraged by XML 1.0 specifications
|
|
1983
1810
|
regex = new RegExp(
|
|
1984
1811
|
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1985
|
-
"([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\
|
|
1812
|
+
"([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDFFE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uDFFF])|\\uD97F[\\uDFFE\\uDFFF]|\\uD9BF[\\uDFFE\\uDFFF]|\\uD9FF[\\uDFFE\\uDFFF]|\\uDA3F[\\uDFFE\\uDFFF]|\\uDA7F[\\uDFFE\\uDFFF]|\\uDABF[\\uDFFE\\uDFFF]|(?:\\uDAFF[\\uDFFE\\uDFFF])|\\uDB3F[\\uDFFE\\uDFFF]|\\uDB7F[\\uDFFE\\uDFFF]|(?:\\uDBBF[\\uDFFE\\uDFFF])|\\uDBFF[\\uDFFE\\uDFFF](?:[\\0-\\t\\v\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))",
|
|
1986
1813
|
"g"
|
|
1987
1814
|
/* eslint-enable */
|
|
1988
|
-
);
|
|
1815
|
+
);
|
|
1989
1816
|
value = value.replace(regex, "");
|
|
1990
1817
|
}
|
|
1991
1818
|
return value;
|
|
@@ -2008,7 +1835,7 @@ class JUnitReporter {
|
|
|
2008
1835
|
reportFile;
|
|
2009
1836
|
baseLog;
|
|
2010
1837
|
logger;
|
|
2011
|
-
_timeStart = new Date();
|
|
1838
|
+
_timeStart = /* @__PURE__ */ new Date();
|
|
2012
1839
|
fileFd;
|
|
2013
1840
|
options;
|
|
2014
1841
|
constructor(options) {
|
|
@@ -2021,30 +1848,22 @@ class JUnitReporter {
|
|
|
2021
1848
|
if (outputFile) {
|
|
2022
1849
|
this.reportFile = resolve(this.ctx.config.root, outputFile);
|
|
2023
1850
|
const outputDirectory = dirname(this.reportFile);
|
|
2024
|
-
if (!existsSync(outputDirectory)) {
|
|
2025
|
-
await promises.mkdir(outputDirectory, { recursive: true });
|
|
2026
|
-
}
|
|
1851
|
+
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
2027
1852
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
2028
1853
|
this.fileFd = fileFd;
|
|
2029
1854
|
this.baseLog = async (text) => {
|
|
2030
|
-
if (!this.fileFd)
|
|
2031
|
-
this.fileFd = await promises.open(this.reportFile, "w+");
|
|
2032
|
-
}
|
|
1855
|
+
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
2033
1856
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
2034
1857
|
};
|
|
2035
|
-
} else
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2038
|
-
this._timeStart = new Date();
|
|
1858
|
+
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1859
|
+
this._timeStart = /* @__PURE__ */ new Date();
|
|
2039
1860
|
this.logger = new IndentedLogger(this.baseLog);
|
|
2040
1861
|
}
|
|
2041
1862
|
async writeElement(name, attrs, children) {
|
|
2042
1863
|
const pairs = [];
|
|
2043
1864
|
for (const key in attrs) {
|
|
2044
1865
|
const attr = attrs[key];
|
|
2045
|
-
if (attr ===
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
1866
|
+
if (attr === void 0) continue;
|
|
2048
1867
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
2049
1868
|
}
|
|
2050
1869
|
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
|
|
@@ -2054,18 +1873,12 @@ class JUnitReporter {
|
|
|
2054
1873
|
await this.logger.log(`</${name}>`);
|
|
2055
1874
|
}
|
|
2056
1875
|
async writeLogs(task, type) {
|
|
2057
|
-
if (task.logs == null || task.logs.length === 0)
|
|
2058
|
-
return;
|
|
2059
|
-
}
|
|
1876
|
+
if (task.logs == null || task.logs.length === 0) return;
|
|
2060
1877
|
const logType = type === "err" ? "stderr" : "stdout";
|
|
2061
1878
|
const logs = task.logs.filter((log) => log.type === logType);
|
|
2062
|
-
if (logs.length === 0)
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
1879
|
+
if (logs.length === 0) return;
|
|
2065
1880
|
await this.writeElement(`system-${type}`, {}, async () => {
|
|
2066
|
-
for (const log of logs)
|
|
2067
|
-
await this.baseLog(escapeXML(log.content));
|
|
2068
|
-
}
|
|
1881
|
+
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
2069
1882
|
});
|
|
2070
1883
|
}
|
|
2071
1884
|
async writeTasks(tasks, filename) {
|
|
@@ -2075,16 +1888,12 @@ class JUnitReporter {
|
|
|
2075
1888
|
filename: task.file.name,
|
|
2076
1889
|
filepath: task.file.filepath
|
|
2077
1890
|
};
|
|
2078
|
-
if (typeof this.options.classnameTemplate === "function")
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
|
|
2082
|
-
} else if (typeof this.options.classname === "string") {
|
|
2083
|
-
classname = this.options.classname;
|
|
2084
|
-
}
|
|
1891
|
+
if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
|
|
1892
|
+
else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
|
|
1893
|
+
else if (typeof this.options.classname === "string") classname = this.options.classname;
|
|
2085
1894
|
await this.writeElement("testcase", {
|
|
2086
1895
|
classname,
|
|
2087
|
-
file: this.options.addFileAttribute ? filename :
|
|
1896
|
+
file: this.options.addFileAttribute ? filename : void 0,
|
|
2088
1897
|
name: task.name,
|
|
2089
1898
|
time: getDuration(task)
|
|
2090
1899
|
}, async () => {
|
|
@@ -2092,26 +1901,30 @@ class JUnitReporter {
|
|
|
2092
1901
|
await this.writeLogs(task, "out");
|
|
2093
1902
|
await this.writeLogs(task, "err");
|
|
2094
1903
|
}
|
|
2095
|
-
if (task.mode === "skip" || task.mode === "todo")
|
|
2096
|
-
|
|
1904
|
+
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1905
|
+
if (task.type === "test" && task.annotations.length) {
|
|
1906
|
+
await this.logger.log("<properties>");
|
|
1907
|
+
this.logger.indent();
|
|
1908
|
+
for (const annotation of task.annotations) {
|
|
1909
|
+
await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
|
|
1910
|
+
await this.logger.log("</property>");
|
|
1911
|
+
}
|
|
1912
|
+
this.logger.unindent();
|
|
1913
|
+
await this.logger.log("</properties>");
|
|
2097
1914
|
}
|
|
2098
1915
|
if (task.result?.state === "fail") {
|
|
2099
1916
|
const errors = task.result.errors || [];
|
|
2100
|
-
for (const error of errors) {
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
const result = capturePrintError(error, this.ctx, {
|
|
2109
|
-
project: this.ctx.getProjectByName(task.file.projectName || ""),
|
|
2110
|
-
task
|
|
2111
|
-
});
|
|
2112
|
-
await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
|
|
1917
|
+
for (const error of errors) await this.writeElement("failure", {
|
|
1918
|
+
message: error?.message,
|
|
1919
|
+
type: error?.name
|
|
1920
|
+
}, async () => {
|
|
1921
|
+
if (!error) return;
|
|
1922
|
+
const result = capturePrintError(error, this.ctx, {
|
|
1923
|
+
project: this.ctx.getProjectByName(task.file.projectName || ""),
|
|
1924
|
+
task
|
|
2113
1925
|
});
|
|
2114
|
-
|
|
1926
|
+
await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
|
|
1927
|
+
});
|
|
2115
1928
|
}
|
|
2116
1929
|
});
|
|
2117
1930
|
}
|
|
@@ -2131,13 +1944,13 @@ class JUnitReporter {
|
|
|
2131
1944
|
failures: 0,
|
|
2132
1945
|
skipped: 0
|
|
2133
1946
|
});
|
|
1947
|
+
// inject failed suites to surface errors during beforeAll/afterAll
|
|
2134
1948
|
const suites = getSuites(file);
|
|
2135
|
-
for (const suite of suites) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
stats.failures += 1;
|
|
2139
|
-
}
|
|
1949
|
+
for (const suite of suites) if (suite.result?.errors) {
|
|
1950
|
+
tasks.push(suite);
|
|
1951
|
+
stats.failures += 1;
|
|
2140
1952
|
}
|
|
1953
|
+
// If there are no tests, but the file failed to load, we still want to report it as a failure
|
|
2141
1954
|
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
2142
1955
|
stats.failures = 1;
|
|
2143
1956
|
tasks.push({
|
|
@@ -2150,7 +1963,8 @@ class JUnitReporter {
|
|
|
2150
1963
|
timeout: 0,
|
|
2151
1964
|
context: null,
|
|
2152
1965
|
suite: null,
|
|
2153
|
-
file: null
|
|
1966
|
+
file: null,
|
|
1967
|
+
annotations: []
|
|
2154
1968
|
});
|
|
2155
1969
|
}
|
|
2156
1970
|
return {
|
|
@@ -2179,7 +1993,7 @@ class JUnitReporter {
|
|
|
2179
1993
|
const filename = relative(this.ctx.config.root, file.filepath);
|
|
2180
1994
|
await this.writeElement("testsuite", {
|
|
2181
1995
|
name: filename,
|
|
2182
|
-
timestamp: new Date().toISOString(),
|
|
1996
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2183
1997
|
hostname: hostname(),
|
|
2184
1998
|
tests: file.tasks.length,
|
|
2185
1999
|
failures: file.stats.failures,
|
|
@@ -2191,11 +2005,9 @@ class JUnitReporter {
|
|
|
2191
2005
|
});
|
|
2192
2006
|
}
|
|
2193
2007
|
});
|
|
2194
|
-
if (this.reportFile) {
|
|
2195
|
-
this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
2196
|
-
}
|
|
2008
|
+
if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
2197
2009
|
await this.fileFd?.close();
|
|
2198
|
-
this.fileFd =
|
|
2010
|
+
this.fileFd = void 0;
|
|
2199
2011
|
}
|
|
2200
2012
|
}
|
|
2201
2013
|
|
|
@@ -2213,23 +2025,18 @@ class TapReporter {
|
|
|
2213
2025
|
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2214
2026
|
}
|
|
2215
2027
|
static getComment(task) {
|
|
2216
|
-
if (task.mode === "skip")
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
} else if (task.result?.duration != null) {
|
|
2221
|
-
return ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2222
|
-
} else {
|
|
2223
|
-
return "";
|
|
2224
|
-
}
|
|
2028
|
+
if (task.mode === "skip") return " # SKIP";
|
|
2029
|
+
else if (task.mode === "todo") return " # TODO";
|
|
2030
|
+
else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2031
|
+
else return "";
|
|
2225
2032
|
}
|
|
2226
2033
|
logErrorDetails(error, stack) {
|
|
2227
|
-
const errorName = error.name ||
|
|
2034
|
+
const errorName = error.name || "Unknown Error";
|
|
2228
2035
|
this.logger.log(`name: ${yamlString(String(errorName))}`);
|
|
2229
2036
|
this.logger.log(`message: ${yamlString(String(error.message))}`);
|
|
2230
|
-
if (stack)
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2037
|
+
if (stack)
|
|
2038
|
+
// For compatibility with tap-mocha-reporter
|
|
2039
|
+
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2233
2040
|
}
|
|
2234
2041
|
logTasks(tasks) {
|
|
2235
2042
|
this.logger.log(`1..${tasks.length}`);
|
|
@@ -2246,6 +2053,13 @@ class TapReporter {
|
|
|
2246
2053
|
} else {
|
|
2247
2054
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2248
2055
|
const project = this.ctx.getProjectByName(task.file.projectName || "");
|
|
2056
|
+
if (task.type === "test" && task.annotations) {
|
|
2057
|
+
this.logger.indent();
|
|
2058
|
+
task.annotations.forEach(({ type, message }) => {
|
|
2059
|
+
this.logger.log(`# ${type}: ${message}`);
|
|
2060
|
+
});
|
|
2061
|
+
this.logger.unindent();
|
|
2062
|
+
}
|
|
2249
2063
|
if (task.result?.state === "fail" && task.result.errors) {
|
|
2250
2064
|
this.logger.indent();
|
|
2251
2065
|
task.result.errors.forEach((error) => {
|
|
@@ -2256,9 +2070,7 @@ class TapReporter {
|
|
|
2256
2070
|
this.logger.indent();
|
|
2257
2071
|
this.logErrorDetails(error);
|
|
2258
2072
|
this.logger.unindent();
|
|
2259
|
-
if (stack) {
|
|
2260
|
-
this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2261
|
-
}
|
|
2073
|
+
if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2262
2074
|
if (error.showDiff) {
|
|
2263
2075
|
this.logger.log(`actual: ${yamlString(error.actual)}`);
|
|
2264
2076
|
this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
@@ -2278,14 +2090,11 @@ class TapReporter {
|
|
|
2278
2090
|
|
|
2279
2091
|
function flattenTasks(task, baseName = "") {
|
|
2280
2092
|
const base = baseName ? `${baseName} > ` : "";
|
|
2281
|
-
if (task.type === "suite" && task.tasks.length > 0) {
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
name: `${base}${task.name}`
|
|
2287
|
-
}];
|
|
2288
|
-
}
|
|
2093
|
+
if (task.type === "suite" && task.tasks.length > 0) return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
|
|
2094
|
+
else return [{
|
|
2095
|
+
...task,
|
|
2096
|
+
name: `${base}${task.name}`
|
|
2097
|
+
}];
|
|
2289
2098
|
}
|
|
2290
2099
|
class TapFlatReporter extends TapReporter {
|
|
2291
2100
|
onInit(ctx) {
|
|
@@ -2302,35 +2111,32 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2302
2111
|
verbose = true;
|
|
2303
2112
|
renderSucceed = true;
|
|
2304
2113
|
printTestModule(module) {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2114
|
+
// still print the test module in TTY,
|
|
2115
|
+
// but don't print it in the CLI because we
|
|
2116
|
+
// print all the tests when they finish
|
|
2117
|
+
// instead of printing them when the test file finishes
|
|
2118
|
+
if (this.isTTY) return super.printTestModule(module);
|
|
2308
2119
|
}
|
|
2309
2120
|
onTestCaseResult(test) {
|
|
2310
2121
|
super.onTestCaseResult(test);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2122
|
+
// don't print tests in TTY as they go, only print them
|
|
2123
|
+
// in the CLI when they finish
|
|
2124
|
+
if (this.isTTY) return;
|
|
2314
2125
|
const testResult = test.result();
|
|
2315
|
-
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped")
|
|
2316
|
-
return;
|
|
2317
|
-
}
|
|
2126
|
+
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2318
2127
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2319
|
-
if (test.project.name)
|
|
2320
|
-
title += formatProjectName(test.project);
|
|
2321
|
-
}
|
|
2128
|
+
if (test.project.name) title += formatProjectName(test.project);
|
|
2322
2129
|
title += getFullName(test.task, c.dim(" > "));
|
|
2323
2130
|
title += this.getDurationPrefix(test.task);
|
|
2324
2131
|
const diagnostic = test.diagnostic();
|
|
2325
|
-
if (diagnostic?.heap != null) {
|
|
2326
|
-
|
|
2327
|
-
}
|
|
2328
|
-
if (testResult.state === "skipped" && testResult.note) {
|
|
2329
|
-
title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2330
|
-
}
|
|
2132
|
+
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2133
|
+
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2331
2134
|
this.log(title);
|
|
2332
|
-
if (testResult.state === "failed") {
|
|
2333
|
-
|
|
2135
|
+
if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
|
|
2136
|
+
if (test.annotations().length) {
|
|
2137
|
+
this.log();
|
|
2138
|
+
this.printAnnotations(test, "log", 3);
|
|
2139
|
+
this.log();
|
|
2334
2140
|
}
|
|
2335
2141
|
}
|
|
2336
2142
|
printTestSuite(testSuite) {
|
|
@@ -2346,13 +2152,12 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2346
2152
|
return " ".repeat(getIndentation(test));
|
|
2347
2153
|
}
|
|
2348
2154
|
formatShortError() {
|
|
2155
|
+
// Short errors are not shown in tree-view
|
|
2349
2156
|
return "";
|
|
2350
2157
|
}
|
|
2351
2158
|
}
|
|
2352
2159
|
function getIndentation(suite, level = 1) {
|
|
2353
|
-
if (suite.suite && !("filepath" in suite.suite))
|
|
2354
|
-
return getIndentation(suite.suite, level + 1);
|
|
2355
|
-
}
|
|
2160
|
+
if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
|
|
2356
2161
|
return level;
|
|
2357
2162
|
}
|
|
2358
2163
|
|