vitest 4.0.0-beta.4 → 4.0.0-beta.6
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 +8 -9
- package/dist/browser.js +3 -2
- package/dist/chunks/base.BXI97p6t.js +39 -0
- package/dist/chunks/{benchmark.CYdenmiT.js → benchmark.UW6Ezvxy.js} +6 -8
- package/dist/chunks/{browser.d.BRP8scJf.d.ts → browser.d.Cawq_X_N.d.ts} +1 -1
- package/dist/chunks/{cac.CY0IAxC4.js → cac.WE-urWw5.js} +38 -115
- package/dist/chunks/{cli-api.B8xRY9Zt.js → cli-api.CZz3evYC.js} +931 -1439
- package/dist/chunks/{config.d.DZo8c7fw.d.ts → config.d.CKNVOKm0.d.ts} +3 -8
- package/dist/chunks/{console.DoJHFxmj.js → console.B0quX7yH.js} +32 -68
- package/dist/chunks/{constants.CXzqaLmq.js → constants.D_Q9UYh-.js} +1 -6
- package/dist/chunks/{coverage.C84l9G-M.js → coverage.BPRS6xgn.js} +395 -665
- package/dist/chunks/{coverage.DVF1vEu8.js → coverage.D_JHT54q.js} +2 -2
- package/dist/chunks/{coverage.d.CNYjU4GF.d.ts → coverage.d.BZtK59WP.d.ts} +7 -5
- package/dist/chunks/{creator.yfA2ExGt.js → creator.KEg6n5IC.js} +29 -75
- package/dist/chunks/{date.Bq6ZW5rf.js → date.-jtEtIeV.js} +6 -17
- package/dist/chunks/{environment.d.Bhm9oc0v.d.ts → environment.d.2fYMoz3o.d.ts} +26 -4
- package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
- package/dist/chunks/{global.d.DAhT2emn.d.ts → global.d.K6uBQHzY.d.ts} +1 -1
- package/dist/chunks/{globals.Dgo-vS5G.js → globals.lgsmH00r.js} +7 -6
- package/dist/chunks/{index.D3SKT3tv.js → index.7w0eqmYM.js} +14 -24
- package/dist/chunks/{index.D1_MsKEt.js → index.AR8aAkCC.js} +4 -2
- package/dist/chunks/{index.CmSc2RE5.js → index.BG0gqZH-.js} +43 -106
- package/dist/chunks/{index.CtUvr1c8.js → index.CsFXYRkW.js} +27 -46
- package/dist/chunks/{index.Bz6b0Ib7.js → index.VNI-1z5c.js} +276 -604
- package/dist/chunks/{inspector.C914Efll.js → inspector.CvQD-Nie.js} +10 -25
- package/dist/chunks/moduleRunner.d.8kKUsuDg.d.ts +202 -0
- package/dist/chunks/moduleTransport.I-bgQy0S.js +19 -0
- package/dist/chunks/{node.fjCdwEIl.js → node.BOqcT2jW.js} +1 -1
- package/dist/chunks/{plugin.d.CLhMcYdD.d.ts → plugin.d.DuiQJfUL.d.ts} +1 -1
- package/dist/chunks/{reporters.d.DWg40D2B.d.ts → reporters.d.CqR9-CDJ.d.ts} +52 -101
- package/dist/chunks/resolver.Bx6lE0iq.js +119 -0
- package/dist/chunks/{rpc.jnQO9F8a.js → rpc.RpPylpp0.js} +7 -21
- package/dist/chunks/runBaseTests.D6sfuWBM.js +99 -0
- package/dist/chunks/{setup-common.Ebx5x0eP.js → setup-common.hLGRxhC8.js} +15 -27
- package/dist/chunks/startModuleRunner.C8TW8zTN.js +655 -0
- package/dist/chunks/{typechecker.CMNPqJOo.js → typechecker.Cd1wvxUM.js} +97 -209
- package/dist/chunks/{utils.CcGm2cd1.js → utils.C2YI6McM.js} +4 -13
- package/dist/chunks/{utils.XdZDrNZV.js → utils.C7__0Iv5.js} +7 -17
- package/dist/chunks/{vi.CA0EPI9Y.js → vi.BfdOiD4j.js} +116 -269
- package/dist/chunks/{vm.BUnLJt_P.js → vm.BHBje7cC.js} +101 -225
- package/dist/chunks/{worker.d.zjyR34Pb.d.ts → worker.d.D9QWnzAe.d.ts} +16 -13
- package/dist/chunks/{worker.d.C-1AbnVe.d.ts → worker.d.Db-UVmXc.d.ts} +1 -1
- package/dist/cli.js +4 -4
- package/dist/config.cjs +3 -9
- package/dist/config.d.ts +10 -12
- package/dist/config.js +1 -1
- package/dist/coverage.d.ts +10 -11
- package/dist/coverage.js +5 -6
- package/dist/environments.d.ts +2 -2
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.js +6 -5
- package/dist/module-evaluator.d.ts +12 -0
- package/dist/module-evaluator.js +276 -0
- package/dist/module-runner.js +15 -0
- package/dist/node.d.ts +12 -13
- package/dist/node.js +19 -24
- package/dist/reporters.d.ts +7 -8
- package/dist/reporters.js +3 -3
- package/dist/runners.d.ts +3 -3
- package/dist/runners.js +35 -57
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +82 -45
- package/dist/workers/forks.js +11 -10
- package/dist/workers/runVmTests.js +27 -46
- package/dist/workers/threads.js +11 -10
- package/dist/workers/vmForks.js +11 -10
- package/dist/workers/vmThreads.js +11 -10
- package/dist/workers.d.ts +5 -4
- package/dist/workers.js +17 -16
- package/package.json +22 -17
- package/dist/chunks/base.BaCDDRPG.js +0 -38
- package/dist/chunks/execute.Dt-pCVcL.js +0 -708
- package/dist/chunks/runBaseTests.DBVVLMSb.js +0 -129
- package/dist/execute.d.ts +0 -148
- package/dist/execute.js +0 -13
|
@@ -1,7 +1,7 @@
|
|
|
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.Cd1wvxUM.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';
|
|
@@ -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,23 +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
|
-
|
|
208
|
-
|
|
198
|
+
moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
|
|
199
|
+
code: " ",
|
|
200
|
+
map: null
|
|
201
|
+
}, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
209
202
|
});
|
|
210
203
|
});
|
|
211
204
|
});
|
|
212
205
|
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
213
|
-
const time1 = f1.result?.startTime || 0;
|
|
214
|
-
const time2 = f2.result?.startTime || 0;
|
|
206
|
+
const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
|
|
215
207
|
return time1 - time2;
|
|
216
|
-
});
|
|
217
|
-
const errors = blobs.flatMap((blob) => blob.errors);
|
|
218
|
-
const coverages = blobs.map((blob) => blob.coverage);
|
|
219
|
-
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);
|
|
220
209
|
return {
|
|
221
210
|
files,
|
|
222
211
|
errors,
|
|
@@ -258,26 +247,18 @@ function errorBanner(message) {
|
|
|
258
247
|
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
259
248
|
}
|
|
260
249
|
function divider(text, left, right, color) {
|
|
261
|
-
const cols = getCols();
|
|
262
|
-
const c = color || ((text) => text);
|
|
250
|
+
const cols = getCols(), c = color || ((text) => text);
|
|
263
251
|
if (text) {
|
|
264
252
|
const textLength = stripVTControlCharacters(text).length;
|
|
265
253
|
if (left == null && right != null) left = cols - textLength - right;
|
|
266
|
-
else
|
|
267
|
-
|
|
268
|
-
right = cols - textLength - left;
|
|
269
|
-
}
|
|
270
|
-
left = Math.max(0, left);
|
|
271
|
-
right = Math.max(0, right);
|
|
272
|
-
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))}`;
|
|
273
256
|
}
|
|
274
257
|
return F_LONG_DASH.repeat(cols);
|
|
275
258
|
}
|
|
276
259
|
function formatTestPath(root, path) {
|
|
277
260
|
if (isAbsolute(path)) path = relative(root, path);
|
|
278
|
-
const dir = dirname(path);
|
|
279
|
-
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
280
|
-
const base = basename(path, ext);
|
|
261
|
+
const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
|
|
281
262
|
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
282
263
|
}
|
|
283
264
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
@@ -289,8 +270,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
289
270
|
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
290
271
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
291
272
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
292
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`)
|
|
293
|
-
tail.forEach((key) => {
|
|
273
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
|
|
294
274
|
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
295
275
|
});
|
|
296
276
|
}
|
|
@@ -298,8 +278,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
298
278
|
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
299
279
|
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
300
280
|
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
301
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
302
|
-
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}`));
|
|
303
282
|
});
|
|
304
283
|
}
|
|
305
284
|
return summary;
|
|
@@ -309,10 +288,7 @@ function countTestErrors(tasks) {
|
|
|
309
288
|
}
|
|
310
289
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
311
290
|
if (tasks.length === 0) return c.dim(`no ${name}`);
|
|
312
|
-
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
313
|
-
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
314
|
-
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
315
|
-
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");
|
|
316
292
|
return [
|
|
317
293
|
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
318
294
|
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
@@ -326,16 +302,13 @@ function getStateSymbol(task) {
|
|
|
326
302
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
327
303
|
if (task.type === "suite") return pointer;
|
|
328
304
|
}
|
|
329
|
-
|
|
330
|
-
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
331
|
-
return " ";
|
|
305
|
+
return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
|
|
332
306
|
}
|
|
333
307
|
function formatTimeString(date) {
|
|
334
308
|
return date.toTimeString().split(" ")[0];
|
|
335
309
|
}
|
|
336
310
|
function formatTime(time) {
|
|
337
|
-
|
|
338
|
-
return `${Math.round(time)}ms`;
|
|
311
|
+
return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
|
|
339
312
|
}
|
|
340
313
|
function formatProjectName(project, suffix = " ") {
|
|
341
314
|
if (!project?.name) return "";
|
|
@@ -356,8 +329,7 @@ function padSummaryTitle(str) {
|
|
|
356
329
|
}
|
|
357
330
|
function truncateString(text, maxLength) {
|
|
358
331
|
const plainText = stripVTControlCharacters(text);
|
|
359
|
-
|
|
360
|
-
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
332
|
+
return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
|
|
361
333
|
}
|
|
362
334
|
function capitalize(text) {
|
|
363
335
|
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
@@ -403,9 +375,7 @@ class BaseReporter {
|
|
|
403
375
|
this.isTTY = options.isTTY ?? isTTY;
|
|
404
376
|
}
|
|
405
377
|
onInit(ctx) {
|
|
406
|
-
this.ctx = ctx;
|
|
407
|
-
this.ctx.logger.printBanner();
|
|
408
|
-
this.start = performance$1.now();
|
|
378
|
+
this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
|
|
409
379
|
}
|
|
410
380
|
log(...messages) {
|
|
411
381
|
this.ctx.logger.log(...messages);
|
|
@@ -416,9 +386,9 @@ class BaseReporter {
|
|
|
416
386
|
relative(path) {
|
|
417
387
|
return relative(this.ctx.config.root, path);
|
|
418
388
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
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);
|
|
422
392
|
else this.reportSummary(files, errors);
|
|
423
393
|
}
|
|
424
394
|
onTestCaseResult(testCase) {
|
|
@@ -437,13 +407,10 @@ class BaseReporter {
|
|
|
437
407
|
printTestModule(testModule) {
|
|
438
408
|
const moduleState = testModule.state();
|
|
439
409
|
if (moduleState === "queued" || moduleState === "pending") return;
|
|
440
|
-
let testsCount = 0;
|
|
441
|
-
let failedCount = 0;
|
|
442
|
-
let skippedCount = 0;
|
|
410
|
+
let testsCount = 0, failedCount = 0, skippedCount = 0;
|
|
443
411
|
// delaying logs to calculate the test stats first
|
|
444
412
|
// which minimizes the amount of for loops
|
|
445
|
-
const logs = [];
|
|
446
|
-
const originalLog = this.log.bind(this);
|
|
413
|
+
const logs = [], originalLog = this.log.bind(this);
|
|
447
414
|
this.log = (msg) => logs.push(msg);
|
|
448
415
|
const visit = (suiteState, children) => {
|
|
449
416
|
for (const child of children) if (child.type === "suite") {
|
|
@@ -453,8 +420,7 @@ class BaseReporter {
|
|
|
453
420
|
visit(suiteState, child.children);
|
|
454
421
|
} else {
|
|
455
422
|
const testResult = child.result();
|
|
456
|
-
testsCount++;
|
|
457
|
-
if (testResult.state === "failed") failedCount++;
|
|
423
|
+
if (testsCount++, testResult.state === "failed") failedCount++;
|
|
458
424
|
else if (testResult.state === "skipped") skippedCount++;
|
|
459
425
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
460
426
|
// Skipped suites are hidden when --hideSkippedTests
|
|
@@ -471,24 +437,20 @@ class BaseReporter {
|
|
|
471
437
|
tests: testsCount,
|
|
472
438
|
failed: failedCount,
|
|
473
439
|
skipped: skippedCount
|
|
474
|
-
}));
|
|
475
|
-
logs.forEach((log) => this.log(log));
|
|
440
|
+
})), logs.forEach((log) => this.log(log));
|
|
476
441
|
}
|
|
477
442
|
printTestCase(moduleState, test) {
|
|
478
|
-
const testResult = test.result();
|
|
479
|
-
const { duration, retryCount, repeatCount } = test.diagnostic() || {};
|
|
480
|
-
const padding = this.getTestIndentation(test.task);
|
|
443
|
+
const testResult = test.result(), { duration, retryCount, repeatCount } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task);
|
|
481
444
|
let suffix = this.getDurationPrefix(test.task);
|
|
482
445
|
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
483
446
|
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
484
|
-
if (testResult.state === "failed")
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} 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}`);
|
|
492
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}]`))}`);
|
|
493
455
|
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
494
456
|
}
|
|
@@ -547,10 +509,8 @@ class BaseReporter {
|
|
|
547
509
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
548
510
|
}
|
|
549
511
|
onWatcherRerun(files, trigger) {
|
|
550
|
-
this.watchFilters = files;
|
|
551
|
-
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
552
512
|
// Update re-run count for each file
|
|
553
|
-
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) => {
|
|
554
514
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
555
515
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
556
516
|
});
|
|
@@ -559,35 +519,26 @@ class BaseReporter {
|
|
|
559
519
|
const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
|
|
560
520
|
banner += c.blue(`x${rerun} `);
|
|
561
521
|
}
|
|
562
|
-
this.ctx.logger.clearFullScreen();
|
|
563
|
-
this.log(withLabel("blue", "RERUN", banner));
|
|
564
|
-
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(", ")));
|
|
565
523
|
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
566
524
|
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
567
525
|
this.log("");
|
|
568
526
|
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
569
|
-
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
570
|
-
this.start = performance$1.now();
|
|
527
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
|
|
571
528
|
}
|
|
572
529
|
onUserConsoleLog(log, taskState) {
|
|
573
530
|
if (!this.shouldLog(log, taskState)) return;
|
|
574
|
-
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
|
|
575
|
-
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);
|
|
576
532
|
let headerText = "unknown test";
|
|
577
533
|
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
578
534
|
if (task) headerText = this.getFullName(task, c.dim(" > "));
|
|
579
535
|
else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
|
|
580
|
-
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content)
|
|
581
|
-
if (log.origin) {
|
|
536
|
+
if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
|
|
582
537
|
// browser logs don't have an extra end of line at the end like Node.js does
|
|
583
538
|
if (log.browser) write("\n");
|
|
584
|
-
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
585
|
-
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
586
|
-
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);
|
|
587
540
|
for (const frame of stack) {
|
|
588
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
589
|
-
const path = relative(project.config.root, frame.file);
|
|
590
|
-
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(" ");
|
|
591
542
|
write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
|
|
592
543
|
}
|
|
593
544
|
}
|
|
@@ -597,12 +548,9 @@ class BaseReporter {
|
|
|
597
548
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
598
549
|
}
|
|
599
550
|
shouldLog(log, taskState) {
|
|
600
|
-
if (this.ctx.config.silent === true) return false;
|
|
601
|
-
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;
|
|
602
552
|
if (this.ctx.config.onConsoleLog) {
|
|
603
|
-
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
604
|
-
const entity = task && this.ctx.state.getReportedEntity(task);
|
|
605
|
-
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);
|
|
606
554
|
if (shouldLog === false) return shouldLog;
|
|
607
555
|
}
|
|
608
556
|
return true;
|
|
@@ -611,41 +559,27 @@ class BaseReporter {
|
|
|
611
559
|
this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
|
|
612
560
|
}
|
|
613
561
|
reportSummary(files, errors) {
|
|
614
|
-
this.printErrorsSummary(files, errors);
|
|
615
|
-
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
562
|
+
if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
616
563
|
else this.reportTestSummary(files, errors);
|
|
617
564
|
}
|
|
618
565
|
reportTestSummary(files, errors) {
|
|
619
566
|
this.log();
|
|
620
|
-
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
|
|
621
|
-
const tests = getTests(affectedFiles);
|
|
622
|
-
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);
|
|
623
568
|
for (const [index, snapshot] of snapshotOutput.entries()) {
|
|
624
569
|
const title = index === 0 ? "Snapshots" : "";
|
|
625
570
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
626
571
|
}
|
|
627
572
|
if (snapshotOutput.length > 1) this.log();
|
|
628
|
-
this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles))
|
|
629
|
-
this.log(padSummaryTitle("Tests"), getStateString$1(tests));
|
|
630
|
-
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)) {
|
|
631
574
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
632
575
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
633
576
|
}
|
|
634
577
|
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
635
578
|
this.log(padSummaryTitle("Start at"), this._timeStart);
|
|
636
|
-
const collectTime = sum(files, (file) => file.collectDuration);
|
|
637
|
-
const testsTime = sum(files, (file) => file.result?.duration);
|
|
638
|
-
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);
|
|
639
580
|
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
640
581
|
else {
|
|
641
|
-
const blobs = this.ctx.state.blobs
|
|
642
|
-
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
643
|
-
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
644
|
-
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
645
|
-
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
646
|
-
const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
|
|
647
|
-
const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
|
|
648
|
-
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 = [
|
|
649
583
|
`transform ${formatTime(transformTime)}`,
|
|
650
584
|
`setup ${formatTime(setupTime)}`,
|
|
651
585
|
`collect ${formatTime(collectTime)}`,
|
|
@@ -654,41 +588,25 @@ class BaseReporter {
|
|
|
654
588
|
`prepare ${formatTime(prepareTime)}`,
|
|
655
589
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
656
590
|
].filter(Boolean).join(", ");
|
|
657
|
-
this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
|
|
658
|
-
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(""));
|
|
659
592
|
}
|
|
660
593
|
this.log();
|
|
661
594
|
}
|
|
662
595
|
printErrorsSummary(files, errors) {
|
|
663
|
-
const suites = getSuites(files);
|
|
664
|
-
const tests = getTests(files);
|
|
665
|
-
const failedSuites = suites.filter((i) => i.result?.errors);
|
|
666
|
-
const failedTests = tests.filter((i) => i.result?.state === "fail");
|
|
667
|
-
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);
|
|
668
597
|
let current = 1;
|
|
669
598
|
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
|
|
670
|
-
if (failedSuites.length) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
if (failedTests.length) {
|
|
675
|
-
this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
|
|
676
|
-
this.printTaskErrors(failedTests, errorDivider);
|
|
677
|
-
}
|
|
678
|
-
if (errors.length) {
|
|
679
|
-
this.ctx.logger.printUnhandledErrors(errors);
|
|
680
|
-
this.error();
|
|
681
|
-
}
|
|
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();
|
|
682
602
|
}
|
|
683
603
|
reportBenchmarkSummary(files) {
|
|
684
|
-
const benches = getTests(files);
|
|
685
|
-
const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
604
|
+
const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
686
605
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
687
606
|
for (const bench of topBenches) {
|
|
688
607
|
const group = bench.suite || bench.file;
|
|
689
608
|
if (!group) continue;
|
|
690
|
-
const groupName = this.getFullName(group, c.dim(" > "));
|
|
691
|
-
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);
|
|
692
610
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
693
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);
|
|
694
612
|
for (const sibling of siblings) {
|
|
@@ -706,10 +624,7 @@ class BaseReporter {
|
|
|
706
624
|
let previous;
|
|
707
625
|
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
708
626
|
if (i[0]?.stack !== error.stack) return false;
|
|
709
|
-
const currentProjectName = task?.projectName || task.file?.projectName || "";
|
|
710
|
-
const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
|
|
711
|
-
const currentAnnotations = task.type === "test" && task.annotations;
|
|
712
|
-
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;
|
|
713
628
|
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
714
629
|
});
|
|
715
630
|
if (previous) previous[1].push(task);
|
|
@@ -717,24 +632,20 @@ class BaseReporter {
|
|
|
717
632
|
});
|
|
718
633
|
for (const [error, tasks] of errorsQueue) {
|
|
719
634
|
for (const task of tasks) {
|
|
720
|
-
const filepath = task?.filepath || "";
|
|
721
|
-
const projectName = task?.projectName || task.file?.projectName || "";
|
|
722
|
-
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);
|
|
723
636
|
let name = this.getFullName(task, c.dim(" > "));
|
|
724
637
|
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
725
638
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
726
639
|
}
|
|
727
640
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
728
|
-
this.ctx.logger.printError(error, {
|
|
641
|
+
if (this.ctx.logger.printError(error, {
|
|
729
642
|
project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
|
|
730
643
|
verbose: this.verbose,
|
|
731
644
|
screenshotPaths,
|
|
732
645
|
task: tasks[0]
|
|
733
|
-
})
|
|
734
|
-
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
646
|
+
}), tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
735
647
|
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
736
|
-
this.printAnnotations(test, "error", 1);
|
|
737
|
-
this.error();
|
|
648
|
+
this.printAnnotations(test, "error", 1), this.error();
|
|
738
649
|
}
|
|
739
650
|
errorDivider();
|
|
740
651
|
}
|
|
@@ -743,8 +654,7 @@ class BaseReporter {
|
|
|
743
654
|
function deepEqual(a, b) {
|
|
744
655
|
if (a === b) return true;
|
|
745
656
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
746
|
-
const keysA = Object.keys(a);
|
|
747
|
-
const keysB = Object.keys(b);
|
|
657
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
748
658
|
if (keysA.length !== keysB.length) return false;
|
|
749
659
|
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
750
660
|
return true;
|
|
@@ -755,12 +665,7 @@ function sum(items, cb) {
|
|
|
755
665
|
}, 0);
|
|
756
666
|
}
|
|
757
667
|
|
|
758
|
-
const DEFAULT_RENDER_INTERVAL_MS = 1e3
|
|
759
|
-
const ESC = "\x1B[";
|
|
760
|
-
const CLEAR_LINE = `${ESC}K`;
|
|
761
|
-
const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
|
|
762
|
-
const SYNC_START = `${ESC}?2026h`;
|
|
763
|
-
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`;
|
|
764
669
|
/**
|
|
765
670
|
* Renders content of `getWindow` at the bottom of the terminal and
|
|
766
671
|
* forwards all other intercepted `stdout` and `stderr` logs above it.
|
|
@@ -776,50 +681,37 @@ class WindowRenderer {
|
|
|
776
681
|
finished = false;
|
|
777
682
|
cleanups = [];
|
|
778
683
|
constructor(options) {
|
|
684
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
779
685
|
this.options = {
|
|
780
686
|
interval: DEFAULT_RENDER_INTERVAL_MS,
|
|
781
687
|
...options
|
|
782
|
-
}
|
|
783
|
-
this.streams = {
|
|
688
|
+
}, this.streams = {
|
|
784
689
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
785
690
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
789
|
-
this.options.logger.onTerminalCleanup(() => {
|
|
790
|
-
this.flushBuffer();
|
|
791
|
-
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();
|
|
792
693
|
});
|
|
793
694
|
}
|
|
794
695
|
start() {
|
|
795
|
-
this.started = true;
|
|
796
|
-
this.finished = false;
|
|
797
|
-
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();
|
|
798
697
|
}
|
|
799
698
|
stop() {
|
|
800
|
-
this.cleanups.splice(0).map((fn) => fn());
|
|
801
|
-
clearInterval(this.renderInterval);
|
|
699
|
+
this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
|
|
802
700
|
}
|
|
803
701
|
/**
|
|
804
702
|
* Write all buffered output and stop buffering.
|
|
805
703
|
* All intercepted writes are forwarded to actual write after this.
|
|
806
704
|
*/
|
|
807
705
|
finish() {
|
|
808
|
-
this.finished = true;
|
|
809
|
-
this.flushBuffer();
|
|
810
|
-
clearInterval(this.renderInterval);
|
|
706
|
+
this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
|
|
811
707
|
}
|
|
812
708
|
/**
|
|
813
709
|
* Queue new render update
|
|
814
710
|
*/
|
|
815
711
|
schedule() {
|
|
816
|
-
if (!this.renderScheduled) {
|
|
817
|
-
this.renderScheduled =
|
|
818
|
-
|
|
819
|
-
setTimeout(() => {
|
|
820
|
-
this.renderScheduled = false;
|
|
821
|
-
}, 100).unref();
|
|
822
|
-
}
|
|
712
|
+
if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
|
|
713
|
+
this.renderScheduled = false;
|
|
714
|
+
}, 100).unref();
|
|
823
715
|
}
|
|
824
716
|
flushBuffer() {
|
|
825
717
|
if (this.buffer.length === 0) return this.render();
|
|
@@ -831,8 +723,7 @@ class WindowRenderer {
|
|
|
831
723
|
continue;
|
|
832
724
|
}
|
|
833
725
|
if (current.type !== next.type) {
|
|
834
|
-
this.render(current.message, current.type);
|
|
835
|
-
current = next;
|
|
726
|
+
this.render(current.message, current.type), current = next;
|
|
836
727
|
continue;
|
|
837
728
|
}
|
|
838
729
|
current.message += next.message;
|
|
@@ -840,40 +731,31 @@ class WindowRenderer {
|
|
|
840
731
|
if (current) this.render(current?.message, current?.type);
|
|
841
732
|
}
|
|
842
733
|
render(message, type = "output") {
|
|
843
|
-
if (this.finished)
|
|
844
|
-
|
|
845
|
-
return this.write(message || "", type);
|
|
846
|
-
}
|
|
847
|
-
const windowContent = this.options.getWindow();
|
|
848
|
-
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());
|
|
849
736
|
let padding = this.windowHeight - rowCount;
|
|
850
737
|
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
851
|
-
this.write(SYNC_START);
|
|
852
|
-
this.clearWindow();
|
|
853
|
-
if (message) this.write(message, type);
|
|
738
|
+
if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
|
|
854
739
|
if (padding > 0) this.write("\n".repeat(padding));
|
|
855
|
-
this.write(windowContent.join("\n"));
|
|
856
|
-
this.write(SYNC_END);
|
|
857
|
-
this.windowHeight = rowCount + Math.max(0, padding);
|
|
740
|
+
this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
|
|
858
741
|
}
|
|
859
742
|
clearWindow() {
|
|
860
|
-
if (this.windowHeight
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
+
}
|
|
864
748
|
}
|
|
865
749
|
interceptStream(stream, type) {
|
|
866
750
|
const original = stream.write;
|
|
867
|
-
|
|
868
|
-
stream.write = (chunk, _, callback) => {
|
|
751
|
+
return stream.write = (chunk, _, callback) => {
|
|
869
752
|
if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
|
|
870
753
|
else this.buffer.push({
|
|
871
754
|
type,
|
|
872
755
|
message: chunk.toString()
|
|
873
756
|
});
|
|
874
757
|
callback?.();
|
|
875
|
-
}
|
|
876
|
-
return function restore() {
|
|
758
|
+
}, function restore() {
|
|
877
759
|
stream.write = original;
|
|
878
760
|
};
|
|
879
761
|
}
|
|
@@ -891,8 +773,7 @@ function getRenderedRowCount(rows, columns) {
|
|
|
891
773
|
return count;
|
|
892
774
|
}
|
|
893
775
|
|
|
894
|
-
const DURATION_UPDATE_INTERVAL_MS = 100;
|
|
895
|
-
const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
776
|
+
const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
896
777
|
/**
|
|
897
778
|
* Reporter extension that renders summary and forwards all other logs above itself.
|
|
898
779
|
* Intended to be used by other reporters, not as a standalone reporter.
|
|
@@ -913,34 +794,21 @@ class SummaryReporter {
|
|
|
913
794
|
duration = 0;
|
|
914
795
|
durationInterval = void 0;
|
|
915
796
|
onInit(ctx, options = {}) {
|
|
916
|
-
this.ctx = ctx
|
|
917
|
-
this.options = {
|
|
797
|
+
this.ctx = ctx, this.options = {
|
|
918
798
|
verbose: false,
|
|
919
799
|
...options
|
|
920
|
-
}
|
|
921
|
-
this.renderer = new WindowRenderer({
|
|
800
|
+
}, this.renderer = new WindowRenderer({
|
|
922
801
|
logger: ctx.logger,
|
|
923
802
|
getWindow: () => this.createSummary()
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
clearInterval(this.durationInterval);
|
|
927
|
-
this.renderer.stop();
|
|
803
|
+
}), this.ctx.onClose(() => {
|
|
804
|
+
clearInterval(this.durationInterval), this.renderer.stop();
|
|
928
805
|
});
|
|
929
806
|
}
|
|
930
807
|
onTestRunStart(specifications) {
|
|
931
|
-
this.runningModules.clear();
|
|
932
|
-
this.finishedModules.clear();
|
|
933
|
-
this.modules = emptyCounters();
|
|
934
|
-
this.tests = emptyCounters();
|
|
935
|
-
this.startTimers();
|
|
936
|
-
this.renderer.start();
|
|
937
|
-
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;
|
|
938
809
|
}
|
|
939
810
|
onTestRunEnd() {
|
|
940
|
-
this.runningModules.clear();
|
|
941
|
-
this.finishedModules.clear();
|
|
942
|
-
this.renderer.finish();
|
|
943
|
-
clearInterval(this.durationInterval);
|
|
811
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
|
|
944
812
|
}
|
|
945
813
|
onTestModuleQueued(module) {
|
|
946
814
|
// When new test module starts, take the place of previously finished test module, if any
|
|
@@ -948,20 +816,13 @@ class SummaryReporter {
|
|
|
948
816
|
const finished = this.finishedModules.keys().next().value;
|
|
949
817
|
this.removeTestModule(finished);
|
|
950
818
|
}
|
|
951
|
-
this.runningModules.set(module.id, initializeStats(module));
|
|
952
|
-
this.renderer.schedule();
|
|
819
|
+
this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
|
|
953
820
|
}
|
|
954
821
|
onTestModuleCollected(module) {
|
|
955
822
|
let stats = this.runningModules.get(module.id);
|
|
956
|
-
if (!stats)
|
|
957
|
-
stats = initializeStats(module);
|
|
958
|
-
this.runningModules.set(module.id, stats);
|
|
959
|
-
}
|
|
823
|
+
if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
|
|
960
824
|
const total = Array.from(module.children.allTests()).length;
|
|
961
|
-
this.tests.total += total;
|
|
962
|
-
stats.total = total;
|
|
963
|
-
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
964
|
-
this.renderer.schedule();
|
|
825
|
+
this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
|
|
965
826
|
}
|
|
966
827
|
onHookStart(options) {
|
|
967
828
|
const stats = this.getHookStats(options);
|
|
@@ -972,8 +833,7 @@ class SummaryReporter {
|
|
|
972
833
|
startTime: performance.now(),
|
|
973
834
|
onFinish: () => {}
|
|
974
835
|
};
|
|
975
|
-
stats.hook?.onFinish?.();
|
|
976
|
-
stats.hook = hook;
|
|
836
|
+
stats.hook?.onFinish?.(), stats.hook = hook;
|
|
977
837
|
const timeout = setTimeout(() => {
|
|
978
838
|
hook.visible = true;
|
|
979
839
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
@@ -981,9 +841,7 @@ class SummaryReporter {
|
|
|
981
841
|
}
|
|
982
842
|
onHookEnd(options) {
|
|
983
843
|
const stats = this.getHookStats(options);
|
|
984
|
-
|
|
985
|
-
stats.hook.onFinish();
|
|
986
|
-
stats.hook.visible = false;
|
|
844
|
+
stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
|
|
987
845
|
}
|
|
988
846
|
onTestCaseReady(test) {
|
|
989
847
|
// Track slow running tests only on verbose mode
|
|
@@ -995,22 +853,17 @@ class SummaryReporter {
|
|
|
995
853
|
visible: false,
|
|
996
854
|
startTime: performance.now(),
|
|
997
855
|
onFinish: () => {}
|
|
998
|
-
}
|
|
999
|
-
const timeout = setTimeout(() => {
|
|
856
|
+
}, timeout = setTimeout(() => {
|
|
1000
857
|
slowTest.visible = true;
|
|
1001
858
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
1002
859
|
slowTest.onFinish = () => {
|
|
1003
|
-
slowTest.hook?.onFinish();
|
|
1004
|
-
|
|
1005
|
-
};
|
|
1006
|
-
stats.tests.set(test.id, slowTest);
|
|
860
|
+
slowTest.hook?.onFinish(), clearTimeout(timeout);
|
|
861
|
+
}, stats.tests.set(test.id, slowTest);
|
|
1007
862
|
}
|
|
1008
863
|
onTestCaseResult(test) {
|
|
1009
864
|
const stats = this.runningModules.get(test.module.id);
|
|
1010
865
|
if (!stats) return;
|
|
1011
|
-
stats.tests.get(test.id)?.onFinish()
|
|
1012
|
-
stats.tests.delete(test.id);
|
|
1013
|
-
stats.completed++;
|
|
866
|
+
stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
|
|
1014
867
|
const result = test.result();
|
|
1015
868
|
if (result?.state === "passed") this.tests.passed++;
|
|
1016
869
|
else if (result?.state === "failed") this.tests.failed++;
|
|
@@ -1019,8 +872,7 @@ class SummaryReporter {
|
|
|
1019
872
|
}
|
|
1020
873
|
onTestModuleEnd(module) {
|
|
1021
874
|
const state = module.state();
|
|
1022
|
-
this.modules.completed++;
|
|
1023
|
-
if (state === "passed") this.modules.passed++;
|
|
875
|
+
if (this.modules.completed++, state === "passed") this.modules.passed++;
|
|
1024
876
|
else if (state === "failed") this.modules.failed++;
|
|
1025
877
|
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1026
878
|
else if (state === "skipped") this.modules.skipped++;
|
|
@@ -1040,10 +892,8 @@ class SummaryReporter {
|
|
|
1040
892
|
getHookStats({ entity }) {
|
|
1041
893
|
// Track slow running hooks only on verbose mode
|
|
1042
894
|
if (!this.options.verbose) return;
|
|
1043
|
-
const module = entity.type === "module" ? entity : entity.module;
|
|
1044
|
-
|
|
1045
|
-
if (!stats) return;
|
|
1046
|
-
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;
|
|
1047
897
|
}
|
|
1048
898
|
createSummary() {
|
|
1049
899
|
const summary = [""];
|
|
@@ -1055,36 +905,23 @@ class SummaryReporter {
|
|
|
1055
905
|
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1056
906
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1057
907
|
for (const [index, task] of slowTasks.entries()) {
|
|
1058
|
-
const elapsed = this.currentTime - task.startTime;
|
|
1059
|
-
|
|
1060
|
-
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
1061
|
-
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);
|
|
1062
910
|
}
|
|
1063
911
|
}
|
|
1064
912
|
if (this.runningModules.size > 0) summary.push("");
|
|
1065
|
-
summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
|
|
1066
|
-
summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
|
|
1067
|
-
summary.push(padSummaryTitle("Start at") + this.startTime);
|
|
1068
|
-
summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
|
|
1069
|
-
summary.push("");
|
|
1070
|
-
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;
|
|
1071
914
|
}
|
|
1072
915
|
startTimers() {
|
|
1073
916
|
const start = performance.now();
|
|
1074
|
-
this.startTime = formatTimeString(/* @__PURE__ */ new Date())
|
|
1075
|
-
|
|
1076
|
-
this.currentTime = performance.now();
|
|
1077
|
-
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;
|
|
1078
919
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1079
920
|
}
|
|
1080
921
|
removeTestModule(id) {
|
|
1081
922
|
if (!id) return;
|
|
1082
923
|
const testFile = this.runningModules.get(id);
|
|
1083
|
-
testFile?.hook?.onFinish();
|
|
1084
|
-
testFile?.tests?.forEach((test) => test.onFinish());
|
|
1085
|
-
this.runningModules.delete(id);
|
|
1086
|
-
clearTimeout(this.finishedModules.get(id));
|
|
1087
|
-
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);
|
|
1088
925
|
}
|
|
1089
926
|
}
|
|
1090
927
|
function emptyCounters() {
|
|
@@ -1106,9 +943,7 @@ function getStateString(entry) {
|
|
|
1106
943
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1107
944
|
}
|
|
1108
945
|
function sortRunningModules(a, b) {
|
|
1109
|
-
|
|
1110
|
-
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1111
|
-
return a.filename.localeCompare(b.filename);
|
|
946
|
+
return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
|
|
1112
947
|
}
|
|
1113
948
|
function initializeStats(module) {
|
|
1114
949
|
return {
|
|
@@ -1126,12 +961,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
1126
961
|
options;
|
|
1127
962
|
summary;
|
|
1128
963
|
constructor(options = {}) {
|
|
1129
|
-
super(options)
|
|
1130
|
-
this.options = {
|
|
964
|
+
if (super(options), this.options = {
|
|
1131
965
|
summary: true,
|
|
1132
966
|
...options
|
|
1133
|
-
};
|
|
1134
|
-
if (!this.isTTY) this.options.summary = false;
|
|
967
|
+
}, !this.isTTY) this.options.summary = false;
|
|
1135
968
|
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1136
969
|
}
|
|
1137
970
|
onTestRunStart(specifications) {
|
|
@@ -1141,6 +974,9 @@ class DefaultReporter extends BaseReporter {
|
|
|
1141
974
|
}
|
|
1142
975
|
this.summary?.onTestRunStart(specifications);
|
|
1143
976
|
}
|
|
977
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
978
|
+
super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
|
|
979
|
+
}
|
|
1144
980
|
onTestModuleQueued(file) {
|
|
1145
981
|
this.summary?.onTestModuleQueued(file);
|
|
1146
982
|
}
|
|
@@ -1148,15 +984,13 @@ class DefaultReporter extends BaseReporter {
|
|
|
1148
984
|
this.summary?.onTestModuleCollected(module);
|
|
1149
985
|
}
|
|
1150
986
|
onTestModuleEnd(module) {
|
|
1151
|
-
super.onTestModuleEnd(module);
|
|
1152
|
-
this.summary?.onTestModuleEnd(module);
|
|
987
|
+
super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
|
|
1153
988
|
}
|
|
1154
989
|
onTestCaseReady(test) {
|
|
1155
990
|
this.summary?.onTestCaseReady(test);
|
|
1156
991
|
}
|
|
1157
992
|
onTestCaseResult(test) {
|
|
1158
|
-
super.onTestCaseResult(test);
|
|
1159
|
-
this.summary?.onTestCaseResult(test);
|
|
993
|
+
super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
|
|
1160
994
|
}
|
|
1161
995
|
onHookStart(hook) {
|
|
1162
996
|
this.summary?.onHookStart(hook);
|
|
@@ -1165,11 +999,7 @@ class DefaultReporter extends BaseReporter {
|
|
|
1165
999
|
this.summary?.onHookEnd(hook);
|
|
1166
1000
|
}
|
|
1167
1001
|
onInit(ctx) {
|
|
1168
|
-
super.onInit(ctx);
|
|
1169
|
-
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1170
|
-
}
|
|
1171
|
-
onTestRunEnd() {
|
|
1172
|
-
this.summary?.onTestRunEnd();
|
|
1002
|
+
super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1173
1003
|
}
|
|
1174
1004
|
}
|
|
1175
1005
|
|
|
@@ -1178,30 +1008,22 @@ class DotReporter extends BaseReporter {
|
|
|
1178
1008
|
tests = /* @__PURE__ */ new Map();
|
|
1179
1009
|
finishedTests = /* @__PURE__ */ new Set();
|
|
1180
1010
|
onInit(ctx) {
|
|
1181
|
-
super.onInit(ctx)
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
getWindow: () => this.createSummary()
|
|
1186
|
-
});
|
|
1187
|
-
this.ctx.onClose(() => this.renderer?.stop());
|
|
1188
|
-
}
|
|
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());
|
|
1189
1015
|
}
|
|
1190
1016
|
// Ignore default logging of base reporter
|
|
1191
1017
|
printTestModule() {}
|
|
1192
1018
|
onWatcherRerun(files, trigger) {
|
|
1193
|
-
this.tests.clear();
|
|
1194
|
-
this.renderer?.start();
|
|
1195
|
-
super.onWatcherRerun(files, trigger);
|
|
1019
|
+
this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
|
|
1196
1020
|
}
|
|
1197
|
-
|
|
1021
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
1198
1022
|
if (this.isTTY) {
|
|
1199
1023
|
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1200
1024
|
this.ctx.logger.log(finalLog);
|
|
1201
1025
|
} else this.ctx.logger.log();
|
|
1202
|
-
this.tests.clear();
|
|
1203
|
-
this.renderer?.finish();
|
|
1204
|
-
super.onFinished(files, errors);
|
|
1026
|
+
this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
1205
1027
|
}
|
|
1206
1028
|
onTestModuleCollected(module) {
|
|
1207
1029
|
for (const test of module.children.allTests())
|
|
@@ -1209,22 +1031,16 @@ class DotReporter extends BaseReporter {
|
|
|
1209
1031
|
this.onTestCaseReady(test);
|
|
1210
1032
|
}
|
|
1211
1033
|
onTestCaseReady(test) {
|
|
1212
|
-
|
|
1213
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1214
|
-
this.renderer?.schedule();
|
|
1034
|
+
this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
|
|
1215
1035
|
}
|
|
1216
1036
|
onTestCaseResult(test) {
|
|
1217
1037
|
const result = test.result().state;
|
|
1218
1038
|
// On non-TTY the finished tests are printed immediately
|
|
1219
1039
|
if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
|
|
1220
|
-
super.onTestCaseResult(test);
|
|
1221
|
-
this.finishedTests.add(test.id);
|
|
1222
|
-
this.tests.set(test.id, result || "skipped");
|
|
1223
|
-
this.renderer?.schedule();
|
|
1040
|
+
super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
|
|
1224
1041
|
}
|
|
1225
1042
|
onTestModuleEnd(testModule) {
|
|
1226
|
-
super.onTestModuleEnd(testModule);
|
|
1227
|
-
if (!this.isTTY) return;
|
|
1043
|
+
if (super.onTestModuleEnd(testModule), !this.isTTY) return;
|
|
1228
1044
|
const columns = this.ctx.logger.getColumns();
|
|
1229
1045
|
if (this.tests.size < columns) return;
|
|
1230
1046
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
@@ -1234,11 +1050,9 @@ class DotReporter extends BaseReporter {
|
|
|
1234
1050
|
let count = 0;
|
|
1235
1051
|
for (const [id, state] of finishedTests) {
|
|
1236
1052
|
if (count++ >= columns) break;
|
|
1237
|
-
this.tests.delete(id);
|
|
1238
|
-
states.push(state);
|
|
1053
|
+
this.tests.delete(id), states.push(state);
|
|
1239
1054
|
}
|
|
1240
|
-
this.ctx.logger.log(formatTests(states));
|
|
1241
|
-
this.renderer?.schedule();
|
|
1055
|
+
this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
|
|
1242
1056
|
}
|
|
1243
1057
|
createSummary() {
|
|
1244
1058
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
@@ -1248,16 +1062,13 @@ class DotReporter extends BaseReporter {
|
|
|
1248
1062
|
const pass = {
|
|
1249
1063
|
char: "·",
|
|
1250
1064
|
color: c.green
|
|
1251
|
-
}
|
|
1252
|
-
const fail = {
|
|
1065
|
+
}, fail = {
|
|
1253
1066
|
char: "x",
|
|
1254
1067
|
color: c.red
|
|
1255
|
-
}
|
|
1256
|
-
const pending = {
|
|
1068
|
+
}, pending = {
|
|
1257
1069
|
char: "*",
|
|
1258
1070
|
color: c.yellow
|
|
1259
|
-
}
|
|
1260
|
-
const skip = {
|
|
1071
|
+
}, skip = {
|
|
1261
1072
|
char: "-",
|
|
1262
1073
|
color: (char) => c.dim(c.gray(char))
|
|
1263
1074
|
};
|
|
@@ -1274,37 +1085,27 @@ function getIcon(state) {
|
|
|
1274
1085
|
* Sibling icons with same color are merged into a single c.color() call.
|
|
1275
1086
|
*/
|
|
1276
1087
|
function formatTests(states) {
|
|
1277
|
-
let currentIcon = pending;
|
|
1278
|
-
let count = 0;
|
|
1279
|
-
let output = "";
|
|
1088
|
+
let currentIcon = pending, count = 0, output = "";
|
|
1280
1089
|
for (const state of states) {
|
|
1281
1090
|
const icon = getIcon(state);
|
|
1282
1091
|
if (currentIcon === icon) {
|
|
1283
1092
|
count++;
|
|
1284
1093
|
continue;
|
|
1285
1094
|
}
|
|
1286
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1287
|
-
// Start tracking new group
|
|
1288
|
-
count = 1;
|
|
1289
|
-
currentIcon = icon;
|
|
1095
|
+
output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
|
|
1290
1096
|
}
|
|
1291
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1292
|
-
return output;
|
|
1097
|
+
return output += currentIcon.color(currentIcon.char.repeat(count)), output;
|
|
1293
1098
|
}
|
|
1294
1099
|
|
|
1295
1100
|
// use Logger with custom Console to capture entire error printing
|
|
1296
1101
|
function capturePrintError(error, ctx, options) {
|
|
1297
1102
|
let output = "";
|
|
1298
1103
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1299
|
-
output += String(chunk);
|
|
1300
|
-
|
|
1301
|
-
} });
|
|
1302
|
-
const console = new Console(writable);
|
|
1303
|
-
const logger = {
|
|
1104
|
+
output += String(chunk), callback();
|
|
1105
|
+
} }), console = new Console(writable), logger = {
|
|
1304
1106
|
error: console.error.bind(console),
|
|
1305
1107
|
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1306
|
-
}
|
|
1307
|
-
const result = printError(error, ctx, logger, {
|
|
1108
|
+
}, result = printError(error, ctx, logger, {
|
|
1308
1109
|
showCodeFrame: false,
|
|
1309
1110
|
...options
|
|
1310
1111
|
});
|
|
@@ -1322,11 +1123,8 @@ function printError(error, ctx, logger, options) {
|
|
|
1322
1123
|
screenshotPaths: options.screenshotPaths,
|
|
1323
1124
|
printProperties: options.verbose,
|
|
1324
1125
|
parseErrorStacktrace(error) {
|
|
1325
|
-
// browser stack trace needs to be processed differently,
|
|
1326
|
-
// so there is a separate method for that
|
|
1327
|
-
if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
|
|
1328
1126
|
// node.js stack trace already has correct source map locations
|
|
1329
|
-
return parseErrorStacktrace(error, {
|
|
1127
|
+
return options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 }) : parseErrorStacktrace(error, {
|
|
1330
1128
|
frameFilter: project.config.onStackTrace,
|
|
1331
1129
|
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1332
1130
|
});
|
|
@@ -1334,8 +1132,7 @@ function printError(error, ctx, logger, options) {
|
|
|
1334
1132
|
});
|
|
1335
1133
|
}
|
|
1336
1134
|
function printErrorInner(error, project, options) {
|
|
1337
|
-
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1338
|
-
const logger = options.logger;
|
|
1135
|
+
const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
|
|
1339
1136
|
let e = error;
|
|
1340
1137
|
if (isPrimitive(e)) e = {
|
|
1341
1138
|
message: String(error).split(/\n/g)[0],
|
|
@@ -1353,21 +1150,18 @@ function printErrorInner(error, project, options) {
|
|
|
1353
1150
|
printErrorMessage(e, logger);
|
|
1354
1151
|
return;
|
|
1355
1152
|
}
|
|
1356
|
-
const stacks = options.parseErrorStacktrace(e)
|
|
1357
|
-
const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1153
|
+
const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1358
1154
|
try {
|
|
1359
|
-
|
|
1155
|
+
const module = project._vite && project.getModuleById(stack.file);
|
|
1156
|
+
return (module?.transformResult || module?.ssrTransformResult) && existsSync(stack.file);
|
|
1360
1157
|
} catch {
|
|
1361
1158
|
return false;
|
|
1362
1159
|
}
|
|
1363
1160
|
});
|
|
1364
1161
|
if (type) printErrorType(type, project.vitest);
|
|
1365
|
-
printErrorMessage(e, logger)
|
|
1366
|
-
if (options.screenshotPaths?.length) {
|
|
1162
|
+
if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
|
|
1367
1163
|
const length = options.screenshotPaths.length;
|
|
1368
|
-
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1369
|
-
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1370
|
-
if (!e.diff) logger.error();
|
|
1164
|
+
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();
|
|
1371
1165
|
}
|
|
1372
1166
|
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1373
1167
|
if ("__vitest_rollup_error__" in e) {
|
|
@@ -1392,25 +1186,19 @@ function printErrorInner(error, project, options) {
|
|
|
1392
1186
|
}
|
|
1393
1187
|
});
|
|
1394
1188
|
}
|
|
1395
|
-
const testPath = e.VITEST_TEST_PATH;
|
|
1396
|
-
const testName = e.VITEST_TEST_NAME;
|
|
1397
|
-
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1189
|
+
const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1398
1190
|
// testName has testPath inside
|
|
1399
1191
|
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.`));
|
|
1400
1192
|
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:
|
|
1401
1193
|
- The error was thrown, while Vitest was running this test.
|
|
1402
1194
|
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1403
1195
|
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"));
|
|
1404
|
-
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
handleImportOutsideModuleError(e.stack || "", logger);
|
|
1413
|
-
return { nearest };
|
|
1196
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
|
|
1197
|
+
showCodeFrame: false,
|
|
1198
|
+
logger: options.logger,
|
|
1199
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1200
|
+
});
|
|
1201
|
+
return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
|
|
1414
1202
|
}
|
|
1415
1203
|
function printErrorType(type, ctx) {
|
|
1416
1204
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
@@ -1427,6 +1215,7 @@ const skipErrorProperties = new Set([
|
|
|
1427
1215
|
"actual",
|
|
1428
1216
|
"expected",
|
|
1429
1217
|
"diffOptions",
|
|
1218
|
+
"runnerError",
|
|
1430
1219
|
"sourceURL",
|
|
1431
1220
|
"column",
|
|
1432
1221
|
"line",
|
|
@@ -1492,10 +1281,8 @@ function printErrorMessage(error, logger) {
|
|
|
1492
1281
|
}
|
|
1493
1282
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1494
1283
|
for (const frame of stack) {
|
|
1495
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
1496
|
-
|
|
1497
|
-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1498
|
-
onStack?.(frame);
|
|
1284
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
|
|
1285
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
|
|
1499
1286
|
}
|
|
1500
1287
|
if (stack.length) logger.error();
|
|
1501
1288
|
if (hasProperties(errorProperties)) {
|
|
@@ -1510,26 +1297,19 @@ function hasProperties(obj) {
|
|
|
1510
1297
|
return false;
|
|
1511
1298
|
}
|
|
1512
1299
|
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1513
|
-
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1514
|
-
|
|
1515
|
-
const lines = source.split(lineSplitRE);
|
|
1516
|
-
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1517
|
-
let count = 0;
|
|
1518
|
-
let res = [];
|
|
1300
|
+
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;
|
|
1301
|
+
let count = 0, res = [];
|
|
1519
1302
|
const columns = process.stdout?.columns || 80;
|
|
1520
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const lineLength = lines[j].length;
|
|
1303
|
+
for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
|
|
1304
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1305
|
+
if (j < 0 || j >= lines.length) continue;
|
|
1306
|
+
const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
|
|
1307
|
+
if (!strippedContent.startsWith("//# sourceMappingURL")) {
|
|
1526
1308
|
// too long, maybe it's a minified file, skip for codeframe
|
|
1527
|
-
if (
|
|
1528
|
-
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent))
|
|
1529
|
-
if (j === i) {
|
|
1309
|
+
if (strippedContent.length > 200) return "";
|
|
1310
|
+
if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
|
|
1530
1311
|
// push underline
|
|
1531
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
1532
|
-
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1312
|
+
const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1533
1313
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1534
1314
|
} else if (j > i) {
|
|
1535
1315
|
if (end > count) {
|
|
@@ -1539,8 +1319,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1539
1319
|
count += lineLength + 1;
|
|
1540
1320
|
}
|
|
1541
1321
|
}
|
|
1542
|
-
break;
|
|
1543
1322
|
}
|
|
1323
|
+
break;
|
|
1544
1324
|
}
|
|
1545
1325
|
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1546
1326
|
return res.join("\n");
|
|
@@ -1560,8 +1340,7 @@ class GithubActionsReporter {
|
|
|
1560
1340
|
}
|
|
1561
1341
|
onTestCaseAnnotate(testCase, annotation) {
|
|
1562
1342
|
if (!annotation.location) return;
|
|
1563
|
-
const type = getTitle(annotation.type)
|
|
1564
|
-
const formatted = formatMessage({
|
|
1343
|
+
const type = getTitle(annotation.type), formatted = formatMessage({
|
|
1565
1344
|
command: getType(annotation.type),
|
|
1566
1345
|
properties: {
|
|
1567
1346
|
file: annotation.location.file,
|
|
@@ -1573,17 +1352,15 @@ class GithubActionsReporter {
|
|
|
1573
1352
|
});
|
|
1574
1353
|
this.ctx.logger.log(`\n${formatted}`);
|
|
1575
1354
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const projectErrors = new Array();
|
|
1355
|
+
onTestRunEnd(testModules, unhandledErrors) {
|
|
1356
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
|
|
1579
1357
|
for (const error of errors) projectErrors.push({
|
|
1580
1358
|
project: this.ctx.getRootProject(),
|
|
1581
1359
|
title: "Unhandled error",
|
|
1582
1360
|
error
|
|
1583
1361
|
});
|
|
1584
1362
|
for (const file of files) {
|
|
1585
|
-
const tasks = getTasks(file);
|
|
1586
|
-
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1363
|
+
const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
|
|
1587
1364
|
for (const task of tasks) {
|
|
1588
1365
|
if (task.result?.state !== "fail") continue;
|
|
1589
1366
|
const title = getFullName(task, " > ");
|
|
@@ -1601,8 +1378,7 @@ class GithubActionsReporter {
|
|
|
1601
1378
|
const result = capturePrintError(error, this.ctx, {
|
|
1602
1379
|
project,
|
|
1603
1380
|
task: file
|
|
1604
|
-
});
|
|
1605
|
-
const stack = result?.nearest;
|
|
1381
|
+
}), stack = result?.nearest;
|
|
1606
1382
|
if (!stack) continue;
|
|
1607
1383
|
const formatted = formatMessage({
|
|
1608
1384
|
command: "error",
|
|
@@ -1624,12 +1400,10 @@ const BUILT_IN_TYPES = [
|
|
|
1624
1400
|
"warning"
|
|
1625
1401
|
];
|
|
1626
1402
|
function getTitle(type) {
|
|
1627
|
-
|
|
1628
|
-
return type;
|
|
1403
|
+
return BUILT_IN_TYPES.includes(type) ? void 0 : type;
|
|
1629
1404
|
}
|
|
1630
1405
|
function getType(type) {
|
|
1631
|
-
|
|
1632
|
-
return "notice";
|
|
1406
|
+
return BUILT_IN_TYPES.includes(type) ? type : "notice";
|
|
1633
1407
|
}
|
|
1634
1408
|
function defaultOnWritePath(path) {
|
|
1635
1409
|
return path;
|
|
@@ -1639,12 +1413,9 @@ function defaultOnWritePath(path) {
|
|
|
1639
1413
|
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1640
1414
|
function formatMessage({ command, properties, message }) {
|
|
1641
1415
|
let result = `::${command}`;
|
|
1642
|
-
Object.entries(properties).forEach(([k, v], i) => {
|
|
1643
|
-
result += i === 0 ? " " : ","
|
|
1644
|
-
|
|
1645
|
-
});
|
|
1646
|
-
result += `::${escapeData(message)}`;
|
|
1647
|
-
return result;
|
|
1416
|
+
return Object.entries(properties).forEach(([k, v], i) => {
|
|
1417
|
+
result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
|
|
1418
|
+
}), result += `::${escapeData(message)}`, result;
|
|
1648
1419
|
}
|
|
1649
1420
|
function escapeData(s) {
|
|
1650
1421
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -1677,41 +1448,27 @@ class JsonReporter {
|
|
|
1677
1448
|
start = 0;
|
|
1678
1449
|
ctx;
|
|
1679
1450
|
options;
|
|
1451
|
+
coverageMap;
|
|
1680
1452
|
constructor(options) {
|
|
1681
1453
|
this.options = options;
|
|
1682
1454
|
}
|
|
1683
1455
|
onInit(ctx) {
|
|
1684
|
-
this.ctx = ctx;
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const tests = getTests(files);
|
|
1691
|
-
const numTotalTests = tests.length;
|
|
1692
|
-
const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
|
|
1693
|
-
const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
|
|
1694
|
-
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
|
|
1695
|
-
const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
|
|
1696
|
-
const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
|
|
1697
|
-
const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
|
|
1698
|
-
const numTodoTests = tests.filter((t) => t.mode === "todo").length;
|
|
1699
|
-
const testResults = [];
|
|
1700
|
-
const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
|
|
1456
|
+
this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
|
|
1457
|
+
}
|
|
1458
|
+
onCoverage(coverageMap) {
|
|
1459
|
+
this.coverageMap = coverageMap;
|
|
1460
|
+
}
|
|
1461
|
+
async onTestRunEnd(testModules) {
|
|
1462
|
+
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;
|
|
1701
1463
|
for (const file of files) {
|
|
1702
1464
|
const tests = getTests([file]);
|
|
1703
1465
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1704
1466
|
if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
|
|
1705
|
-
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime)
|
|
1706
|
-
const assertionResults = tests.map((t) => {
|
|
1467
|
+
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
|
|
1707
1468
|
const ancestorTitles = [];
|
|
1708
1469
|
let iter = t.suite;
|
|
1709
|
-
while (iter)
|
|
1710
|
-
|
|
1711
|
-
iter = iter.suite;
|
|
1712
|
-
}
|
|
1713
|
-
ancestorTitles.reverse();
|
|
1714
|
-
return {
|
|
1470
|
+
while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
|
|
1471
|
+
return ancestorTitles.reverse(), {
|
|
1715
1472
|
ancestorTitles,
|
|
1716
1473
|
fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
|
|
1717
1474
|
status: StatusMap[t.result?.state || t.mode] || "skipped",
|
|
@@ -1747,13 +1504,10 @@ class JsonReporter {
|
|
|
1747
1504
|
startTime: this.start,
|
|
1748
1505
|
success,
|
|
1749
1506
|
testResults,
|
|
1750
|
-
coverageMap
|
|
1507
|
+
coverageMap: this.coverageMap
|
|
1751
1508
|
};
|
|
1752
1509
|
await this.writeReport(JSON.stringify(result));
|
|
1753
1510
|
}
|
|
1754
|
-
async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
|
|
1755
|
-
await this.logTasks(files, coverageMap);
|
|
1756
|
-
}
|
|
1757
1511
|
/**
|
|
1758
1512
|
* Writes the report to an output file if specified in the config,
|
|
1759
1513
|
* or logs it to the console otherwise.
|
|
@@ -1762,11 +1516,9 @@ class JsonReporter {
|
|
|
1762
1516
|
async writeReport(report) {
|
|
1763
1517
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
1764
1518
|
if (outputFile) {
|
|
1765
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1766
|
-
const outputDirectory = dirname(reportFile);
|
|
1519
|
+
const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
|
|
1767
1520
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1768
|
-
await promises.writeFile(reportFile, report, "utf-8");
|
|
1769
|
-
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1521
|
+
await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1770
1522
|
} else this.ctx.logger.log(report);
|
|
1771
1523
|
}
|
|
1772
1524
|
}
|
|
@@ -1789,8 +1541,7 @@ class IndentedLogger {
|
|
|
1789
1541
|
|
|
1790
1542
|
function flattenTasks$1(task, baseName = "") {
|
|
1791
1543
|
const base = baseName ? `${baseName} > ` : "";
|
|
1792
|
-
|
|
1793
|
-
else return [{
|
|
1544
|
+
return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
|
|
1794
1545
|
...task,
|
|
1795
1546
|
name: `${base}${task.name}`
|
|
1796
1547
|
}];
|
|
@@ -1798,21 +1549,16 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1798
1549
|
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1799
1550
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1800
1551
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1801
|
-
value = String(value || "").replace(regex, "")
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
"g"
|
|
1808
|
-
/* eslint-enable */
|
|
1809
|
-
);
|
|
1810
|
-
value = value.replace(regex, "");
|
|
1811
|
-
}
|
|
1552
|
+
if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
|
|
1553
|
+
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1554
|
+
"([\\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]))",
|
|
1555
|
+
"g"
|
|
1556
|
+
/* eslint-enable */
|
|
1557
|
+
), value = value.replace(regex, "");
|
|
1812
1558
|
return value;
|
|
1813
1559
|
}
|
|
1814
1560
|
function escapeXML(value) {
|
|
1815
|
-
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
1561
|
+
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"), true);
|
|
1816
1562
|
}
|
|
1817
1563
|
function executionTime(durationMS) {
|
|
1818
1564
|
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
@@ -1833,8 +1579,7 @@ class JUnitReporter {
|
|
|
1833
1579
|
fileFd;
|
|
1834
1580
|
options;
|
|
1835
1581
|
constructor(options) {
|
|
1836
|
-
this.options = { ...options };
|
|
1837
|
-
this.options.includeConsoleOutput ??= true;
|
|
1582
|
+
this.options = { ...options }, this.options.includeConsoleOutput ??= true;
|
|
1838
1583
|
}
|
|
1839
1584
|
async onInit(ctx) {
|
|
1840
1585
|
this.ctx = ctx;
|
|
@@ -1844,14 +1589,12 @@ class JUnitReporter {
|
|
|
1844
1589
|
const outputDirectory = dirname(this.reportFile);
|
|
1845
1590
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1846
1591
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
1847
|
-
this.fileFd = fileFd
|
|
1848
|
-
this.baseLog = async (text) => {
|
|
1592
|
+
this.fileFd = fileFd, this.baseLog = async (text) => {
|
|
1849
1593
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
1850
1594
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
1851
1595
|
};
|
|
1852
1596
|
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1853
|
-
this._timeStart = /* @__PURE__ */ new Date();
|
|
1854
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
1597
|
+
this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
|
|
1855
1598
|
}
|
|
1856
1599
|
async writeElement(name, attrs, children) {
|
|
1857
1600
|
const pairs = [];
|
|
@@ -1860,18 +1603,12 @@ class JUnitReporter {
|
|
|
1860
1603
|
if (attr === void 0) continue;
|
|
1861
1604
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
1862
1605
|
}
|
|
1863
|
-
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
|
|
1864
|
-
this.logger.indent();
|
|
1865
|
-
await children.call(this);
|
|
1866
|
-
this.logger.unindent();
|
|
1867
|
-
await this.logger.log(`</${name}>`);
|
|
1606
|
+
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`), this.logger.indent(), await children.call(this), this.logger.unindent(), await this.logger.log(`</${name}>`);
|
|
1868
1607
|
}
|
|
1869
1608
|
async writeLogs(task, type) {
|
|
1870
1609
|
if (task.logs == null || task.logs.length === 0) return;
|
|
1871
|
-
const logType = type === "err" ? "stderr" : "stdout";
|
|
1872
|
-
|
|
1873
|
-
if (logs.length === 0) return;
|
|
1874
|
-
await this.writeElement(`system-${type}`, {}, async () => {
|
|
1610
|
+
const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
|
|
1611
|
+
logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
|
|
1875
1612
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
1876
1613
|
});
|
|
1877
1614
|
}
|
|
@@ -1890,20 +1627,12 @@ class JUnitReporter {
|
|
|
1890
1627
|
name: task.name,
|
|
1891
1628
|
time: getDuration(task)
|
|
1892
1629
|
}, async () => {
|
|
1893
|
-
if (this.options.includeConsoleOutput)
|
|
1894
|
-
await this.writeLogs(task, "out");
|
|
1895
|
-
await this.writeLogs(task, "err");
|
|
1896
|
-
}
|
|
1630
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
|
|
1897
1631
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1898
1632
|
if (task.type === "test" && task.annotations.length) {
|
|
1899
|
-
await this.logger.log("<properties>");
|
|
1900
|
-
this.logger.
|
|
1901
|
-
|
|
1902
|
-
await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
|
|
1903
|
-
await this.logger.log("</property>");
|
|
1904
|
-
}
|
|
1905
|
-
this.logger.unindent();
|
|
1906
|
-
await this.logger.log("</properties>");
|
|
1633
|
+
await this.logger.log("<properties>"), this.logger.indent();
|
|
1634
|
+
for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
|
|
1635
|
+
this.logger.unindent(), await this.logger.log("</properties>");
|
|
1907
1636
|
}
|
|
1908
1637
|
if (task.result?.state === "fail") {
|
|
1909
1638
|
const errors = task.result.errors || [];
|
|
@@ -1922,11 +1651,11 @@ class JUnitReporter {
|
|
|
1922
1651
|
});
|
|
1923
1652
|
}
|
|
1924
1653
|
}
|
|
1925
|
-
async
|
|
1654
|
+
async onTestRunEnd(testModules) {
|
|
1655
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1926
1656
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
1927
1657
|
const transformed = files.map((file) => {
|
|
1928
|
-
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task))
|
|
1929
|
-
const stats = tasks.reduce((stats, task) => {
|
|
1658
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
|
|
1930
1659
|
return {
|
|
1931
1660
|
passed: stats.passed + Number(task.result?.state === "pass"),
|
|
1932
1661
|
failures: stats.failures + Number(task.result?.state === "fail"),
|
|
@@ -1936,41 +1665,29 @@ class JUnitReporter {
|
|
|
1936
1665
|
passed: 0,
|
|
1937
1666
|
failures: 0,
|
|
1938
1667
|
skipped: 0
|
|
1939
|
-
});
|
|
1940
|
-
|
|
1941
|
-
const suites = getSuites(file);
|
|
1942
|
-
for (const suite of suites) if (suite.result?.errors) {
|
|
1943
|
-
tasks.push(suite);
|
|
1944
|
-
stats.failures += 1;
|
|
1945
|
-
}
|
|
1668
|
+
}), suites = getSuites(file);
|
|
1669
|
+
for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
|
|
1946
1670
|
// If there are no tests, but the file failed to load, we still want to report it as a failure
|
|
1947
|
-
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
annotations: []
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1671
|
+
if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
|
|
1672
|
+
id: file.id,
|
|
1673
|
+
type: "test",
|
|
1674
|
+
name: file.name,
|
|
1675
|
+
mode: "run",
|
|
1676
|
+
result: file.result,
|
|
1677
|
+
meta: {},
|
|
1678
|
+
timeout: 0,
|
|
1679
|
+
context: null,
|
|
1680
|
+
suite: null,
|
|
1681
|
+
file: null,
|
|
1682
|
+
annotations: []
|
|
1683
|
+
});
|
|
1963
1684
|
return {
|
|
1964
1685
|
...file,
|
|
1965
1686
|
tasks,
|
|
1966
1687
|
stats
|
|
1967
1688
|
};
|
|
1968
|
-
})
|
|
1969
|
-
|
|
1970
|
-
stats.tests += file.tasks.length;
|
|
1971
|
-
stats.failures += file.stats.failures;
|
|
1972
|
-
stats.time += file.result?.duration || 0;
|
|
1973
|
-
return stats;
|
|
1689
|
+
}), stats = transformed.reduce((stats, file) => {
|
|
1690
|
+
return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
|
|
1974
1691
|
}, {
|
|
1975
1692
|
name: this.options.suiteName || "vitest tests",
|
|
1976
1693
|
tests: 0,
|
|
@@ -1978,7 +1695,7 @@ class JUnitReporter {
|
|
|
1978
1695
|
errors: 0,
|
|
1979
1696
|
time: 0
|
|
1980
1697
|
});
|
|
1981
|
-
await this.writeElement("testsuites", {
|
|
1698
|
+
if (await this.writeElement("testsuites", {
|
|
1982
1699
|
...stats,
|
|
1983
1700
|
time: executionTime(stats.time)
|
|
1984
1701
|
}, async () => {
|
|
@@ -1997,16 +1714,13 @@ class JUnitReporter {
|
|
|
1997
1714
|
await this.writeTasks(file.tasks, filename);
|
|
1998
1715
|
});
|
|
1999
1716
|
}
|
|
2000
|
-
});
|
|
2001
|
-
|
|
2002
|
-
await this.fileFd?.close();
|
|
2003
|
-
this.fileFd = void 0;
|
|
1717
|
+
}), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
1718
|
+
await this.fileFd?.close(), this.fileFd = void 0;
|
|
2004
1719
|
}
|
|
2005
1720
|
}
|
|
2006
1721
|
|
|
2007
1722
|
function yamlString(str) {
|
|
2008
|
-
|
|
2009
|
-
return `"${str.replace(/"/g, "\\\"")}"`;
|
|
1723
|
+
return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
|
|
2010
1724
|
}
|
|
2011
1725
|
function tapString(str) {
|
|
2012
1726
|
return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
|
|
@@ -2015,77 +1729,45 @@ class TapReporter {
|
|
|
2015
1729
|
ctx;
|
|
2016
1730
|
logger;
|
|
2017
1731
|
onInit(ctx) {
|
|
2018
|
-
this.ctx = ctx;
|
|
2019
|
-
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
1732
|
+
this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2020
1733
|
}
|
|
2021
1734
|
static getComment(task) {
|
|
2022
|
-
|
|
2023
|
-
else if (task.mode === "todo") return " # TODO";
|
|
2024
|
-
else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2025
|
-
else return "";
|
|
1735
|
+
return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2026
1736
|
}
|
|
2027
1737
|
logErrorDetails(error, stack) {
|
|
2028
1738
|
const errorName = error.name || "Unknown Error";
|
|
2029
|
-
this.logger.log(`name: ${yamlString(String(errorName))}`)
|
|
2030
|
-
this.logger.log(`message: ${yamlString(String(error.message))}`);
|
|
2031
|
-
if (stack)
|
|
1739
|
+
if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
|
|
2032
1740
|
// For compatibility with tap-mocha-reporter
|
|
2033
1741
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2034
1742
|
}
|
|
2035
1743
|
logTasks(tasks) {
|
|
2036
1744
|
this.logger.log(`1..${tasks.length}`);
|
|
2037
1745
|
for (const [i, task] of tasks.entries()) {
|
|
2038
|
-
const id = i + 1;
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
if (task.type === "suite" && task.tasks.length > 0) {
|
|
2042
|
-
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
|
|
2043
|
-
this.logger.indent();
|
|
2044
|
-
this.logTasks(task.tasks);
|
|
2045
|
-
this.logger.unindent();
|
|
2046
|
-
this.logger.log("}");
|
|
2047
|
-
} else {
|
|
1746
|
+
const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
|
|
1747
|
+
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("}");
|
|
1748
|
+
else {
|
|
2048
1749
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2049
1750
|
const project = this.ctx.getProjectByName(task.file.projectName || "");
|
|
2050
|
-
if (task.type === "test" && task.annotations) {
|
|
2051
|
-
this.logger.
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
});
|
|
2055
|
-
this.logger.unindent();
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
this.logger.indent();
|
|
2059
|
-
task.result.errors.forEach((error) => {
|
|
2060
|
-
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace });
|
|
2061
|
-
const stack = stacks[0];
|
|
2062
|
-
this.logger.log("---");
|
|
2063
|
-
this.logger.log("error:");
|
|
2064
|
-
this.logger.indent();
|
|
2065
|
-
this.logErrorDetails(error);
|
|
2066
|
-
this.logger.unindent();
|
|
2067
|
-
if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2068
|
-
if (error.showDiff) {
|
|
2069
|
-
this.logger.log(`actual: ${yamlString(error.actual)}`);
|
|
2070
|
-
this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
2071
|
-
}
|
|
2072
|
-
});
|
|
2073
|
-
this.logger.log("...");
|
|
2074
|
-
this.logger.unindent();
|
|
2075
|
-
}
|
|
1751
|
+
if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
|
|
1752
|
+
this.logger.log(`# ${type}: ${message}`);
|
|
1753
|
+
}), this.logger.unindent();
|
|
1754
|
+
if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
|
|
1755
|
+
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
|
|
1756
|
+
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}`)}`);
|
|
1757
|
+
if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
1758
|
+
}), this.logger.log("..."), this.logger.unindent();
|
|
2076
1759
|
}
|
|
2077
1760
|
}
|
|
2078
1761
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
this.logTasks(files);
|
|
1762
|
+
onTestRunEnd(testModules) {
|
|
1763
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1764
|
+
this.logger.log("TAP version 13"), this.logTasks(files);
|
|
2082
1765
|
}
|
|
2083
1766
|
}
|
|
2084
1767
|
|
|
2085
1768
|
function flattenTasks(task, baseName = "") {
|
|
2086
1769
|
const base = baseName ? `${baseName} > ` : "";
|
|
2087
|
-
|
|
2088
|
-
else return [{
|
|
1770
|
+
return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
|
|
2089
1771
|
...task,
|
|
2090
1772
|
name: `${base}${task.name}`
|
|
2091
1773
|
}];
|
|
@@ -2094,9 +1776,9 @@ class TapFlatReporter extends TapReporter {
|
|
|
2094
1776
|
onInit(ctx) {
|
|
2095
1777
|
super.onInit(ctx);
|
|
2096
1778
|
}
|
|
2097
|
-
|
|
1779
|
+
onTestRunEnd(testModules) {
|
|
2098
1780
|
this.ctx.logger.log("TAP version 13");
|
|
2099
|
-
const flatTasks =
|
|
1781
|
+
const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
|
|
2100
1782
|
this.logTasks(flatTasks);
|
|
2101
1783
|
}
|
|
2102
1784
|
}
|
|
@@ -2112,31 +1794,22 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2112
1794
|
if (this.isTTY) return super.printTestModule(module);
|
|
2113
1795
|
}
|
|
2114
1796
|
onTestCaseResult(test) {
|
|
2115
|
-
super.onTestCaseResult(test);
|
|
2116
1797
|
// don't print tests in TTY as they go, only print them
|
|
2117
1798
|
// in the CLI when they finish
|
|
2118
|
-
if (this.isTTY) return;
|
|
1799
|
+
if (super.onTestCaseResult(test), this.isTTY) return;
|
|
2119
1800
|
const testResult = test.result();
|
|
2120
1801
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2121
1802
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2122
1803
|
if (test.project.name) title += formatProjectName(test.project);
|
|
2123
|
-
title += getFullName(test.task, c.dim(" > "));
|
|
2124
|
-
title += this.getDurationPrefix(test.task);
|
|
1804
|
+
title += getFullName(test.task, c.dim(" > ")), title += this.getDurationPrefix(test.task);
|
|
2125
1805
|
const diagnostic = test.diagnostic();
|
|
2126
1806
|
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2127
1807
|
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2128
|
-
this.log(title);
|
|
2129
|
-
if (
|
|
2130
|
-
if (test.annotations().length) {
|
|
2131
|
-
this.log();
|
|
2132
|
-
this.printAnnotations(test, "log", 3);
|
|
2133
|
-
this.log();
|
|
2134
|
-
}
|
|
1808
|
+
if (this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
|
|
1809
|
+
if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
|
|
2135
1810
|
}
|
|
2136
1811
|
printTestSuite(testSuite) {
|
|
2137
|
-
const indentation = " ".repeat(getIndentation(testSuite.task));
|
|
2138
|
-
const tests = Array.from(testSuite.children.allTests());
|
|
2139
|
-
const state = getStateSymbol(testSuite.task);
|
|
1812
|
+
const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = getStateSymbol(testSuite.task);
|
|
2140
1813
|
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
2141
1814
|
}
|
|
2142
1815
|
getTestName(test) {
|
|
@@ -2151,8 +1824,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2151
1824
|
}
|
|
2152
1825
|
}
|
|
2153
1826
|
function getIndentation(suite, level = 1) {
|
|
2154
|
-
|
|
2155
|
-
return level;
|
|
1827
|
+
return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
|
|
2156
1828
|
}
|
|
2157
1829
|
|
|
2158
1830
|
const ReportersMap = {
|