vitest 4.0.0-beta.5 → 4.0.0-beta.7
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 +1 -1
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +2 -2
- package/dist/chunks/{base.DMfOuRWD.js → base.BXI97p6t.js} +7 -16
- package/dist/chunks/{benchmark.CtuRzf-i.js → benchmark.UW6Ezvxy.js} +4 -9
- package/dist/chunks/{browser.d.Cawq_X_N.d.ts → browser.d.DOMmqJQx.d.ts} +1 -1
- package/dist/chunks/{cac.CKnbxhn2.js → cac.Dsn7ixFt.js} +38 -113
- package/dist/chunks/{cli-api.COn58yrl.js → cli-api.DfGJyldU.js} +829 -1232
- package/dist/chunks/{config.d.CKNVOKm0.d.ts → config.d._GBBbReY.d.ts} +1 -0
- package/dist/chunks/{console.Duv2dVIC.js → console.B0quX7yH.js} +32 -68
- package/dist/chunks/{coverage.B6cReEn1.js → coverage.Dvxug1RM.js} +210 -579
- package/dist/chunks/{creator.DUVZ6rfm.js → creator.KEg6n5IC.js} +28 -74
- package/dist/chunks/{date.Bq6ZW5rf.js → date.-jtEtIeV.js} +6 -17
- package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
- package/dist/chunks/{globals.CJQ63oO0.js → globals.lgsmH00r.js} +5 -5
- package/dist/chunks/{index.QZr3S3vQ.js → index.AR8aAkCC.js} +2 -2
- package/dist/chunks/{index.DQhAfQQU.js → index.C3EbxYwt.js} +276 -607
- package/dist/chunks/{index.oWRWx-nj.js → index.CsFXYRkW.js} +17 -36
- package/dist/chunks/{index.DgN0Zk9a.js → index.D2B6d2vv.js} +14 -24
- package/dist/chunks/{index.BRtIe7r8.js → index.DfviD7lX.js} +55 -110
- package/dist/chunks/{inspector.C914Efll.js → inspector.CvQD-Nie.js} +10 -25
- package/dist/chunks/{moduleRunner.d.mmOmOGrW.d.ts → moduleRunner.d.CX4DuqOx.d.ts} +2 -2
- package/dist/chunks/{node.4JV5OXkt.js → node.BOqcT2jW.js} +1 -1
- package/dist/chunks/{plugin.d.CvOlgjxK.d.ts → plugin.d.vcD4xbMS.d.ts} +1 -1
- package/dist/chunks/{reporters.d.CYE9sT5z.d.ts → reporters.d.BC86JJdB.d.ts} +799 -758
- package/dist/chunks/{resolver.D5bG4zy5.js → resolver.Bx6lE0iq.js} +21 -64
- package/dist/chunks/{rpc.DGoW_Vl-.js → rpc.RpPylpp0.js} +7 -21
- package/dist/chunks/{runBaseTests.B3KcKqlF.js → runBaseTests.D6sfuWBM.js} +25 -54
- package/dist/chunks/{setup-common.lgPs-bYv.js → setup-common.hLGRxhC8.js} +9 -22
- package/dist/chunks/{startModuleRunner.C8FtT_BY.js → startModuleRunner.C8TW8zTN.js} +83 -205
- package/dist/chunks/{typechecker.BgoW4nTA.js → typechecker.DSo_maXz.js} +97 -209
- package/dist/chunks/{utils.CcGm2cd1.js → utils.C2YI6McM.js} +4 -13
- package/dist/chunks/{utils.B9FY3b73.js → utils.C7__0Iv5.js} +5 -14
- package/dist/chunks/{vi.DGAfBY4R.js → vi.BfdOiD4j.js} +110 -267
- package/dist/chunks/{vm.BKfKvaKl.js → vm.BHBje7cC.js} +73 -177
- package/dist/chunks/{worker.d.Db-UVmXc.d.ts → worker.d.BKu8cnnX.d.ts} +1 -1
- package/dist/chunks/{worker.d.D9QWnzAe.d.ts → worker.d.DYlqbejz.d.ts} +1 -1
- package/dist/cli.js +3 -3
- package/dist/config.d.ts +7 -7
- package/dist/coverage.d.ts +4 -4
- package/dist/coverage.js +2 -2
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -5
- package/dist/module-evaluator.d.ts +3 -3
- package/dist/module-evaluator.js +33 -84
- package/dist/module-runner.js +2 -2
- package/dist/node.d.ts +11 -9
- package/dist/node.js +16 -27
- package/dist/reporters.d.ts +5 -5
- package/dist/reporters.js +3 -3
- package/dist/runners.d.ts +1 -1
- package/dist/runners.js +23 -51
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +18 -34
- package/dist/workers/forks.js +4 -4
- package/dist/workers/runVmTests.js +19 -37
- package/dist/workers/threads.js +4 -4
- package/dist/workers/vmForks.js +7 -7
- package/dist/workers/vmThreads.js +7 -7
- package/dist/workers.d.ts +3 -3
- package/dist/workers.js +11 -11
- package/package.json +11 -11
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
2
2
|
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve, dirname, isAbsolute, relative, basename, normalize } from 'pathe';
|
|
4
|
-
import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.
|
|
4
|
+
import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.DSo_maXz.js';
|
|
5
5
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
6
6
|
import { getTestName, getFullName, hasFailed, getTests, getSuites, getTasks } from '@vitest/runner/utils';
|
|
7
7
|
import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
|
|
8
|
-
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
8
|
+
import { parseStacktrace, parseErrorStacktrace, defaultStackIgnorePatterns } from '@vitest/utils/source-map';
|
|
9
9
|
import c from 'tinyrainbow';
|
|
10
10
|
import { i as isTTY } from './env.D4Lgay0q.js';
|
|
11
11
|
import { stripVTControlCharacters } from 'node:util';
|
|
@@ -125,16 +125,19 @@ class BlobReporter {
|
|
|
125
125
|
start = 0;
|
|
126
126
|
ctx;
|
|
127
127
|
options;
|
|
128
|
+
coverage;
|
|
128
129
|
constructor(options) {
|
|
129
130
|
this.options = options;
|
|
130
131
|
}
|
|
131
132
|
onInit(ctx) {
|
|
132
133
|
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
133
|
-
this.ctx = ctx;
|
|
134
|
-
|
|
134
|
+
this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
|
|
135
|
+
}
|
|
136
|
+
onCoverage(coverage) {
|
|
137
|
+
this.coverage = coverage;
|
|
135
138
|
}
|
|
136
|
-
async
|
|
137
|
-
const executionTime = performance.now() - this.start;
|
|
139
|
+
async onTestRunEnd(testModules, unhandledErrors) {
|
|
140
|
+
const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
|
|
138
141
|
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
139
142
|
if (!outputFile) {
|
|
140
143
|
const shard = this.ctx.config.shard;
|
|
@@ -142,43 +145,34 @@ class BlobReporter {
|
|
|
142
145
|
}
|
|
143
146
|
const modules = this.ctx.projects.map((project) => {
|
|
144
147
|
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
145
|
-
|
|
146
|
-
return [
|
|
148
|
+
return mod[1].file ? [
|
|
147
149
|
mod[0],
|
|
148
150
|
mod[1].file,
|
|
149
151
|
mod[1].url
|
|
150
|
-
];
|
|
152
|
+
] : null;
|
|
151
153
|
}).filter((x) => x != null)];
|
|
152
|
-
})
|
|
153
|
-
const report = [
|
|
154
|
+
}), report = [
|
|
154
155
|
this.ctx.version,
|
|
155
156
|
files,
|
|
156
157
|
errors,
|
|
157
158
|
modules,
|
|
158
159
|
coverage,
|
|
159
160
|
executionTime
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
await writeBlob(report, reportFile);
|
|
163
|
-
this.ctx.logger.log("blob report written to", reportFile);
|
|
161
|
+
], reportFile = resolve(this.ctx.config.root, outputFile);
|
|
162
|
+
await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
async function writeBlob(content, filename) {
|
|
167
|
-
const report = stringify(content);
|
|
168
|
-
const dir = dirname(filename);
|
|
166
|
+
const report = stringify(content), dir = dirname(filename);
|
|
169
167
|
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
170
168
|
await writeFile(filename, report, "utf-8");
|
|
171
169
|
}
|
|
172
170
|
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
173
171
|
// using process.cwd() because --merge-reports can only be used in CLI
|
|
174
|
-
const resolvedDir = resolve(process.cwd(), blobsDirectory)
|
|
175
|
-
|
|
176
|
-
const promises = blobsFiles.map(async (filename) => {
|
|
177
|
-
const fullPath = resolve(resolvedDir, filename);
|
|
178
|
-
const stats = await stat(fullPath);
|
|
172
|
+
const resolvedDir = resolve(process.cwd(), blobsDirectory), blobsFiles = await readdir(resolvedDir), promises = blobsFiles.map(async (filename) => {
|
|
173
|
+
const fullPath = resolve(resolvedDir, filename), stats = await stat(fullPath);
|
|
179
174
|
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`);
|
|
180
|
-
const content = await readFile(fullPath, "utf-8");
|
|
181
|
-
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
175
|
+
const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
182
176
|
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`);
|
|
183
177
|
return {
|
|
184
178
|
version,
|
|
@@ -189,8 +183,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
189
183
|
file: filename,
|
|
190
184
|
executionTime
|
|
191
185
|
};
|
|
192
|
-
});
|
|
193
|
-
const blobs = await Promise.all(promises);
|
|
186
|
+
}), blobs = await Promise.all(promises);
|
|
194
187
|
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
195
188
|
const versions = new Set(blobs.map((blob) => blob.version));
|
|
196
189
|
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")}`);
|
|
@@ -200,27 +193,19 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
200
193
|
blobs.forEach((blob) => {
|
|
201
194
|
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
202
195
|
const project = projects[projectName];
|
|
203
|
-
|
|
204
|
-
moduleIds.forEach(([moduleId, file, url]) => {
|
|
196
|
+
project && moduleIds.forEach(([moduleId, file, url]) => {
|
|
205
197
|
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
206
|
-
moduleNode.url = url
|
|
207
|
-
moduleNode.id = moduleId;
|
|
208
|
-
moduleNode.transformResult = {
|
|
198
|
+
moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
|
|
209
199
|
code: " ",
|
|
210
200
|
map: null
|
|
211
|
-
};
|
|
212
|
-
project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
201
|
+
}, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
213
202
|
});
|
|
214
203
|
});
|
|
215
204
|
});
|
|
216
205
|
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
217
|
-
const time1 = f1.result?.startTime || 0;
|
|
218
|
-
const time2 = f2.result?.startTime || 0;
|
|
206
|
+
const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
|
|
219
207
|
return time1 - time2;
|
|
220
|
-
});
|
|
221
|
-
const errors = blobs.flatMap((blob) => blob.errors);
|
|
222
|
-
const coverages = blobs.map((blob) => blob.coverage);
|
|
223
|
-
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
208
|
+
}), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
|
|
224
209
|
return {
|
|
225
210
|
files,
|
|
226
211
|
errors,
|
|
@@ -262,26 +247,18 @@ function errorBanner(message) {
|
|
|
262
247
|
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
263
248
|
}
|
|
264
249
|
function divider(text, left, right, color) {
|
|
265
|
-
const cols = getCols();
|
|
266
|
-
const c = color || ((text) => text);
|
|
250
|
+
const cols = getCols(), c = color || ((text) => text);
|
|
267
251
|
if (text) {
|
|
268
252
|
const textLength = stripVTControlCharacters(text).length;
|
|
269
253
|
if (left == null && right != null) left = cols - textLength - right;
|
|
270
|
-
else
|
|
271
|
-
|
|
272
|
-
right = cols - textLength - left;
|
|
273
|
-
}
|
|
274
|
-
left = Math.max(0, left);
|
|
275
|
-
right = Math.max(0, right);
|
|
276
|
-
return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
|
|
254
|
+
else left = left ?? Math.floor((cols - textLength) / 2), right = cols - textLength - left;
|
|
255
|
+
return left = Math.max(0, left), right = Math.max(0, right), `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
|
|
277
256
|
}
|
|
278
257
|
return F_LONG_DASH.repeat(cols);
|
|
279
258
|
}
|
|
280
259
|
function formatTestPath(root, path) {
|
|
281
260
|
if (isAbsolute(path)) path = relative(root, path);
|
|
282
|
-
const dir = dirname(path);
|
|
283
|
-
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
284
|
-
const base = basename(path, ext);
|
|
261
|
+
const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
|
|
285
262
|
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
286
263
|
}
|
|
287
264
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
@@ -293,8 +270,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
293
270
|
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
294
271
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
295
272
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
296
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`)
|
|
297
|
-
tail.forEach((key) => {
|
|
273
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
|
|
298
274
|
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
299
275
|
});
|
|
300
276
|
}
|
|
@@ -302,8 +278,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
302
278
|
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
303
279
|
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
304
280
|
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
305
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
306
|
-
uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
281
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`), uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
307
282
|
});
|
|
308
283
|
}
|
|
309
284
|
return summary;
|
|
@@ -313,10 +288,7 @@ function countTestErrors(tasks) {
|
|
|
313
288
|
}
|
|
314
289
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
315
290
|
if (tasks.length === 0) return c.dim(`no ${name}`);
|
|
316
|
-
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
317
|
-
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
318
|
-
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
319
|
-
const todo = tasks.filter((i) => i.mode === "todo");
|
|
291
|
+
const passed = tasks.filter((i) => i.result?.state === "pass"), failed = tasks.filter((i) => i.result?.state === "fail"), skipped = tasks.filter((i) => i.mode === "skip"), todo = tasks.filter((i) => i.mode === "todo");
|
|
320
292
|
return [
|
|
321
293
|
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
322
294
|
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
@@ -330,16 +302,13 @@ function getStateSymbol(task) {
|
|
|
330
302
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
331
303
|
if (task.type === "suite") return pointer;
|
|
332
304
|
}
|
|
333
|
-
|
|
334
|
-
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
335
|
-
return " ";
|
|
305
|
+
return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
|
|
336
306
|
}
|
|
337
307
|
function formatTimeString(date) {
|
|
338
308
|
return date.toTimeString().split(" ")[0];
|
|
339
309
|
}
|
|
340
310
|
function formatTime(time) {
|
|
341
|
-
|
|
342
|
-
return `${Math.round(time)}ms`;
|
|
311
|
+
return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
|
|
343
312
|
}
|
|
344
313
|
function formatProjectName(project, suffix = " ") {
|
|
345
314
|
if (!project?.name) return "";
|
|
@@ -360,8 +329,7 @@ function padSummaryTitle(str) {
|
|
|
360
329
|
}
|
|
361
330
|
function truncateString(text, maxLength) {
|
|
362
331
|
const plainText = stripVTControlCharacters(text);
|
|
363
|
-
|
|
364
|
-
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
332
|
+
return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
|
|
365
333
|
}
|
|
366
334
|
function capitalize(text) {
|
|
367
335
|
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
@@ -407,9 +375,7 @@ class BaseReporter {
|
|
|
407
375
|
this.isTTY = options.isTTY ?? isTTY;
|
|
408
376
|
}
|
|
409
377
|
onInit(ctx) {
|
|
410
|
-
this.ctx = ctx;
|
|
411
|
-
this.ctx.logger.printBanner();
|
|
412
|
-
this.start = performance$1.now();
|
|
378
|
+
this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
|
|
413
379
|
}
|
|
414
380
|
log(...messages) {
|
|
415
381
|
this.ctx.logger.log(...messages);
|
|
@@ -420,9 +386,9 @@ class BaseReporter {
|
|
|
420
386
|
relative(path) {
|
|
421
387
|
return relative(this.ctx.config.root, path);
|
|
422
388
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
389
|
+
onTestRunEnd(testModules, unhandledErrors, _reason) {
|
|
390
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors];
|
|
391
|
+
if (this.end = performance$1.now(), !files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
426
392
|
else this.reportSummary(files, errors);
|
|
427
393
|
}
|
|
428
394
|
onTestCaseResult(testCase) {
|
|
@@ -441,13 +407,10 @@ class BaseReporter {
|
|
|
441
407
|
printTestModule(testModule) {
|
|
442
408
|
const moduleState = testModule.state();
|
|
443
409
|
if (moduleState === "queued" || moduleState === "pending") return;
|
|
444
|
-
let testsCount = 0;
|
|
445
|
-
let failedCount = 0;
|
|
446
|
-
let skippedCount = 0;
|
|
410
|
+
let testsCount = 0, failedCount = 0, skippedCount = 0;
|
|
447
411
|
// delaying logs to calculate the test stats first
|
|
448
412
|
// which minimizes the amount of for loops
|
|
449
|
-
const logs = [];
|
|
450
|
-
const originalLog = this.log.bind(this);
|
|
413
|
+
const logs = [], originalLog = this.log.bind(this);
|
|
451
414
|
this.log = (msg) => logs.push(msg);
|
|
452
415
|
const visit = (suiteState, children) => {
|
|
453
416
|
for (const child of children) if (child.type === "suite") {
|
|
@@ -457,8 +420,7 @@ class BaseReporter {
|
|
|
457
420
|
visit(suiteState, child.children);
|
|
458
421
|
} else {
|
|
459
422
|
const testResult = child.result();
|
|
460
|
-
testsCount++;
|
|
461
|
-
if (testResult.state === "failed") failedCount++;
|
|
423
|
+
if (testsCount++, testResult.state === "failed") failedCount++;
|
|
462
424
|
else if (testResult.state === "skipped") skippedCount++;
|
|
463
425
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
464
426
|
// Skipped suites are hidden when --hideSkippedTests
|
|
@@ -475,24 +437,20 @@ class BaseReporter {
|
|
|
475
437
|
tests: testsCount,
|
|
476
438
|
failed: failedCount,
|
|
477
439
|
skipped: skippedCount
|
|
478
|
-
}));
|
|
479
|
-
logs.forEach((log) => this.log(log));
|
|
440
|
+
})), logs.forEach((log) => this.log(log));
|
|
480
441
|
}
|
|
481
442
|
printTestCase(moduleState, test) {
|
|
482
|
-
const testResult = test.result();
|
|
483
|
-
const { duration, retryCount, repeatCount } = test.diagnostic() || {};
|
|
484
|
-
const padding = this.getTestIndentation(test.task);
|
|
443
|
+
const testResult = test.result(), { duration, retryCount, repeatCount } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task);
|
|
485
444
|
let suffix = this.getDurationPrefix(test.task);
|
|
486
445
|
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
487
446
|
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
488
|
-
if (testResult.state === "failed")
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
} else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
447
|
+
if (testResult.state === "failed")
|
|
448
|
+
// print short errors, full errors will be at the end in summary
|
|
449
|
+
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix), testResult.errors.forEach((error) => {
|
|
450
|
+
const message = this.formatShortError(error);
|
|
451
|
+
if (message) this.log(c.red(` ${padding}${message}`));
|
|
452
|
+
});
|
|
453
|
+
else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
496
454
|
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}]`))}`);
|
|
497
455
|
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
498
456
|
}
|
|
@@ -551,10 +509,8 @@ class BaseReporter {
|
|
|
551
509
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
552
510
|
}
|
|
553
511
|
onWatcherRerun(files, trigger) {
|
|
554
|
-
this.watchFilters = files;
|
|
555
|
-
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
556
512
|
// Update re-run count for each file
|
|
557
|
-
files.forEach((filepath) => {
|
|
513
|
+
this.watchFilters = files, this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed"), files.forEach((filepath) => {
|
|
558
514
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
559
515
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
560
516
|
});
|
|
@@ -563,35 +519,26 @@ class BaseReporter {
|
|
|
563
519
|
const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
|
|
564
520
|
banner += c.blue(`x${rerun} `);
|
|
565
521
|
}
|
|
566
|
-
this.ctx.logger.clearFullScreen();
|
|
567
|
-
this.log(withLabel("blue", "RERUN", banner));
|
|
568
|
-
if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
522
|
+
if (this.ctx.logger.clearFullScreen(), this.log(withLabel("blue", "RERUN", banner)), this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
569
523
|
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
570
524
|
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
571
525
|
this.log("");
|
|
572
526
|
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
573
|
-
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
574
|
-
this.start = performance$1.now();
|
|
527
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
|
|
575
528
|
}
|
|
576
529
|
onUserConsoleLog(log, taskState) {
|
|
577
530
|
if (!this.shouldLog(log, taskState)) return;
|
|
578
|
-
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
|
|
579
|
-
const write = (msg) => output.write(msg);
|
|
531
|
+
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream, write = (msg) => output.write(msg);
|
|
580
532
|
let headerText = "unknown test";
|
|
581
533
|
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
582
534
|
if (task) headerText = this.getFullName(task, c.dim(" > "));
|
|
583
535
|
else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
|
|
584
|
-
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content)
|
|
585
|
-
if (log.origin) {
|
|
536
|
+
if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
|
|
586
537
|
// browser logs don't have an extra end of line at the end like Node.js does
|
|
587
538
|
if (log.browser) write("\n");
|
|
588
|
-
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
589
|
-
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
590
|
-
const highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
539
|
+
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject(), stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin), highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
591
540
|
for (const frame of stack) {
|
|
592
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
593
|
-
const path = relative(project.config.root, frame.file);
|
|
594
|
-
const positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
|
|
541
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file), positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
|
|
595
542
|
write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
|
|
596
543
|
}
|
|
597
544
|
}
|
|
@@ -601,12 +548,9 @@ class BaseReporter {
|
|
|
601
548
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
602
549
|
}
|
|
603
550
|
shouldLog(log, taskState) {
|
|
604
|
-
if (this.ctx.config.silent === true) return false;
|
|
605
|
-
if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
551
|
+
if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
606
552
|
if (this.ctx.config.onConsoleLog) {
|
|
607
|
-
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
608
|
-
const entity = task && this.ctx.state.getReportedEntity(task);
|
|
609
|
-
const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
|
|
553
|
+
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0, entity = task && this.ctx.state.getReportedEntity(task), shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
|
|
610
554
|
if (shouldLog === false) return shouldLog;
|
|
611
555
|
}
|
|
612
556
|
return true;
|
|
@@ -615,41 +559,27 @@ class BaseReporter {
|
|
|
615
559
|
this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
|
|
616
560
|
}
|
|
617
561
|
reportSummary(files, errors) {
|
|
618
|
-
this.printErrorsSummary(files, errors);
|
|
619
|
-
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
562
|
+
if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
620
563
|
else this.reportTestSummary(files, errors);
|
|
621
564
|
}
|
|
622
565
|
reportTestSummary(files, errors) {
|
|
623
566
|
this.log();
|
|
624
|
-
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
|
|
625
|
-
const tests = getTests(affectedFiles);
|
|
626
|
-
const snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
|
|
567
|
+
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files], tests = getTests(affectedFiles), snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
|
|
627
568
|
for (const [index, snapshot] of snapshotOutput.entries()) {
|
|
628
569
|
const title = index === 0 ? "Snapshots" : "";
|
|
629
570
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
630
571
|
}
|
|
631
572
|
if (snapshotOutput.length > 1) this.log();
|
|
632
|
-
this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles))
|
|
633
|
-
this.log(padSummaryTitle("Tests"), getStateString$1(tests));
|
|
634
|
-
if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
|
|
573
|
+
if (this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles)), this.log(padSummaryTitle("Tests"), getStateString$1(tests)), this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
|
|
635
574
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
636
575
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
637
576
|
}
|
|
638
577
|
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
639
578
|
this.log(padSummaryTitle("Start at"), this._timeStart);
|
|
640
|
-
const collectTime = sum(files, (file) => file.collectDuration);
|
|
641
|
-
const testsTime = sum(files, (file) => file.result?.duration);
|
|
642
|
-
const setupTime = sum(files, (file) => file.setupDuration);
|
|
579
|
+
const collectTime = sum(files, (file) => file.collectDuration), testsTime = sum(files, (file) => file.result?.duration), setupTime = sum(files, (file) => file.setupDuration);
|
|
643
580
|
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
644
581
|
else {
|
|
645
|
-
const blobs = this.ctx.state.blobs
|
|
646
|
-
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
647
|
-
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
648
|
-
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
649
|
-
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
650
|
-
const transformTime = this.ctx.state.transformTime;
|
|
651
|
-
const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
|
|
652
|
-
const timers = [
|
|
582
|
+
const blobs = this.ctx.state.blobs, executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start, environmentTime = sum(files, (file) => file.environmentLoad), prepareTime = sum(files, (file) => file.prepareDuration), transformTime = this.ctx.state.transformTime, typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time), timers = [
|
|
653
583
|
`transform ${formatTime(transformTime)}`,
|
|
654
584
|
`setup ${formatTime(setupTime)}`,
|
|
655
585
|
`collect ${formatTime(collectTime)}`,
|
|
@@ -658,41 +588,25 @@ class BaseReporter {
|
|
|
658
588
|
`prepare ${formatTime(prepareTime)}`,
|
|
659
589
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
660
590
|
].filter(Boolean).join(", ");
|
|
661
|
-
this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
|
|
662
|
-
if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
591
|
+
if (this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`)), blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
663
592
|
}
|
|
664
593
|
this.log();
|
|
665
594
|
}
|
|
666
595
|
printErrorsSummary(files, errors) {
|
|
667
|
-
const suites = getSuites(files);
|
|
668
|
-
const tests = getTests(files);
|
|
669
|
-
const failedSuites = suites.filter((i) => i.result?.errors);
|
|
670
|
-
const failedTests = tests.filter((i) => i.result?.state === "fail");
|
|
671
|
-
const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
|
|
596
|
+
const suites = getSuites(files), tests = getTests(files), failedSuites = suites.filter((i) => i.result?.errors), failedTests = tests.filter((i) => i.result?.state === "fail"), failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
|
|
672
597
|
let current = 1;
|
|
673
598
|
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
|
|
674
|
-
if (failedSuites.length) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
if (failedTests.length) {
|
|
679
|
-
this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
|
|
680
|
-
this.printTaskErrors(failedTests, errorDivider);
|
|
681
|
-
}
|
|
682
|
-
if (errors.length) {
|
|
683
|
-
this.ctx.logger.printUnhandledErrors(errors);
|
|
684
|
-
this.error();
|
|
685
|
-
}
|
|
599
|
+
if (failedSuites.length) this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`), this.printTaskErrors(failedSuites, errorDivider);
|
|
600
|
+
if (failedTests.length) this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`), this.printTaskErrors(failedTests, errorDivider);
|
|
601
|
+
if (errors.length) this.ctx.logger.printUnhandledErrors(errors), this.error();
|
|
686
602
|
}
|
|
687
603
|
reportBenchmarkSummary(files) {
|
|
688
|
-
const benches = getTests(files);
|
|
689
|
-
const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
604
|
+
const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
690
605
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
691
606
|
for (const bench of topBenches) {
|
|
692
607
|
const group = bench.suite || bench.file;
|
|
693
608
|
if (!group) continue;
|
|
694
|
-
const groupName = this.getFullName(group, c.dim(" > "));
|
|
695
|
-
const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
609
|
+
const groupName = this.getFullName(group, c.dim(" > ")), project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
696
610
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
697
611
|
const siblings = group.tasks.filter((i) => i.meta.benchmark && i.result?.benchmark && i !== bench).sort((a, b) => a.result.benchmark.rank - b.result.benchmark.rank);
|
|
698
612
|
for (const sibling of siblings) {
|
|
@@ -710,10 +624,7 @@ class BaseReporter {
|
|
|
710
624
|
let previous;
|
|
711
625
|
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
712
626
|
if (i[0]?.stack !== error.stack) return false;
|
|
713
|
-
const currentProjectName = task?.projectName || task.file?.projectName || "";
|
|
714
|
-
const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
|
|
715
|
-
const currentAnnotations = task.type === "test" && task.annotations;
|
|
716
|
-
const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
|
|
627
|
+
const currentProjectName = task?.projectName || task.file?.projectName || "", projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "", currentAnnotations = task.type === "test" && task.annotations, itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
|
|
717
628
|
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
718
629
|
});
|
|
719
630
|
if (previous) previous[1].push(task);
|
|
@@ -721,24 +632,20 @@ class BaseReporter {
|
|
|
721
632
|
});
|
|
722
633
|
for (const [error, tasks] of errorsQueue) {
|
|
723
634
|
for (const task of tasks) {
|
|
724
|
-
const filepath = task?.filepath || "";
|
|
725
|
-
const projectName = task?.projectName || task.file?.projectName || "";
|
|
726
|
-
const project = this.ctx.projects.find((p) => p.name === projectName);
|
|
635
|
+
const filepath = task?.filepath || "", projectName = task?.projectName || task.file?.projectName || "", project = this.ctx.projects.find((p) => p.name === projectName);
|
|
727
636
|
let name = this.getFullName(task, c.dim(" > "));
|
|
728
637
|
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
729
638
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
730
639
|
}
|
|
731
640
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
732
|
-
this.ctx.logger.printError(error, {
|
|
641
|
+
if (this.ctx.logger.printError(error, {
|
|
733
642
|
project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
|
|
734
643
|
verbose: this.verbose,
|
|
735
644
|
screenshotPaths,
|
|
736
645
|
task: tasks[0]
|
|
737
|
-
})
|
|
738
|
-
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
646
|
+
}), tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
739
647
|
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
740
|
-
this.printAnnotations(test, "error", 1);
|
|
741
|
-
this.error();
|
|
648
|
+
this.printAnnotations(test, "error", 1), this.error();
|
|
742
649
|
}
|
|
743
650
|
errorDivider();
|
|
744
651
|
}
|
|
@@ -747,8 +654,7 @@ class BaseReporter {
|
|
|
747
654
|
function deepEqual(a, b) {
|
|
748
655
|
if (a === b) return true;
|
|
749
656
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
750
|
-
const keysA = Object.keys(a);
|
|
751
|
-
const keysB = Object.keys(b);
|
|
657
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
752
658
|
if (keysA.length !== keysB.length) return false;
|
|
753
659
|
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
754
660
|
return true;
|
|
@@ -759,12 +665,7 @@ function sum(items, cb) {
|
|
|
759
665
|
}, 0);
|
|
760
666
|
}
|
|
761
667
|
|
|
762
|
-
const DEFAULT_RENDER_INTERVAL_MS = 1e3
|
|
763
|
-
const ESC = "\x1B[";
|
|
764
|
-
const CLEAR_LINE = `${ESC}K`;
|
|
765
|
-
const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
|
|
766
|
-
const SYNC_START = `${ESC}?2026h`;
|
|
767
|
-
const SYNC_END = `${ESC}?2026l`;
|
|
668
|
+
const DEFAULT_RENDER_INTERVAL_MS = 1e3, ESC = "\x1B[", CLEAR_LINE = `${ESC}K`, MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`, SYNC_START = `${ESC}?2026h`, SYNC_END = `${ESC}?2026l`;
|
|
768
669
|
/**
|
|
769
670
|
* Renders content of `getWindow` at the bottom of the terminal and
|
|
770
671
|
* forwards all other intercepted `stdout` and `stderr` logs above it.
|
|
@@ -780,50 +681,37 @@ class WindowRenderer {
|
|
|
780
681
|
finished = false;
|
|
781
682
|
cleanups = [];
|
|
782
683
|
constructor(options) {
|
|
684
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
783
685
|
this.options = {
|
|
784
686
|
interval: DEFAULT_RENDER_INTERVAL_MS,
|
|
785
687
|
...options
|
|
786
|
-
}
|
|
787
|
-
this.streams = {
|
|
688
|
+
}, this.streams = {
|
|
788
689
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
789
690
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
793
|
-
this.options.logger.onTerminalCleanup(() => {
|
|
794
|
-
this.flushBuffer();
|
|
795
|
-
this.stop();
|
|
691
|
+
}, this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error")), this.options.logger.onTerminalCleanup(() => {
|
|
692
|
+
this.flushBuffer(), this.stop();
|
|
796
693
|
});
|
|
797
694
|
}
|
|
798
695
|
start() {
|
|
799
|
-
this.started = true;
|
|
800
|
-
this.finished = false;
|
|
801
|
-
this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
696
|
+
this.started = true, this.finished = false, this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
802
697
|
}
|
|
803
698
|
stop() {
|
|
804
|
-
this.cleanups.splice(0).map((fn) => fn());
|
|
805
|
-
clearInterval(this.renderInterval);
|
|
699
|
+
this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
|
|
806
700
|
}
|
|
807
701
|
/**
|
|
808
702
|
* Write all buffered output and stop buffering.
|
|
809
703
|
* All intercepted writes are forwarded to actual write after this.
|
|
810
704
|
*/
|
|
811
705
|
finish() {
|
|
812
|
-
this.finished = true;
|
|
813
|
-
this.flushBuffer();
|
|
814
|
-
clearInterval(this.renderInterval);
|
|
706
|
+
this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
|
|
815
707
|
}
|
|
816
708
|
/**
|
|
817
709
|
* Queue new render update
|
|
818
710
|
*/
|
|
819
711
|
schedule() {
|
|
820
|
-
if (!this.renderScheduled) {
|
|
821
|
-
this.renderScheduled =
|
|
822
|
-
|
|
823
|
-
setTimeout(() => {
|
|
824
|
-
this.renderScheduled = false;
|
|
825
|
-
}, 100).unref();
|
|
826
|
-
}
|
|
712
|
+
if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
|
|
713
|
+
this.renderScheduled = false;
|
|
714
|
+
}, 100).unref();
|
|
827
715
|
}
|
|
828
716
|
flushBuffer() {
|
|
829
717
|
if (this.buffer.length === 0) return this.render();
|
|
@@ -835,8 +723,7 @@ class WindowRenderer {
|
|
|
835
723
|
continue;
|
|
836
724
|
}
|
|
837
725
|
if (current.type !== next.type) {
|
|
838
|
-
this.render(current.message, current.type);
|
|
839
|
-
current = next;
|
|
726
|
+
this.render(current.message, current.type), current = next;
|
|
840
727
|
continue;
|
|
841
728
|
}
|
|
842
729
|
current.message += next.message;
|
|
@@ -844,40 +731,31 @@ class WindowRenderer {
|
|
|
844
731
|
if (current) this.render(current?.message, current?.type);
|
|
845
732
|
}
|
|
846
733
|
render(message, type = "output") {
|
|
847
|
-
if (this.finished)
|
|
848
|
-
|
|
849
|
-
return this.write(message || "", type);
|
|
850
|
-
}
|
|
851
|
-
const windowContent = this.options.getWindow();
|
|
852
|
-
const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
734
|
+
if (this.finished) return this.clearWindow(), this.write(message || "", type);
|
|
735
|
+
const windowContent = this.options.getWindow(), rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
853
736
|
let padding = this.windowHeight - rowCount;
|
|
854
737
|
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
855
|
-
this.write(SYNC_START);
|
|
856
|
-
this.clearWindow();
|
|
857
|
-
if (message) this.write(message, type);
|
|
738
|
+
if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
|
|
858
739
|
if (padding > 0) this.write("\n".repeat(padding));
|
|
859
|
-
this.write(windowContent.join("\n"));
|
|
860
|
-
this.write(SYNC_END);
|
|
861
|
-
this.windowHeight = rowCount + Math.max(0, padding);
|
|
740
|
+
this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
|
|
862
741
|
}
|
|
863
742
|
clearWindow() {
|
|
864
|
-
if (this.windowHeight
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
743
|
+
if (this.windowHeight !== 0) {
|
|
744
|
+
this.write(CLEAR_LINE);
|
|
745
|
+
for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
|
|
746
|
+
this.windowHeight = 0;
|
|
747
|
+
}
|
|
868
748
|
}
|
|
869
749
|
interceptStream(stream, type) {
|
|
870
750
|
const original = stream.write;
|
|
871
|
-
|
|
872
|
-
stream.write = (chunk, _, callback) => {
|
|
751
|
+
return stream.write = (chunk, _, callback) => {
|
|
873
752
|
if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
|
|
874
753
|
else this.buffer.push({
|
|
875
754
|
type,
|
|
876
755
|
message: chunk.toString()
|
|
877
756
|
});
|
|
878
757
|
callback?.();
|
|
879
|
-
}
|
|
880
|
-
return function restore() {
|
|
758
|
+
}, function restore() {
|
|
881
759
|
stream.write = original;
|
|
882
760
|
};
|
|
883
761
|
}
|
|
@@ -895,8 +773,7 @@ function getRenderedRowCount(rows, columns) {
|
|
|
895
773
|
return count;
|
|
896
774
|
}
|
|
897
775
|
|
|
898
|
-
const DURATION_UPDATE_INTERVAL_MS = 100;
|
|
899
|
-
const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
776
|
+
const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
900
777
|
/**
|
|
901
778
|
* Reporter extension that renders summary and forwards all other logs above itself.
|
|
902
779
|
* Intended to be used by other reporters, not as a standalone reporter.
|
|
@@ -917,34 +794,21 @@ class SummaryReporter {
|
|
|
917
794
|
duration = 0;
|
|
918
795
|
durationInterval = void 0;
|
|
919
796
|
onInit(ctx, options = {}) {
|
|
920
|
-
this.ctx = ctx
|
|
921
|
-
this.options = {
|
|
797
|
+
this.ctx = ctx, this.options = {
|
|
922
798
|
verbose: false,
|
|
923
799
|
...options
|
|
924
|
-
}
|
|
925
|
-
this.renderer = new WindowRenderer({
|
|
800
|
+
}, this.renderer = new WindowRenderer({
|
|
926
801
|
logger: ctx.logger,
|
|
927
802
|
getWindow: () => this.createSummary()
|
|
928
|
-
})
|
|
929
|
-
|
|
930
|
-
clearInterval(this.durationInterval);
|
|
931
|
-
this.renderer.stop();
|
|
803
|
+
}), this.ctx.onClose(() => {
|
|
804
|
+
clearInterval(this.durationInterval), this.renderer.stop();
|
|
932
805
|
});
|
|
933
806
|
}
|
|
934
807
|
onTestRunStart(specifications) {
|
|
935
|
-
this.runningModules.clear();
|
|
936
|
-
this.finishedModules.clear();
|
|
937
|
-
this.modules = emptyCounters();
|
|
938
|
-
this.tests = emptyCounters();
|
|
939
|
-
this.startTimers();
|
|
940
|
-
this.renderer.start();
|
|
941
|
-
this.modules.total = specifications.length;
|
|
808
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.modules = emptyCounters(), this.tests = emptyCounters(), this.startTimers(), this.renderer.start(), this.modules.total = specifications.length;
|
|
942
809
|
}
|
|
943
810
|
onTestRunEnd() {
|
|
944
|
-
this.runningModules.clear();
|
|
945
|
-
this.finishedModules.clear();
|
|
946
|
-
this.renderer.finish();
|
|
947
|
-
clearInterval(this.durationInterval);
|
|
811
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
|
|
948
812
|
}
|
|
949
813
|
onTestModuleQueued(module) {
|
|
950
814
|
// When new test module starts, take the place of previously finished test module, if any
|
|
@@ -952,20 +816,13 @@ class SummaryReporter {
|
|
|
952
816
|
const finished = this.finishedModules.keys().next().value;
|
|
953
817
|
this.removeTestModule(finished);
|
|
954
818
|
}
|
|
955
|
-
this.runningModules.set(module.id, initializeStats(module));
|
|
956
|
-
this.renderer.schedule();
|
|
819
|
+
this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
|
|
957
820
|
}
|
|
958
821
|
onTestModuleCollected(module) {
|
|
959
822
|
let stats = this.runningModules.get(module.id);
|
|
960
|
-
if (!stats)
|
|
961
|
-
stats = initializeStats(module);
|
|
962
|
-
this.runningModules.set(module.id, stats);
|
|
963
|
-
}
|
|
823
|
+
if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
|
|
964
824
|
const total = Array.from(module.children.allTests()).length;
|
|
965
|
-
this.tests.total += total;
|
|
966
|
-
stats.total = total;
|
|
967
|
-
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
968
|
-
this.renderer.schedule();
|
|
825
|
+
this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
|
|
969
826
|
}
|
|
970
827
|
onHookStart(options) {
|
|
971
828
|
const stats = this.getHookStats(options);
|
|
@@ -976,8 +833,7 @@ class SummaryReporter {
|
|
|
976
833
|
startTime: performance.now(),
|
|
977
834
|
onFinish: () => {}
|
|
978
835
|
};
|
|
979
|
-
stats.hook?.onFinish?.();
|
|
980
|
-
stats.hook = hook;
|
|
836
|
+
stats.hook?.onFinish?.(), stats.hook = hook;
|
|
981
837
|
const timeout = setTimeout(() => {
|
|
982
838
|
hook.visible = true;
|
|
983
839
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
@@ -985,9 +841,7 @@ class SummaryReporter {
|
|
|
985
841
|
}
|
|
986
842
|
onHookEnd(options) {
|
|
987
843
|
const stats = this.getHookStats(options);
|
|
988
|
-
|
|
989
|
-
stats.hook.onFinish();
|
|
990
|
-
stats.hook.visible = false;
|
|
844
|
+
stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
|
|
991
845
|
}
|
|
992
846
|
onTestCaseReady(test) {
|
|
993
847
|
// Track slow running tests only on verbose mode
|
|
@@ -999,22 +853,17 @@ class SummaryReporter {
|
|
|
999
853
|
visible: false,
|
|
1000
854
|
startTime: performance.now(),
|
|
1001
855
|
onFinish: () => {}
|
|
1002
|
-
}
|
|
1003
|
-
const timeout = setTimeout(() => {
|
|
856
|
+
}, timeout = setTimeout(() => {
|
|
1004
857
|
slowTest.visible = true;
|
|
1005
858
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
1006
859
|
slowTest.onFinish = () => {
|
|
1007
|
-
slowTest.hook?.onFinish();
|
|
1008
|
-
|
|
1009
|
-
};
|
|
1010
|
-
stats.tests.set(test.id, slowTest);
|
|
860
|
+
slowTest.hook?.onFinish(), clearTimeout(timeout);
|
|
861
|
+
}, stats.tests.set(test.id, slowTest);
|
|
1011
862
|
}
|
|
1012
863
|
onTestCaseResult(test) {
|
|
1013
864
|
const stats = this.runningModules.get(test.module.id);
|
|
1014
865
|
if (!stats) return;
|
|
1015
|
-
stats.tests.get(test.id)?.onFinish()
|
|
1016
|
-
stats.tests.delete(test.id);
|
|
1017
|
-
stats.completed++;
|
|
866
|
+
stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
|
|
1018
867
|
const result = test.result();
|
|
1019
868
|
if (result?.state === "passed") this.tests.passed++;
|
|
1020
869
|
else if (result?.state === "failed") this.tests.failed++;
|
|
@@ -1023,8 +872,7 @@ class SummaryReporter {
|
|
|
1023
872
|
}
|
|
1024
873
|
onTestModuleEnd(module) {
|
|
1025
874
|
const state = module.state();
|
|
1026
|
-
this.modules.completed++;
|
|
1027
|
-
if (state === "passed") this.modules.passed++;
|
|
875
|
+
if (this.modules.completed++, state === "passed") this.modules.passed++;
|
|
1028
876
|
else if (state === "failed") this.modules.failed++;
|
|
1029
877
|
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1030
878
|
else if (state === "skipped") this.modules.skipped++;
|
|
@@ -1044,10 +892,8 @@ class SummaryReporter {
|
|
|
1044
892
|
getHookStats({ entity }) {
|
|
1045
893
|
// Track slow running hooks only on verbose mode
|
|
1046
894
|
if (!this.options.verbose) return;
|
|
1047
|
-
const module = entity.type === "module" ? entity : entity.module;
|
|
1048
|
-
|
|
1049
|
-
if (!stats) return;
|
|
1050
|
-
return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
895
|
+
const module = entity.type === "module" ? entity : entity.module, stats = this.runningModules.get(module.id);
|
|
896
|
+
if (stats) return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
1051
897
|
}
|
|
1052
898
|
createSummary() {
|
|
1053
899
|
const summary = [""];
|
|
@@ -1059,36 +905,23 @@ class SummaryReporter {
|
|
|
1059
905
|
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1060
906
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1061
907
|
for (const [index, task] of slowTasks.entries()) {
|
|
1062
|
-
const elapsed = this.currentTime - task.startTime;
|
|
1063
|
-
|
|
1064
|
-
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
1065
|
-
if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
908
|
+
const elapsed = this.currentTime - task.startTime, icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
909
|
+
if (summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`))), task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
1066
910
|
}
|
|
1067
911
|
}
|
|
1068
912
|
if (this.runningModules.size > 0) summary.push("");
|
|
1069
|
-
summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
|
|
1070
|
-
summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
|
|
1071
|
-
summary.push(padSummaryTitle("Start at") + this.startTime);
|
|
1072
|
-
summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
|
|
1073
|
-
summary.push("");
|
|
1074
|
-
return summary;
|
|
913
|
+
return summary.push(padSummaryTitle("Test Files") + getStateString(this.modules)), summary.push(padSummaryTitle("Tests") + getStateString(this.tests)), summary.push(padSummaryTitle("Start at") + this.startTime), summary.push(padSummaryTitle("Duration") + formatTime(this.duration)), summary.push(""), summary;
|
|
1075
914
|
}
|
|
1076
915
|
startTimers() {
|
|
1077
916
|
const start = performance.now();
|
|
1078
|
-
this.startTime = formatTimeString(/* @__PURE__ */ new Date())
|
|
1079
|
-
|
|
1080
|
-
this.currentTime = performance.now();
|
|
1081
|
-
this.duration = this.currentTime - start;
|
|
917
|
+
this.startTime = formatTimeString(/* @__PURE__ */ new Date()), this.durationInterval = setInterval(() => {
|
|
918
|
+
this.currentTime = performance.now(), this.duration = this.currentTime - start;
|
|
1082
919
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1083
920
|
}
|
|
1084
921
|
removeTestModule(id) {
|
|
1085
922
|
if (!id) return;
|
|
1086
923
|
const testFile = this.runningModules.get(id);
|
|
1087
|
-
testFile?.hook?.onFinish();
|
|
1088
|
-
testFile?.tests?.forEach((test) => test.onFinish());
|
|
1089
|
-
this.runningModules.delete(id);
|
|
1090
|
-
clearTimeout(this.finishedModules.get(id));
|
|
1091
|
-
this.finishedModules.delete(id);
|
|
924
|
+
testFile?.hook?.onFinish(), testFile?.tests?.forEach((test) => test.onFinish()), this.runningModules.delete(id), clearTimeout(this.finishedModules.get(id)), this.finishedModules.delete(id);
|
|
1092
925
|
}
|
|
1093
926
|
}
|
|
1094
927
|
function emptyCounters() {
|
|
@@ -1110,9 +943,7 @@ function getStateString(entry) {
|
|
|
1110
943
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1111
944
|
}
|
|
1112
945
|
function sortRunningModules(a, b) {
|
|
1113
|
-
|
|
1114
|
-
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1115
|
-
return a.filename.localeCompare(b.filename);
|
|
946
|
+
return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
|
|
1116
947
|
}
|
|
1117
948
|
function initializeStats(module) {
|
|
1118
949
|
return {
|
|
@@ -1130,12 +961,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
1130
961
|
options;
|
|
1131
962
|
summary;
|
|
1132
963
|
constructor(options = {}) {
|
|
1133
|
-
super(options)
|
|
1134
|
-
this.options = {
|
|
964
|
+
if (super(options), this.options = {
|
|
1135
965
|
summary: true,
|
|
1136
966
|
...options
|
|
1137
|
-
};
|
|
1138
|
-
if (!this.isTTY) this.options.summary = false;
|
|
967
|
+
}, !this.isTTY) this.options.summary = false;
|
|
1139
968
|
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1140
969
|
}
|
|
1141
970
|
onTestRunStart(specifications) {
|
|
@@ -1145,6 +974,9 @@ class DefaultReporter extends BaseReporter {
|
|
|
1145
974
|
}
|
|
1146
975
|
this.summary?.onTestRunStart(specifications);
|
|
1147
976
|
}
|
|
977
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
978
|
+
super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
|
|
979
|
+
}
|
|
1148
980
|
onTestModuleQueued(file) {
|
|
1149
981
|
this.summary?.onTestModuleQueued(file);
|
|
1150
982
|
}
|
|
@@ -1152,15 +984,13 @@ class DefaultReporter extends BaseReporter {
|
|
|
1152
984
|
this.summary?.onTestModuleCollected(module);
|
|
1153
985
|
}
|
|
1154
986
|
onTestModuleEnd(module) {
|
|
1155
|
-
super.onTestModuleEnd(module);
|
|
1156
|
-
this.summary?.onTestModuleEnd(module);
|
|
987
|
+
super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
|
|
1157
988
|
}
|
|
1158
989
|
onTestCaseReady(test) {
|
|
1159
990
|
this.summary?.onTestCaseReady(test);
|
|
1160
991
|
}
|
|
1161
992
|
onTestCaseResult(test) {
|
|
1162
|
-
super.onTestCaseResult(test);
|
|
1163
|
-
this.summary?.onTestCaseResult(test);
|
|
993
|
+
super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
|
|
1164
994
|
}
|
|
1165
995
|
onHookStart(hook) {
|
|
1166
996
|
this.summary?.onHookStart(hook);
|
|
@@ -1169,11 +999,7 @@ class DefaultReporter extends BaseReporter {
|
|
|
1169
999
|
this.summary?.onHookEnd(hook);
|
|
1170
1000
|
}
|
|
1171
1001
|
onInit(ctx) {
|
|
1172
|
-
super.onInit(ctx);
|
|
1173
|
-
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1174
|
-
}
|
|
1175
|
-
onTestRunEnd() {
|
|
1176
|
-
this.summary?.onTestRunEnd();
|
|
1002
|
+
super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1177
1003
|
}
|
|
1178
1004
|
}
|
|
1179
1005
|
|
|
@@ -1182,30 +1008,22 @@ class DotReporter extends BaseReporter {
|
|
|
1182
1008
|
tests = /* @__PURE__ */ new Map();
|
|
1183
1009
|
finishedTests = /* @__PURE__ */ new Set();
|
|
1184
1010
|
onInit(ctx) {
|
|
1185
|
-
super.onInit(ctx)
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
getWindow: () => this.createSummary()
|
|
1190
|
-
});
|
|
1191
|
-
this.ctx.onClose(() => this.renderer?.stop());
|
|
1192
|
-
}
|
|
1011
|
+
if (super.onInit(ctx), this.isTTY) this.renderer = new WindowRenderer({
|
|
1012
|
+
logger: ctx.logger,
|
|
1013
|
+
getWindow: () => this.createSummary()
|
|
1014
|
+
}), this.ctx.onClose(() => this.renderer?.stop());
|
|
1193
1015
|
}
|
|
1194
1016
|
// Ignore default logging of base reporter
|
|
1195
1017
|
printTestModule() {}
|
|
1196
1018
|
onWatcherRerun(files, trigger) {
|
|
1197
|
-
this.tests.clear();
|
|
1198
|
-
this.renderer?.start();
|
|
1199
|
-
super.onWatcherRerun(files, trigger);
|
|
1019
|
+
this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
|
|
1200
1020
|
}
|
|
1201
|
-
|
|
1021
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
1202
1022
|
if (this.isTTY) {
|
|
1203
1023
|
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1204
1024
|
this.ctx.logger.log(finalLog);
|
|
1205
1025
|
} else this.ctx.logger.log();
|
|
1206
|
-
this.tests.clear();
|
|
1207
|
-
this.renderer?.finish();
|
|
1208
|
-
super.onFinished(files, errors);
|
|
1026
|
+
this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
1209
1027
|
}
|
|
1210
1028
|
onTestModuleCollected(module) {
|
|
1211
1029
|
for (const test of module.children.allTests())
|
|
@@ -1213,22 +1031,16 @@ class DotReporter extends BaseReporter {
|
|
|
1213
1031
|
this.onTestCaseReady(test);
|
|
1214
1032
|
}
|
|
1215
1033
|
onTestCaseReady(test) {
|
|
1216
|
-
|
|
1217
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1218
|
-
this.renderer?.schedule();
|
|
1034
|
+
this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
|
|
1219
1035
|
}
|
|
1220
1036
|
onTestCaseResult(test) {
|
|
1221
1037
|
const result = test.result().state;
|
|
1222
1038
|
// On non-TTY the finished tests are printed immediately
|
|
1223
1039
|
if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
|
|
1224
|
-
super.onTestCaseResult(test);
|
|
1225
|
-
this.finishedTests.add(test.id);
|
|
1226
|
-
this.tests.set(test.id, result || "skipped");
|
|
1227
|
-
this.renderer?.schedule();
|
|
1040
|
+
super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
|
|
1228
1041
|
}
|
|
1229
1042
|
onTestModuleEnd(testModule) {
|
|
1230
|
-
super.onTestModuleEnd(testModule);
|
|
1231
|
-
if (!this.isTTY) return;
|
|
1043
|
+
if (super.onTestModuleEnd(testModule), !this.isTTY) return;
|
|
1232
1044
|
const columns = this.ctx.logger.getColumns();
|
|
1233
1045
|
if (this.tests.size < columns) return;
|
|
1234
1046
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
@@ -1238,11 +1050,9 @@ class DotReporter extends BaseReporter {
|
|
|
1238
1050
|
let count = 0;
|
|
1239
1051
|
for (const [id, state] of finishedTests) {
|
|
1240
1052
|
if (count++ >= columns) break;
|
|
1241
|
-
this.tests.delete(id);
|
|
1242
|
-
states.push(state);
|
|
1053
|
+
this.tests.delete(id), states.push(state);
|
|
1243
1054
|
}
|
|
1244
|
-
this.ctx.logger.log(formatTests(states));
|
|
1245
|
-
this.renderer?.schedule();
|
|
1055
|
+
this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
|
|
1246
1056
|
}
|
|
1247
1057
|
createSummary() {
|
|
1248
1058
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
@@ -1252,16 +1062,13 @@ class DotReporter extends BaseReporter {
|
|
|
1252
1062
|
const pass = {
|
|
1253
1063
|
char: "·",
|
|
1254
1064
|
color: c.green
|
|
1255
|
-
}
|
|
1256
|
-
const fail = {
|
|
1065
|
+
}, fail = {
|
|
1257
1066
|
char: "x",
|
|
1258
1067
|
color: c.red
|
|
1259
|
-
}
|
|
1260
|
-
const pending = {
|
|
1068
|
+
}, pending = {
|
|
1261
1069
|
char: "*",
|
|
1262
1070
|
color: c.yellow
|
|
1263
|
-
}
|
|
1264
|
-
const skip = {
|
|
1071
|
+
}, skip = {
|
|
1265
1072
|
char: "-",
|
|
1266
1073
|
color: (char) => c.dim(c.gray(char))
|
|
1267
1074
|
};
|
|
@@ -1278,37 +1085,27 @@ function getIcon(state) {
|
|
|
1278
1085
|
* Sibling icons with same color are merged into a single c.color() call.
|
|
1279
1086
|
*/
|
|
1280
1087
|
function formatTests(states) {
|
|
1281
|
-
let currentIcon = pending;
|
|
1282
|
-
let count = 0;
|
|
1283
|
-
let output = "";
|
|
1088
|
+
let currentIcon = pending, count = 0, output = "";
|
|
1284
1089
|
for (const state of states) {
|
|
1285
1090
|
const icon = getIcon(state);
|
|
1286
1091
|
if (currentIcon === icon) {
|
|
1287
1092
|
count++;
|
|
1288
1093
|
continue;
|
|
1289
1094
|
}
|
|
1290
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1291
|
-
// Start tracking new group
|
|
1292
|
-
count = 1;
|
|
1293
|
-
currentIcon = icon;
|
|
1095
|
+
output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
|
|
1294
1096
|
}
|
|
1295
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1296
|
-
return output;
|
|
1097
|
+
return output += currentIcon.color(currentIcon.char.repeat(count)), output;
|
|
1297
1098
|
}
|
|
1298
1099
|
|
|
1299
1100
|
// use Logger with custom Console to capture entire error printing
|
|
1300
1101
|
function capturePrintError(error, ctx, options) {
|
|
1301
1102
|
let output = "";
|
|
1302
1103
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1303
|
-
output += String(chunk);
|
|
1304
|
-
|
|
1305
|
-
} });
|
|
1306
|
-
const console = new Console(writable);
|
|
1307
|
-
const logger = {
|
|
1104
|
+
output += String(chunk), callback();
|
|
1105
|
+
} }), console = new Console(writable), logger = {
|
|
1308
1106
|
error: console.error.bind(console),
|
|
1309
1107
|
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1310
|
-
}
|
|
1311
|
-
const result = printError(error, ctx, logger, {
|
|
1108
|
+
}, result = printError(error, ctx, logger, {
|
|
1312
1109
|
showCodeFrame: false,
|
|
1313
1110
|
...options
|
|
1314
1111
|
});
|
|
@@ -1326,11 +1123,13 @@ function printError(error, ctx, logger, options) {
|
|
|
1326
1123
|
screenshotPaths: options.screenshotPaths,
|
|
1327
1124
|
printProperties: options.verbose,
|
|
1328
1125
|
parseErrorStacktrace(error) {
|
|
1329
|
-
// browser stack trace needs to be processed differently,
|
|
1330
|
-
// so there is a separate method for that
|
|
1331
|
-
if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
|
|
1332
1126
|
// node.js stack trace already has correct source map locations
|
|
1333
|
-
return
|
|
1127
|
+
return error.stacks ? options.fullStack ? error.stacks : error.stacks.filter((stack) => {
|
|
1128
|
+
return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
|
|
1129
|
+
}) : options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, {
|
|
1130
|
+
frameFilter: project.config.onStackTrace,
|
|
1131
|
+
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1132
|
+
}) : parseErrorStacktrace(error, {
|
|
1334
1133
|
frameFilter: project.config.onStackTrace,
|
|
1335
1134
|
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1336
1135
|
});
|
|
@@ -1338,8 +1137,7 @@ function printError(error, ctx, logger, options) {
|
|
|
1338
1137
|
});
|
|
1339
1138
|
}
|
|
1340
1139
|
function printErrorInner(error, project, options) {
|
|
1341
|
-
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1342
|
-
const logger = options.logger;
|
|
1140
|
+
const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
|
|
1343
1141
|
let e = error;
|
|
1344
1142
|
if (isPrimitive(e)) e = {
|
|
1345
1143
|
message: String(error).split(/\n/g)[0],
|
|
@@ -1357,8 +1155,7 @@ function printErrorInner(error, project, options) {
|
|
|
1357
1155
|
printErrorMessage(e, logger);
|
|
1358
1156
|
return;
|
|
1359
1157
|
}
|
|
1360
|
-
const stacks = options.parseErrorStacktrace(e)
|
|
1361
|
-
const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1158
|
+
const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1362
1159
|
try {
|
|
1363
1160
|
const module = project._vite && project.getModuleById(stack.file);
|
|
1364
1161
|
return (module?.transformResult || module?.ssrTransformResult) && existsSync(stack.file);
|
|
@@ -1367,12 +1164,9 @@ function printErrorInner(error, project, options) {
|
|
|
1367
1164
|
}
|
|
1368
1165
|
});
|
|
1369
1166
|
if (type) printErrorType(type, project.vitest);
|
|
1370
|
-
printErrorMessage(e, logger)
|
|
1371
|
-
if (options.screenshotPaths?.length) {
|
|
1167
|
+
if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
|
|
1372
1168
|
const length = options.screenshotPaths.length;
|
|
1373
|
-
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1374
|
-
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1375
|
-
if (!e.diff) logger.error();
|
|
1169
|
+
if (logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`), logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n")), !e.diff) logger.error();
|
|
1376
1170
|
}
|
|
1377
1171
|
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1378
1172
|
if ("__vitest_rollup_error__" in e) {
|
|
@@ -1397,25 +1191,19 @@ function printErrorInner(error, project, options) {
|
|
|
1397
1191
|
}
|
|
1398
1192
|
});
|
|
1399
1193
|
}
|
|
1400
|
-
const testPath = e.VITEST_TEST_PATH;
|
|
1401
|
-
const testName = e.VITEST_TEST_NAME;
|
|
1402
|
-
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1194
|
+
const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1403
1195
|
// testName has testPath inside
|
|
1404
1196
|
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.`));
|
|
1405
1197
|
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:
|
|
1406
1198
|
- The error was thrown, while Vitest was running this test.
|
|
1407
1199
|
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1408
1200
|
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"));
|
|
1409
|
-
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
handleImportOutsideModuleError(e.stack || "", logger);
|
|
1418
|
-
return { nearest };
|
|
1201
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
|
|
1202
|
+
showCodeFrame: false,
|
|
1203
|
+
logger: options.logger,
|
|
1204
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1205
|
+
});
|
|
1206
|
+
return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
|
|
1419
1207
|
}
|
|
1420
1208
|
function printErrorType(type, ctx) {
|
|
1421
1209
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
@@ -1498,10 +1286,8 @@ function printErrorMessage(error, logger) {
|
|
|
1498
1286
|
}
|
|
1499
1287
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1500
1288
|
for (const frame of stack) {
|
|
1501
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
1502
|
-
|
|
1503
|
-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1504
|
-
onStack?.(frame);
|
|
1289
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
|
|
1290
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
|
|
1505
1291
|
}
|
|
1506
1292
|
if (stack.length) logger.error();
|
|
1507
1293
|
if (hasProperties(errorProperties)) {
|
|
@@ -1516,28 +1302,19 @@ function hasProperties(obj) {
|
|
|
1516
1302
|
return false;
|
|
1517
1303
|
}
|
|
1518
1304
|
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1519
|
-
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1520
|
-
|
|
1521
|
-
const lines = source.split(lineSplitRE);
|
|
1522
|
-
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1523
|
-
let count = 0;
|
|
1524
|
-
let res = [];
|
|
1305
|
+
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc, end = start, lines = source.split(lineSplitRE), nl = /\r\n/.test(source) ? 2 : 1;
|
|
1306
|
+
let count = 0, res = [];
|
|
1525
1307
|
const columns = process.stdout?.columns || 80;
|
|
1526
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
const lineLength = lines[j].length;
|
|
1532
|
-
const strippedContent = stripVTControlCharacters(lines[j]);
|
|
1533
|
-
if (strippedContent.startsWith("//# sourceMappingURL")) continue;
|
|
1308
|
+
for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
|
|
1309
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1310
|
+
if (j < 0 || j >= lines.length) continue;
|
|
1311
|
+
const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
|
|
1312
|
+
if (!strippedContent.startsWith("//# sourceMappingURL")) {
|
|
1534
1313
|
// too long, maybe it's a minified file, skip for codeframe
|
|
1535
1314
|
if (strippedContent.length > 200) return "";
|
|
1536
|
-
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent))
|
|
1537
|
-
if (j === i) {
|
|
1315
|
+
if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
|
|
1538
1316
|
// push underline
|
|
1539
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
1540
|
-
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1317
|
+
const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1541
1318
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1542
1319
|
} else if (j > i) {
|
|
1543
1320
|
if (end > count) {
|
|
@@ -1547,8 +1324,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1547
1324
|
count += lineLength + 1;
|
|
1548
1325
|
}
|
|
1549
1326
|
}
|
|
1550
|
-
break;
|
|
1551
1327
|
}
|
|
1328
|
+
break;
|
|
1552
1329
|
}
|
|
1553
1330
|
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1554
1331
|
return res.join("\n");
|
|
@@ -1568,8 +1345,7 @@ class GithubActionsReporter {
|
|
|
1568
1345
|
}
|
|
1569
1346
|
onTestCaseAnnotate(testCase, annotation) {
|
|
1570
1347
|
if (!annotation.location) return;
|
|
1571
|
-
const type = getTitle(annotation.type)
|
|
1572
|
-
const formatted = formatMessage({
|
|
1348
|
+
const type = getTitle(annotation.type), formatted = formatMessage({
|
|
1573
1349
|
command: getType(annotation.type),
|
|
1574
1350
|
properties: {
|
|
1575
1351
|
file: annotation.location.file,
|
|
@@ -1581,17 +1357,15 @@ class GithubActionsReporter {
|
|
|
1581
1357
|
});
|
|
1582
1358
|
this.ctx.logger.log(`\n${formatted}`);
|
|
1583
1359
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
const projectErrors = new Array();
|
|
1360
|
+
onTestRunEnd(testModules, unhandledErrors) {
|
|
1361
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
|
|
1587
1362
|
for (const error of errors) projectErrors.push({
|
|
1588
1363
|
project: this.ctx.getRootProject(),
|
|
1589
1364
|
title: "Unhandled error",
|
|
1590
1365
|
error
|
|
1591
1366
|
});
|
|
1592
1367
|
for (const file of files) {
|
|
1593
|
-
const tasks = getTasks(file);
|
|
1594
|
-
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1368
|
+
const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
|
|
1595
1369
|
for (const task of tasks) {
|
|
1596
1370
|
if (task.result?.state !== "fail") continue;
|
|
1597
1371
|
const title = getFullName(task, " > ");
|
|
@@ -1609,8 +1383,7 @@ class GithubActionsReporter {
|
|
|
1609
1383
|
const result = capturePrintError(error, this.ctx, {
|
|
1610
1384
|
project,
|
|
1611
1385
|
task: file
|
|
1612
|
-
});
|
|
1613
|
-
const stack = result?.nearest;
|
|
1386
|
+
}), stack = result?.nearest;
|
|
1614
1387
|
if (!stack) continue;
|
|
1615
1388
|
const formatted = formatMessage({
|
|
1616
1389
|
command: "error",
|
|
@@ -1632,12 +1405,10 @@ const BUILT_IN_TYPES = [
|
|
|
1632
1405
|
"warning"
|
|
1633
1406
|
];
|
|
1634
1407
|
function getTitle(type) {
|
|
1635
|
-
|
|
1636
|
-
return type;
|
|
1408
|
+
return BUILT_IN_TYPES.includes(type) ? void 0 : type;
|
|
1637
1409
|
}
|
|
1638
1410
|
function getType(type) {
|
|
1639
|
-
|
|
1640
|
-
return "notice";
|
|
1411
|
+
return BUILT_IN_TYPES.includes(type) ? type : "notice";
|
|
1641
1412
|
}
|
|
1642
1413
|
function defaultOnWritePath(path) {
|
|
1643
1414
|
return path;
|
|
@@ -1647,12 +1418,9 @@ function defaultOnWritePath(path) {
|
|
|
1647
1418
|
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1648
1419
|
function formatMessage({ command, properties, message }) {
|
|
1649
1420
|
let result = `::${command}`;
|
|
1650
|
-
Object.entries(properties).forEach(([k, v], i) => {
|
|
1651
|
-
result += i === 0 ? " " : ","
|
|
1652
|
-
|
|
1653
|
-
});
|
|
1654
|
-
result += `::${escapeData(message)}`;
|
|
1655
|
-
return result;
|
|
1421
|
+
return Object.entries(properties).forEach(([k, v], i) => {
|
|
1422
|
+
result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
|
|
1423
|
+
}), result += `::${escapeData(message)}`, result;
|
|
1656
1424
|
}
|
|
1657
1425
|
function escapeData(s) {
|
|
1658
1426
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -1685,41 +1453,27 @@ class JsonReporter {
|
|
|
1685
1453
|
start = 0;
|
|
1686
1454
|
ctx;
|
|
1687
1455
|
options;
|
|
1456
|
+
coverageMap;
|
|
1688
1457
|
constructor(options) {
|
|
1689
1458
|
this.options = options;
|
|
1690
1459
|
}
|
|
1691
1460
|
onInit(ctx) {
|
|
1692
|
-
this.ctx = ctx;
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const tests = getTests(files);
|
|
1699
|
-
const numTotalTests = tests.length;
|
|
1700
|
-
const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
|
|
1701
|
-
const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
|
|
1702
|
-
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
|
|
1703
|
-
const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
|
|
1704
|
-
const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
|
|
1705
|
-
const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
|
|
1706
|
-
const numTodoTests = tests.filter((t) => t.mode === "todo").length;
|
|
1707
|
-
const testResults = [];
|
|
1708
|
-
const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
|
|
1461
|
+
this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
|
|
1462
|
+
}
|
|
1463
|
+
onCoverage(coverageMap) {
|
|
1464
|
+
this.coverageMap = coverageMap;
|
|
1465
|
+
}
|
|
1466
|
+
async onTestRunEnd(testModules) {
|
|
1467
|
+
const files = testModules.map((testModule) => testModule.task), suites = getSuites(files), numTotalTestSuites = suites.length, tests = getTests(files), numTotalTests = tests.length, numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length, numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length, numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites, numFailedTests = tests.filter((t) => t.result?.state === "fail").length, numPassedTests = tests.filter((t) => t.result?.state === "pass").length, numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length, numTodoTests = tests.filter((t) => t.mode === "todo").length, testResults = [], success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
|
|
1709
1468
|
for (const file of files) {
|
|
1710
1469
|
const tests = getTests([file]);
|
|
1711
1470
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1712
1471
|
if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
|
|
1713
|
-
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime)
|
|
1714
|
-
const assertionResults = tests.map((t) => {
|
|
1472
|
+
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
|
|
1715
1473
|
const ancestorTitles = [];
|
|
1716
1474
|
let iter = t.suite;
|
|
1717
|
-
while (iter)
|
|
1718
|
-
|
|
1719
|
-
iter = iter.suite;
|
|
1720
|
-
}
|
|
1721
|
-
ancestorTitles.reverse();
|
|
1722
|
-
return {
|
|
1475
|
+
while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
|
|
1476
|
+
return ancestorTitles.reverse(), {
|
|
1723
1477
|
ancestorTitles,
|
|
1724
1478
|
fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
|
|
1725
1479
|
status: StatusMap[t.result?.state || t.mode] || "skipped",
|
|
@@ -1755,13 +1509,10 @@ class JsonReporter {
|
|
|
1755
1509
|
startTime: this.start,
|
|
1756
1510
|
success,
|
|
1757
1511
|
testResults,
|
|
1758
|
-
coverageMap
|
|
1512
|
+
coverageMap: this.coverageMap
|
|
1759
1513
|
};
|
|
1760
1514
|
await this.writeReport(JSON.stringify(result));
|
|
1761
1515
|
}
|
|
1762
|
-
async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
|
|
1763
|
-
await this.logTasks(files, coverageMap);
|
|
1764
|
-
}
|
|
1765
1516
|
/**
|
|
1766
1517
|
* Writes the report to an output file if specified in the config,
|
|
1767
1518
|
* or logs it to the console otherwise.
|
|
@@ -1770,11 +1521,9 @@ class JsonReporter {
|
|
|
1770
1521
|
async writeReport(report) {
|
|
1771
1522
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
1772
1523
|
if (outputFile) {
|
|
1773
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1774
|
-
const outputDirectory = dirname(reportFile);
|
|
1524
|
+
const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
|
|
1775
1525
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1776
|
-
await promises.writeFile(reportFile, report, "utf-8");
|
|
1777
|
-
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1526
|
+
await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1778
1527
|
} else this.ctx.logger.log(report);
|
|
1779
1528
|
}
|
|
1780
1529
|
}
|
|
@@ -1797,8 +1546,7 @@ class IndentedLogger {
|
|
|
1797
1546
|
|
|
1798
1547
|
function flattenTasks$1(task, baseName = "") {
|
|
1799
1548
|
const base = baseName ? `${baseName} > ` : "";
|
|
1800
|
-
|
|
1801
|
-
else return [{
|
|
1549
|
+
return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
|
|
1802
1550
|
...task,
|
|
1803
1551
|
name: `${base}${task.name}`
|
|
1804
1552
|
}];
|
|
@@ -1806,21 +1554,16 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1806
1554
|
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1807
1555
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1808
1556
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1809
|
-
value = String(value || "").replace(regex, "")
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
"g"
|
|
1816
|
-
/* eslint-enable */
|
|
1817
|
-
);
|
|
1818
|
-
value = value.replace(regex, "");
|
|
1819
|
-
}
|
|
1557
|
+
if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
|
|
1558
|
+
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1559
|
+
"([\\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]))",
|
|
1560
|
+
"g"
|
|
1561
|
+
/* eslint-enable */
|
|
1562
|
+
), value = value.replace(regex, "");
|
|
1820
1563
|
return value;
|
|
1821
1564
|
}
|
|
1822
1565
|
function escapeXML(value) {
|
|
1823
|
-
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
1566
|
+
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"), true);
|
|
1824
1567
|
}
|
|
1825
1568
|
function executionTime(durationMS) {
|
|
1826
1569
|
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
@@ -1841,8 +1584,7 @@ class JUnitReporter {
|
|
|
1841
1584
|
fileFd;
|
|
1842
1585
|
options;
|
|
1843
1586
|
constructor(options) {
|
|
1844
|
-
this.options = { ...options };
|
|
1845
|
-
this.options.includeConsoleOutput ??= true;
|
|
1587
|
+
this.options = { ...options }, this.options.includeConsoleOutput ??= true;
|
|
1846
1588
|
}
|
|
1847
1589
|
async onInit(ctx) {
|
|
1848
1590
|
this.ctx = ctx;
|
|
@@ -1852,14 +1594,12 @@ class JUnitReporter {
|
|
|
1852
1594
|
const outputDirectory = dirname(this.reportFile);
|
|
1853
1595
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1854
1596
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
1855
|
-
this.fileFd = fileFd
|
|
1856
|
-
this.baseLog = async (text) => {
|
|
1597
|
+
this.fileFd = fileFd, this.baseLog = async (text) => {
|
|
1857
1598
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
1858
1599
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
1859
1600
|
};
|
|
1860
1601
|
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1861
|
-
this._timeStart = /* @__PURE__ */ new Date();
|
|
1862
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
1602
|
+
this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
|
|
1863
1603
|
}
|
|
1864
1604
|
async writeElement(name, attrs, children) {
|
|
1865
1605
|
const pairs = [];
|
|
@@ -1868,18 +1608,12 @@ class JUnitReporter {
|
|
|
1868
1608
|
if (attr === void 0) continue;
|
|
1869
1609
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
1870
1610
|
}
|
|
1871
|
-
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
|
|
1872
|
-
this.logger.indent();
|
|
1873
|
-
await children.call(this);
|
|
1874
|
-
this.logger.unindent();
|
|
1875
|
-
await this.logger.log(`</${name}>`);
|
|
1611
|
+
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`), this.logger.indent(), await children.call(this), this.logger.unindent(), await this.logger.log(`</${name}>`);
|
|
1876
1612
|
}
|
|
1877
1613
|
async writeLogs(task, type) {
|
|
1878
1614
|
if (task.logs == null || task.logs.length === 0) return;
|
|
1879
|
-
const logType = type === "err" ? "stderr" : "stdout";
|
|
1880
|
-
|
|
1881
|
-
if (logs.length === 0) return;
|
|
1882
|
-
await this.writeElement(`system-${type}`, {}, async () => {
|
|
1615
|
+
const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
|
|
1616
|
+
logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
|
|
1883
1617
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
1884
1618
|
});
|
|
1885
1619
|
}
|
|
@@ -1898,20 +1632,12 @@ class JUnitReporter {
|
|
|
1898
1632
|
name: task.name,
|
|
1899
1633
|
time: getDuration(task)
|
|
1900
1634
|
}, async () => {
|
|
1901
|
-
if (this.options.includeConsoleOutput)
|
|
1902
|
-
await this.writeLogs(task, "out");
|
|
1903
|
-
await this.writeLogs(task, "err");
|
|
1904
|
-
}
|
|
1635
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
|
|
1905
1636
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1906
1637
|
if (task.type === "test" && task.annotations.length) {
|
|
1907
|
-
await this.logger.log("<properties>");
|
|
1908
|
-
this.logger.
|
|
1909
|
-
|
|
1910
|
-
await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
|
|
1911
|
-
await this.logger.log("</property>");
|
|
1912
|
-
}
|
|
1913
|
-
this.logger.unindent();
|
|
1914
|
-
await this.logger.log("</properties>");
|
|
1638
|
+
await this.logger.log("<properties>"), this.logger.indent();
|
|
1639
|
+
for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
|
|
1640
|
+
this.logger.unindent(), await this.logger.log("</properties>");
|
|
1915
1641
|
}
|
|
1916
1642
|
if (task.result?.state === "fail") {
|
|
1917
1643
|
const errors = task.result.errors || [];
|
|
@@ -1930,11 +1656,11 @@ class JUnitReporter {
|
|
|
1930
1656
|
});
|
|
1931
1657
|
}
|
|
1932
1658
|
}
|
|
1933
|
-
async
|
|
1659
|
+
async onTestRunEnd(testModules) {
|
|
1660
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1934
1661
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
1935
1662
|
const transformed = files.map((file) => {
|
|
1936
|
-
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task))
|
|
1937
|
-
const stats = tasks.reduce((stats, task) => {
|
|
1663
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
|
|
1938
1664
|
return {
|
|
1939
1665
|
passed: stats.passed + Number(task.result?.state === "pass"),
|
|
1940
1666
|
failures: stats.failures + Number(task.result?.state === "fail"),
|
|
@@ -1944,41 +1670,29 @@ class JUnitReporter {
|
|
|
1944
1670
|
passed: 0,
|
|
1945
1671
|
failures: 0,
|
|
1946
1672
|
skipped: 0
|
|
1947
|
-
});
|
|
1948
|
-
|
|
1949
|
-
const suites = getSuites(file);
|
|
1950
|
-
for (const suite of suites) if (suite.result?.errors) {
|
|
1951
|
-
tasks.push(suite);
|
|
1952
|
-
stats.failures += 1;
|
|
1953
|
-
}
|
|
1673
|
+
}), suites = getSuites(file);
|
|
1674
|
+
for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
|
|
1954
1675
|
// If there are no tests, but the file failed to load, we still want to report it as a failure
|
|
1955
|
-
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
annotations: []
|
|
1969
|
-
});
|
|
1970
|
-
}
|
|
1676
|
+
if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
|
|
1677
|
+
id: file.id,
|
|
1678
|
+
type: "test",
|
|
1679
|
+
name: file.name,
|
|
1680
|
+
mode: "run",
|
|
1681
|
+
result: file.result,
|
|
1682
|
+
meta: {},
|
|
1683
|
+
timeout: 0,
|
|
1684
|
+
context: null,
|
|
1685
|
+
suite: null,
|
|
1686
|
+
file: null,
|
|
1687
|
+
annotations: []
|
|
1688
|
+
});
|
|
1971
1689
|
return {
|
|
1972
1690
|
...file,
|
|
1973
1691
|
tasks,
|
|
1974
1692
|
stats
|
|
1975
1693
|
};
|
|
1976
|
-
})
|
|
1977
|
-
|
|
1978
|
-
stats.tests += file.tasks.length;
|
|
1979
|
-
stats.failures += file.stats.failures;
|
|
1980
|
-
stats.time += file.result?.duration || 0;
|
|
1981
|
-
return stats;
|
|
1694
|
+
}), stats = transformed.reduce((stats, file) => {
|
|
1695
|
+
return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
|
|
1982
1696
|
}, {
|
|
1983
1697
|
name: this.options.suiteName || "vitest tests",
|
|
1984
1698
|
tests: 0,
|
|
@@ -1986,7 +1700,7 @@ class JUnitReporter {
|
|
|
1986
1700
|
errors: 0,
|
|
1987
1701
|
time: 0
|
|
1988
1702
|
});
|
|
1989
|
-
await this.writeElement("testsuites", {
|
|
1703
|
+
if (await this.writeElement("testsuites", {
|
|
1990
1704
|
...stats,
|
|
1991
1705
|
time: executionTime(stats.time)
|
|
1992
1706
|
}, async () => {
|
|
@@ -2005,16 +1719,13 @@ class JUnitReporter {
|
|
|
2005
1719
|
await this.writeTasks(file.tasks, filename);
|
|
2006
1720
|
});
|
|
2007
1721
|
}
|
|
2008
|
-
});
|
|
2009
|
-
|
|
2010
|
-
await this.fileFd?.close();
|
|
2011
|
-
this.fileFd = void 0;
|
|
1722
|
+
}), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
1723
|
+
await this.fileFd?.close(), this.fileFd = void 0;
|
|
2012
1724
|
}
|
|
2013
1725
|
}
|
|
2014
1726
|
|
|
2015
1727
|
function yamlString(str) {
|
|
2016
|
-
|
|
2017
|
-
return `"${str.replace(/"/g, "\\\"")}"`;
|
|
1728
|
+
return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
|
|
2018
1729
|
}
|
|
2019
1730
|
function tapString(str) {
|
|
2020
1731
|
return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
|
|
@@ -2023,77 +1734,45 @@ class TapReporter {
|
|
|
2023
1734
|
ctx;
|
|
2024
1735
|
logger;
|
|
2025
1736
|
onInit(ctx) {
|
|
2026
|
-
this.ctx = ctx;
|
|
2027
|
-
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
1737
|
+
this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2028
1738
|
}
|
|
2029
1739
|
static getComment(task) {
|
|
2030
|
-
|
|
2031
|
-
else if (task.mode === "todo") return " # TODO";
|
|
2032
|
-
else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2033
|
-
else return "";
|
|
1740
|
+
return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2034
1741
|
}
|
|
2035
1742
|
logErrorDetails(error, stack) {
|
|
2036
1743
|
const errorName = error.name || "Unknown Error";
|
|
2037
|
-
this.logger.log(`name: ${yamlString(String(errorName))}`)
|
|
2038
|
-
this.logger.log(`message: ${yamlString(String(error.message))}`);
|
|
2039
|
-
if (stack)
|
|
1744
|
+
if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
|
|
2040
1745
|
// For compatibility with tap-mocha-reporter
|
|
2041
1746
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2042
1747
|
}
|
|
2043
1748
|
logTasks(tasks) {
|
|
2044
1749
|
this.logger.log(`1..${tasks.length}`);
|
|
2045
1750
|
for (const [i, task] of tasks.entries()) {
|
|
2046
|
-
const id = i + 1;
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
if (task.type === "suite" && task.tasks.length > 0) {
|
|
2050
|
-
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
|
|
2051
|
-
this.logger.indent();
|
|
2052
|
-
this.logTasks(task.tasks);
|
|
2053
|
-
this.logger.unindent();
|
|
2054
|
-
this.logger.log("}");
|
|
2055
|
-
} else {
|
|
1751
|
+
const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
|
|
1752
|
+
if (task.type === "suite" && task.tasks.length > 0) this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`), this.logger.indent(), this.logTasks(task.tasks), this.logger.unindent(), this.logger.log("}");
|
|
1753
|
+
else {
|
|
2056
1754
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2057
1755
|
const project = this.ctx.getProjectByName(task.file.projectName || "");
|
|
2058
|
-
if (task.type === "test" && task.annotations) {
|
|
2059
|
-
this.logger.
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
});
|
|
2063
|
-
this.logger.unindent();
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
this.logger.indent();
|
|
2067
|
-
task.result.errors.forEach((error) => {
|
|
2068
|
-
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace });
|
|
2069
|
-
const stack = stacks[0];
|
|
2070
|
-
this.logger.log("---");
|
|
2071
|
-
this.logger.log("error:");
|
|
2072
|
-
this.logger.indent();
|
|
2073
|
-
this.logErrorDetails(error);
|
|
2074
|
-
this.logger.unindent();
|
|
2075
|
-
if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2076
|
-
if (error.showDiff) {
|
|
2077
|
-
this.logger.log(`actual: ${yamlString(error.actual)}`);
|
|
2078
|
-
this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
2079
|
-
}
|
|
2080
|
-
});
|
|
2081
|
-
this.logger.log("...");
|
|
2082
|
-
this.logger.unindent();
|
|
2083
|
-
}
|
|
1756
|
+
if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
|
|
1757
|
+
this.logger.log(`# ${type}: ${message}`);
|
|
1758
|
+
}), this.logger.unindent();
|
|
1759
|
+
if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
|
|
1760
|
+
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
|
|
1761
|
+
if (this.logger.log("---"), this.logger.log("error:"), this.logger.indent(), this.logErrorDetails(error), this.logger.unindent(), stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
1762
|
+
if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
1763
|
+
}), this.logger.log("..."), this.logger.unindent();
|
|
2084
1764
|
}
|
|
2085
1765
|
}
|
|
2086
1766
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
this.logTasks(files);
|
|
1767
|
+
onTestRunEnd(testModules) {
|
|
1768
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1769
|
+
this.logger.log("TAP version 13"), this.logTasks(files);
|
|
2090
1770
|
}
|
|
2091
1771
|
}
|
|
2092
1772
|
|
|
2093
1773
|
function flattenTasks(task, baseName = "") {
|
|
2094
1774
|
const base = baseName ? `${baseName} > ` : "";
|
|
2095
|
-
|
|
2096
|
-
else return [{
|
|
1775
|
+
return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
|
|
2097
1776
|
...task,
|
|
2098
1777
|
name: `${base}${task.name}`
|
|
2099
1778
|
}];
|
|
@@ -2102,9 +1781,9 @@ class TapFlatReporter extends TapReporter {
|
|
|
2102
1781
|
onInit(ctx) {
|
|
2103
1782
|
super.onInit(ctx);
|
|
2104
1783
|
}
|
|
2105
|
-
|
|
1784
|
+
onTestRunEnd(testModules) {
|
|
2106
1785
|
this.ctx.logger.log("TAP version 13");
|
|
2107
|
-
const flatTasks =
|
|
1786
|
+
const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
|
|
2108
1787
|
this.logTasks(flatTasks);
|
|
2109
1788
|
}
|
|
2110
1789
|
}
|
|
@@ -2120,31 +1799,22 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2120
1799
|
if (this.isTTY) return super.printTestModule(module);
|
|
2121
1800
|
}
|
|
2122
1801
|
onTestCaseResult(test) {
|
|
2123
|
-
super.onTestCaseResult(test);
|
|
2124
1802
|
// don't print tests in TTY as they go, only print them
|
|
2125
1803
|
// in the CLI when they finish
|
|
2126
|
-
if (this.isTTY) return;
|
|
1804
|
+
if (super.onTestCaseResult(test), this.isTTY) return;
|
|
2127
1805
|
const testResult = test.result();
|
|
2128
1806
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2129
1807
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2130
1808
|
if (test.project.name) title += formatProjectName(test.project);
|
|
2131
|
-
title += getFullName(test.task, c.dim(" > "));
|
|
2132
|
-
title += this.getDurationPrefix(test.task);
|
|
1809
|
+
title += getFullName(test.task, c.dim(" > ")), title += this.getDurationPrefix(test.task);
|
|
2133
1810
|
const diagnostic = test.diagnostic();
|
|
2134
1811
|
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2135
1812
|
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2136
|
-
this.log(title);
|
|
2137
|
-
if (
|
|
2138
|
-
if (test.annotations().length) {
|
|
2139
|
-
this.log();
|
|
2140
|
-
this.printAnnotations(test, "log", 3);
|
|
2141
|
-
this.log();
|
|
2142
|
-
}
|
|
1813
|
+
if (this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
|
|
1814
|
+
if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
|
|
2143
1815
|
}
|
|
2144
1816
|
printTestSuite(testSuite) {
|
|
2145
|
-
const indentation = " ".repeat(getIndentation(testSuite.task));
|
|
2146
|
-
const tests = Array.from(testSuite.children.allTests());
|
|
2147
|
-
const state = getStateSymbol(testSuite.task);
|
|
1817
|
+
const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = getStateSymbol(testSuite.task);
|
|
2148
1818
|
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
2149
1819
|
}
|
|
2150
1820
|
getTestName(test) {
|
|
@@ -2159,8 +1829,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2159
1829
|
}
|
|
2160
1830
|
}
|
|
2161
1831
|
function getIndentation(suite, level = 1) {
|
|
2162
|
-
|
|
2163
|
-
return level;
|
|
1832
|
+
return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
|
|
2164
1833
|
}
|
|
2165
1834
|
|
|
2166
1835
|
const ReportersMap = {
|