vitest 4.0.0-beta.1 → 4.0.0-beta.11
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 +83 -2
- package/dist/browser.d.ts +19 -16
- package/dist/browser.js +11 -7
- package/dist/chunks/{benchmark.CYdenmiT.js → benchmark.LXhJ0F0X.js} +7 -9
- package/dist/chunks/{benchmark.d.BwvBVTda.d.ts → benchmark.d.DAaHLpsq.d.ts} +4 -4
- package/dist/chunks/{browser.d.q8Z0P0q1.d.ts → browser.d.Dx7DO_Ce.d.ts} +5 -5
- package/dist/chunks/{cac.D3EzDDZd.js → cac.elvK37c9.js} +71 -153
- package/dist/chunks/{cli-api.Dn5gKePv.js → cli-api.C7plPyhs.js} +1376 -1693
- package/dist/chunks/{config.d.HJdfX-8k.d.ts → config.d.B_LthbQq.d.ts} +58 -63
- package/dist/chunks/{console.CtFJOzRO.js → console.CiTi59Jy.js} +35 -71
- package/dist/chunks/{constants.DnKduX2e.js → constants.D_Q9UYh-.js} +1 -9
- package/dist/chunks/{coverage.Cwa-XhJt.js → coverage.CG6Uhorw.js} +522 -792
- package/dist/chunks/{coverage.DVF1vEu8.js → coverage.D_JHT54q.js} +2 -2
- package/dist/chunks/{coverage.d.S9RMNXIe.d.ts → coverage.d.BZtK59WP.d.ts} +10 -8
- package/dist/chunks/{creator.GK6I-cL4.js → creator.08Gi-vCA.js} +93 -77
- package/dist/chunks/{date.Bq6ZW5rf.js → date.-jtEtIeV.js} +6 -17
- package/dist/chunks/{environment.d.CUq4cUgQ.d.ts → environment.d.BsToaxti.d.ts} +27 -6
- package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
- package/dist/chunks/{global.d.CVbXEflG.d.ts → global.d.BK3X7FW1.d.ts} +2 -5
- package/dist/chunks/{globals.Cxal6MLI.js → globals.BjvYA-AD.js} +11 -9
- package/dist/chunks/{index.BWf_gE5n.js → index.AZOjjqWP.js} +7 -6
- package/dist/chunks/{index.B521nVV-.js → index.Bgo3tNWt.js} +23 -4
- package/dist/chunks/{index.TfbsX-3I.js → index.BhY64fF0.js} +16 -26
- package/dist/chunks/{index.CZI_8rVt.js → index.BwBttQPf.js} +340 -663
- package/dist/chunks/{index.CmSc2RE5.js → index.DIWhzsUh.js} +72 -118
- package/dist/chunks/{inspector.C914Efll.js → inspector.CvQD-Nie.js} +10 -25
- package/dist/chunks/moduleRunner.d.BNa-CL9e.d.ts +201 -0
- package/dist/chunks/{node.fjCdwEIl.js → node.BsdMi6DV.js} +2 -2
- package/dist/chunks/{plugin.d.C2EcJUjo.d.ts → plugin.d.C5phQR6o.d.ts} +1 -1
- package/dist/chunks/{reporters.d.DxZg19fy.d.ts → reporters.d.CVzhsTvK.d.ts} +1233 -1293
- package/dist/chunks/resolveSnapshotEnvironment.DQVamkje.js +81 -0
- package/dist/chunks/rpc.jKGRSXIH.js +65 -0
- package/dist/chunks/{setup-common.D7ZqXFx-.js → setup-common.NAWRuMRP.js} +18 -30
- package/dist/chunks/startModuleRunner.oAuCu1yL.js +682 -0
- package/dist/chunks/{suite.d.FvehnV49.d.ts → suite.d.BJWk38HB.d.ts} +1 -1
- package/dist/chunks/test.KC5tH8hC.js +214 -0
- package/dist/chunks/typechecker.gXq-5P3n.js +1438 -0
- package/dist/chunks/{utils.XdZDrNZV.js → utils.DGKhod2J.js} +9 -28
- package/dist/chunks/{vi.bdSIJ99Y.js → vi.CiJ0Laa6.js} +159 -306
- package/dist/chunks/worker.d.B_Fd9M_w.d.ts +100 -0
- package/dist/chunks/worker.rPGLlbkW.js +200 -0
- package/dist/cli.js +8 -6
- package/dist/config.cjs +3 -9
- package/dist/config.d.ts +49 -54
- package/dist/config.js +1 -1
- package/dist/coverage.d.ts +27 -26
- package/dist/coverage.js +6 -8
- package/dist/environments.d.ts +9 -13
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +38 -45
- package/dist/index.js +10 -10
- package/dist/module-evaluator.d.ts +13 -0
- package/dist/module-evaluator.js +276 -0
- package/dist/module-runner.js +15 -0
- package/dist/node.d.ts +44 -42
- package/dist/node.js +30 -36
- package/dist/reporters.d.ts +12 -13
- package/dist/reporters.js +7 -5
- package/dist/runners.d.ts +3 -3
- package/dist/runners.js +15 -232
- package/dist/snapshot.js +3 -3
- package/dist/suite.d.ts +2 -2
- package/dist/suite.js +4 -3
- package/dist/worker-base.js +203 -0
- package/dist/{chunks/vm.BThCzidc.js → worker-vm.js} +179 -228
- package/dist/workers/runVmTests.js +39 -56
- package/globals.d.ts +17 -17
- package/package.json +40 -38
- package/browser.d.ts +0 -1
- package/dist/chunks/base.Bj3pWTr1.js +0 -38
- package/dist/chunks/execute.B7h3T_Hc.js +0 -708
- package/dist/chunks/index.D-VkfKhf.js +0 -105
- package/dist/chunks/rpc.CsFtxqeq.js +0 -83
- package/dist/chunks/runBaseTests.BC7ZIH5L.js +0 -129
- package/dist/chunks/typechecker.CVytUJuF.js +0 -874
- package/dist/chunks/utils.CAioKnHs.js +0 -61
- package/dist/chunks/worker.d.CmvJfRGs.d.ts +0 -8
- package/dist/chunks/worker.d.DoNjFAiv.d.ts +0 -169
- package/dist/execute.d.ts +0 -148
- package/dist/execute.js +0 -13
- package/dist/worker.js +0 -124
- package/dist/workers/forks.js +0 -43
- package/dist/workers/threads.js +0 -31
- package/dist/workers/vmForks.js +0 -47
- package/dist/workers/vmThreads.js +0 -37
- package/dist/workers.d.ts +0 -37
- package/dist/workers.js +0 -30
- package/execute.d.ts +0 -1
- package/utils.d.ts +0 -1
- package/workers.d.ts +0 -1
|
@@ -1,16 +1,18 @@
|
|
|
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.gXq-5P3n.js';
|
|
5
5
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
6
|
-
import { getTestName,
|
|
7
|
-
import { slash, toArray, isPrimitive
|
|
8
|
-
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
6
|
+
import { getTestName, hasFailed, getTests, getSuites, getTasks, getFullName } from '@vitest/runner/utils';
|
|
7
|
+
import { slash, toArray, isPrimitive } from '@vitest/utils/helpers';
|
|
8
|
+
import { parseStacktrace, parseErrorStacktrace, defaultStackIgnorePatterns } from '@vitest/utils/source-map';
|
|
9
9
|
import c from 'tinyrainbow';
|
|
10
10
|
import { i as isTTY } from './env.D4Lgay0q.js';
|
|
11
11
|
import { stripVTControlCharacters } from 'node:util';
|
|
12
12
|
import { Console } from 'node:console';
|
|
13
13
|
import { Writable } from 'node:stream';
|
|
14
|
+
import { inspect } from '@vitest/utils/display';
|
|
15
|
+
import { positionToOffset, lineSplitRE } from '@vitest/utils/offset';
|
|
14
16
|
import { createRequire } from 'node:module';
|
|
15
17
|
import { hostname } from 'node:os';
|
|
16
18
|
|
|
@@ -125,16 +127,19 @@ class BlobReporter {
|
|
|
125
127
|
start = 0;
|
|
126
128
|
ctx;
|
|
127
129
|
options;
|
|
130
|
+
coverage;
|
|
128
131
|
constructor(options) {
|
|
129
132
|
this.options = options;
|
|
130
133
|
}
|
|
131
134
|
onInit(ctx) {
|
|
132
135
|
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
133
|
-
this.ctx = ctx;
|
|
134
|
-
|
|
136
|
+
this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
|
|
137
|
+
}
|
|
138
|
+
onCoverage(coverage) {
|
|
139
|
+
this.coverage = coverage;
|
|
135
140
|
}
|
|
136
|
-
async
|
|
137
|
-
const executionTime = performance.now() - this.start;
|
|
141
|
+
async onTestRunEnd(testModules, unhandledErrors) {
|
|
142
|
+
const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
|
|
138
143
|
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
139
144
|
if (!outputFile) {
|
|
140
145
|
const shard = this.ctx.config.shard;
|
|
@@ -142,43 +147,34 @@ class BlobReporter {
|
|
|
142
147
|
}
|
|
143
148
|
const modules = this.ctx.projects.map((project) => {
|
|
144
149
|
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
145
|
-
|
|
146
|
-
return [
|
|
150
|
+
return mod[1].file ? [
|
|
147
151
|
mod[0],
|
|
148
152
|
mod[1].file,
|
|
149
153
|
mod[1].url
|
|
150
|
-
];
|
|
154
|
+
] : null;
|
|
151
155
|
}).filter((x) => x != null)];
|
|
152
|
-
})
|
|
153
|
-
const report = [
|
|
156
|
+
}), report = [
|
|
154
157
|
this.ctx.version,
|
|
155
158
|
files,
|
|
156
159
|
errors,
|
|
157
160
|
modules,
|
|
158
161
|
coverage,
|
|
159
162
|
executionTime
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
await writeBlob(report, reportFile);
|
|
163
|
-
this.ctx.logger.log("blob report written to", reportFile);
|
|
163
|
+
], reportFile = resolve(this.ctx.config.root, outputFile);
|
|
164
|
+
await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
async function writeBlob(content, filename) {
|
|
167
|
-
const report = stringify(content);
|
|
168
|
-
const dir = dirname(filename);
|
|
168
|
+
const report = stringify(content), dir = dirname(filename);
|
|
169
169
|
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
170
170
|
await writeFile(filename, report, "utf-8");
|
|
171
171
|
}
|
|
172
172
|
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
173
173
|
// 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);
|
|
174
|
+
const resolvedDir = resolve(process.cwd(), blobsDirectory), blobsFiles = await readdir(resolvedDir), promises = blobsFiles.map(async (filename) => {
|
|
175
|
+
const fullPath = resolve(resolvedDir, filename), stats = await stat(fullPath);
|
|
179
176
|
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);
|
|
177
|
+
const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
182
178
|
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
179
|
return {
|
|
184
180
|
version,
|
|
@@ -189,8 +185,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
189
185
|
file: filename,
|
|
190
186
|
executionTime
|
|
191
187
|
};
|
|
192
|
-
});
|
|
193
|
-
const blobs = await Promise.all(promises);
|
|
188
|
+
}), blobs = await Promise.all(promises);
|
|
194
189
|
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
195
190
|
const versions = new Set(blobs.map((blob) => blob.version));
|
|
196
191
|
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 +195,19 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
200
195
|
blobs.forEach((blob) => {
|
|
201
196
|
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
202
197
|
const project = projects[projectName];
|
|
203
|
-
|
|
204
|
-
moduleIds.forEach(([moduleId, file, url]) => {
|
|
198
|
+
project && moduleIds.forEach(([moduleId, file, url]) => {
|
|
205
199
|
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
206
|
-
moduleNode.url = url
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
|
|
201
|
+
code: " ",
|
|
202
|
+
map: null
|
|
203
|
+
}, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
209
204
|
});
|
|
210
205
|
});
|
|
211
206
|
});
|
|
212
207
|
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
213
|
-
const time1 = f1.result?.startTime || 0;
|
|
214
|
-
const time2 = f2.result?.startTime || 0;
|
|
208
|
+
const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
|
|
215
209
|
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);
|
|
210
|
+
}), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
|
|
220
211
|
return {
|
|
221
212
|
files,
|
|
222
213
|
errors,
|
|
@@ -243,6 +234,7 @@ const testPass = c.green(F_CHECK);
|
|
|
243
234
|
const taskFail = c.red(F_CROSS);
|
|
244
235
|
const suiteFail = c.red(F_POINTER);
|
|
245
236
|
const pending$1 = c.gray("·");
|
|
237
|
+
const separator = c.dim(" > ");
|
|
246
238
|
const labelDefaultColors = [
|
|
247
239
|
c.bgYellow,
|
|
248
240
|
c.bgCyan,
|
|
@@ -258,26 +250,18 @@ function errorBanner(message) {
|
|
|
258
250
|
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
259
251
|
}
|
|
260
252
|
function divider(text, left, right, color) {
|
|
261
|
-
const cols = getCols();
|
|
262
|
-
const c = color || ((text) => text);
|
|
253
|
+
const cols = getCols(), c = color || ((text) => text);
|
|
263
254
|
if (text) {
|
|
264
255
|
const textLength = stripVTControlCharacters(text).length;
|
|
265
256
|
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))}`;
|
|
257
|
+
else left = left ?? Math.floor((cols - textLength) / 2), right = cols - textLength - left;
|
|
258
|
+
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
259
|
}
|
|
274
260
|
return F_LONG_DASH.repeat(cols);
|
|
275
261
|
}
|
|
276
262
|
function formatTestPath(root, path) {
|
|
277
263
|
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);
|
|
264
|
+
const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
|
|
281
265
|
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
282
266
|
}
|
|
283
267
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
@@ -289,8 +273,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
289
273
|
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
290
274
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
291
275
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
292
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`)
|
|
293
|
-
tail.forEach((key) => {
|
|
276
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
|
|
294
277
|
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
295
278
|
});
|
|
296
279
|
}
|
|
@@ -298,8 +281,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
298
281
|
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
299
282
|
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
300
283
|
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}`));
|
|
284
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`), uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
303
285
|
});
|
|
304
286
|
}
|
|
305
287
|
return summary;
|
|
@@ -309,10 +291,7 @@ function countTestErrors(tasks) {
|
|
|
309
291
|
}
|
|
310
292
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
311
293
|
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");
|
|
294
|
+
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
295
|
return [
|
|
317
296
|
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
318
297
|
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
@@ -326,16 +305,13 @@ function getStateSymbol(task) {
|
|
|
326
305
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
327
306
|
if (task.type === "suite") return pointer;
|
|
328
307
|
}
|
|
329
|
-
|
|
330
|
-
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
331
|
-
return " ";
|
|
308
|
+
return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
|
|
332
309
|
}
|
|
333
310
|
function formatTimeString(date) {
|
|
334
311
|
return date.toTimeString().split(" ")[0];
|
|
335
312
|
}
|
|
336
313
|
function formatTime(time) {
|
|
337
|
-
|
|
338
|
-
return `${Math.round(time)}ms`;
|
|
314
|
+
return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
|
|
339
315
|
}
|
|
340
316
|
function formatProjectName(project, suffix = " ") {
|
|
341
317
|
if (!project?.name) return "";
|
|
@@ -356,8 +332,7 @@ function padSummaryTitle(str) {
|
|
|
356
332
|
}
|
|
357
333
|
function truncateString(text, maxLength) {
|
|
358
334
|
const plainText = stripVTControlCharacters(text);
|
|
359
|
-
|
|
360
|
-
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
335
|
+
return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
|
|
361
336
|
}
|
|
362
337
|
function capitalize(text) {
|
|
363
338
|
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
@@ -379,6 +354,7 @@ var utils = /*#__PURE__*/Object.freeze({
|
|
|
379
354
|
pending: pending$1,
|
|
380
355
|
pointer: pointer,
|
|
381
356
|
renderSnapshotSummary: renderSnapshotSummary,
|
|
357
|
+
separator: separator,
|
|
382
358
|
skipped: skipped,
|
|
383
359
|
suiteFail: suiteFail,
|
|
384
360
|
taskFail: taskFail,
|
|
@@ -403,9 +379,7 @@ class BaseReporter {
|
|
|
403
379
|
this.isTTY = options.isTTY ?? isTTY;
|
|
404
380
|
}
|
|
405
381
|
onInit(ctx) {
|
|
406
|
-
this.ctx = ctx;
|
|
407
|
-
this.ctx.logger.printBanner();
|
|
408
|
-
this.start = performance$1.now();
|
|
382
|
+
this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
|
|
409
383
|
}
|
|
410
384
|
log(...messages) {
|
|
411
385
|
this.ctx.logger.log(...messages);
|
|
@@ -416,9 +390,9 @@ class BaseReporter {
|
|
|
416
390
|
relative(path) {
|
|
417
391
|
return relative(this.ctx.config.root, path);
|
|
418
392
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
393
|
+
onTestRunEnd(testModules, unhandledErrors, _reason) {
|
|
394
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors];
|
|
395
|
+
if (this.end = performance$1.now(), !files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
422
396
|
else this.reportSummary(files, errors);
|
|
423
397
|
}
|
|
424
398
|
onTestCaseResult(testCase) {
|
|
@@ -437,13 +411,10 @@ class BaseReporter {
|
|
|
437
411
|
printTestModule(testModule) {
|
|
438
412
|
const moduleState = testModule.state();
|
|
439
413
|
if (moduleState === "queued" || moduleState === "pending") return;
|
|
440
|
-
let testsCount = 0;
|
|
441
|
-
let failedCount = 0;
|
|
442
|
-
let skippedCount = 0;
|
|
414
|
+
let testsCount = 0, failedCount = 0, skippedCount = 0;
|
|
443
415
|
// delaying logs to calculate the test stats first
|
|
444
416
|
// which minimizes the amount of for loops
|
|
445
|
-
const logs = [];
|
|
446
|
-
const originalLog = this.log.bind(this);
|
|
417
|
+
const logs = [], originalLog = this.log.bind(this);
|
|
447
418
|
this.log = (msg) => logs.push(msg);
|
|
448
419
|
const visit = (suiteState, children) => {
|
|
449
420
|
for (const child of children) if (child.type === "suite") {
|
|
@@ -453,8 +424,7 @@ class BaseReporter {
|
|
|
453
424
|
visit(suiteState, child.children);
|
|
454
425
|
} else {
|
|
455
426
|
const testResult = child.result();
|
|
456
|
-
testsCount++;
|
|
457
|
-
if (testResult.state === "failed") failedCount++;
|
|
427
|
+
if (testsCount++, testResult.state === "failed") failedCount++;
|
|
458
428
|
else if (testResult.state === "skipped") skippedCount++;
|
|
459
429
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
460
430
|
// Skipped suites are hidden when --hideSkippedTests
|
|
@@ -471,26 +441,13 @@ class BaseReporter {
|
|
|
471
441
|
tests: testsCount,
|
|
472
442
|
failed: failedCount,
|
|
473
443
|
skipped: skippedCount
|
|
474
|
-
}));
|
|
475
|
-
logs.forEach((log) => this.log(log));
|
|
444
|
+
})), logs.forEach((log) => this.log(log));
|
|
476
445
|
}
|
|
477
446
|
printTestCase(moduleState, test) {
|
|
478
|
-
const testResult = test.result();
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
483
|
-
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
484
|
-
if (testResult.state === "failed") {
|
|
485
|
-
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
|
|
486
|
-
// print short errors, full errors will be at the end in summary
|
|
487
|
-
testResult.errors.forEach((error) => {
|
|
488
|
-
const message = this.formatShortError(error);
|
|
489
|
-
if (message) this.log(c.red(` ${padding}${message}`));
|
|
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}`);
|
|
492
|
-
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
|
-
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
447
|
+
const testResult = test.result(), { duration = 0 } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task), suffix = this.getTestCaseSuffix(test);
|
|
448
|
+
if (testResult.state === "failed") this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix);
|
|
449
|
+
else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)} ${suffix}`);
|
|
450
|
+
else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`);
|
|
494
451
|
}
|
|
495
452
|
getModuleLog(testModule, counts) {
|
|
496
453
|
let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
|
|
@@ -499,25 +456,25 @@ class BaseReporter {
|
|
|
499
456
|
let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(testModule.task);
|
|
500
457
|
const diagnostic = testModule.diagnostic();
|
|
501
458
|
if (diagnostic.heap != null) suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
502
|
-
|
|
503
|
-
if (testModule.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
|
|
504
|
-
if (testModule.project.name) title += ` ${formatProjectName(testModule.project, "")}`;
|
|
459
|
+
const title = this.getEntityPrefix(testModule);
|
|
505
460
|
return ` ${title} ${testModule.task.name} ${suffix}`;
|
|
506
461
|
}
|
|
507
|
-
printTestSuite(
|
|
508
|
-
|
|
462
|
+
printTestSuite(testSuite) {
|
|
463
|
+
if (!this.renderSucceed) return;
|
|
464
|
+
const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = this.getStateSymbol(testSuite);
|
|
465
|
+
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
509
466
|
}
|
|
510
|
-
getTestName(test,
|
|
511
|
-
return
|
|
467
|
+
getTestName(test, _separator) {
|
|
468
|
+
return test.name;
|
|
512
469
|
}
|
|
513
470
|
getFullName(test, separator) {
|
|
514
|
-
|
|
471
|
+
if (test === test.file) return test.name;
|
|
472
|
+
let name = test.file.name;
|
|
473
|
+
if (test.location) name += c.dim(`:${test.location.line}:${test.location.column}`);
|
|
474
|
+
return name += separator, name += getTestName(test, separator), name;
|
|
515
475
|
}
|
|
516
|
-
|
|
517
|
-
return
|
|
518
|
-
}
|
|
519
|
-
getTestIndentation(_test) {
|
|
520
|
-
return " ";
|
|
476
|
+
getTestIndentation(test) {
|
|
477
|
+
return " ".repeat(getIndentation(test));
|
|
521
478
|
}
|
|
522
479
|
printAnnotations(test, console, padding = 0) {
|
|
523
480
|
const annotations = test.annotations();
|
|
@@ -531,10 +488,29 @@ class BaseReporter {
|
|
|
531
488
|
this[console](`${PADDING} ${c.blue(F_DOWN_RIGHT)} ${message}`);
|
|
532
489
|
});
|
|
533
490
|
}
|
|
491
|
+
getEntityPrefix(entity) {
|
|
492
|
+
let title = this.getStateSymbol(entity);
|
|
493
|
+
if (entity.project.name) title += ` ${formatProjectName(entity.project, "")}`;
|
|
494
|
+
if (entity.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
|
|
495
|
+
return title;
|
|
496
|
+
}
|
|
497
|
+
getTestCaseSuffix(testCase) {
|
|
498
|
+
const { heap, retryCount, repeatCount } = testCase.diagnostic() || {}, testResult = testCase.result();
|
|
499
|
+
let suffix = this.getDurationPrefix(testCase.task);
|
|
500
|
+
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
501
|
+
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
502
|
+
if (heap != null) suffix += c.magenta(` ${Math.floor(heap / 1024 / 1024)} MB heap used`);
|
|
503
|
+
if (testResult.state === "skipped" && testResult.note) suffix += c.dim(c.gray(` [${testResult.note}]`));
|
|
504
|
+
return suffix;
|
|
505
|
+
}
|
|
506
|
+
getStateSymbol(test) {
|
|
507
|
+
return getStateSymbol(test.task);
|
|
508
|
+
}
|
|
534
509
|
getDurationPrefix(task) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
510
|
+
const duration = task.result?.duration && Math.round(task.result?.duration);
|
|
511
|
+
if (duration == null) return "";
|
|
512
|
+
const color = duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green;
|
|
513
|
+
return color(` ${duration}${c.dim("ms")}`);
|
|
538
514
|
}
|
|
539
515
|
onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
|
|
540
516
|
const failed = errors.length > 0 || hasFailed(files);
|
|
@@ -547,10 +523,8 @@ class BaseReporter {
|
|
|
547
523
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
548
524
|
}
|
|
549
525
|
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
526
|
// Update re-run count for each file
|
|
553
|
-
files.forEach((filepath) => {
|
|
527
|
+
this.watchFilters = files, this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed"), files.forEach((filepath) => {
|
|
554
528
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
555
529
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
556
530
|
});
|
|
@@ -559,35 +533,26 @@ class BaseReporter {
|
|
|
559
533
|
const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
|
|
560
534
|
banner += c.blue(`x${rerun} `);
|
|
561
535
|
}
|
|
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(", ")));
|
|
536
|
+
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
537
|
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
566
538
|
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
567
539
|
this.log("");
|
|
568
540
|
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
569
|
-
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
570
|
-
this.start = performance$1.now();
|
|
541
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
|
|
571
542
|
}
|
|
572
543
|
onUserConsoleLog(log, taskState) {
|
|
573
544
|
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);
|
|
545
|
+
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream, write = (msg) => output.write(msg);
|
|
576
546
|
let headerText = "unknown test";
|
|
577
547
|
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
578
|
-
if (task) headerText = this.getFullName(task,
|
|
548
|
+
if (task) headerText = this.getFullName(task, separator);
|
|
579
549
|
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) {
|
|
550
|
+
if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
|
|
582
551
|
// browser logs don't have an extra end of line at the end like Node.js does
|
|
583
552
|
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);
|
|
553
|
+
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
554
|
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(" ");
|
|
555
|
+
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
556
|
write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
|
|
592
557
|
}
|
|
593
558
|
}
|
|
@@ -597,13 +562,10 @@ class BaseReporter {
|
|
|
597
562
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
598
563
|
}
|
|
599
564
|
shouldLog(log, taskState) {
|
|
600
|
-
if (this.ctx.config.silent === true) return false;
|
|
601
|
-
if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
565
|
+
if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
602
566
|
if (this.ctx.config.onConsoleLog) {
|
|
603
|
-
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
604
|
-
|
|
605
|
-
const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
|
|
606
|
-
if (shouldLog === false) return shouldLog;
|
|
567
|
+
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);
|
|
568
|
+
if (shouldLog === false) return false;
|
|
607
569
|
}
|
|
608
570
|
return true;
|
|
609
571
|
}
|
|
@@ -611,41 +573,27 @@ class BaseReporter {
|
|
|
611
573
|
this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
|
|
612
574
|
}
|
|
613
575
|
reportSummary(files, errors) {
|
|
614
|
-
this.printErrorsSummary(files, errors);
|
|
615
|
-
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
576
|
+
if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
616
577
|
else this.reportTestSummary(files, errors);
|
|
617
578
|
}
|
|
618
579
|
reportTestSummary(files, errors) {
|
|
619
580
|
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);
|
|
581
|
+
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files], tests = getTests(affectedFiles), snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
|
|
623
582
|
for (const [index, snapshot] of snapshotOutput.entries()) {
|
|
624
583
|
const title = index === 0 ? "Snapshots" : "";
|
|
625
584
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
626
585
|
}
|
|
627
586
|
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)) {
|
|
587
|
+
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
588
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
632
589
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
633
590
|
}
|
|
634
591
|
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
635
592
|
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);
|
|
593
|
+
const collectTime = sum(files, (file) => file.collectDuration), testsTime = sum(files, (file) => file.result?.duration), setupTime = sum(files, (file) => file.setupDuration);
|
|
639
594
|
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
640
595
|
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 = [
|
|
596
|
+
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
597
|
`transform ${formatTime(transformTime)}`,
|
|
650
598
|
`setup ${formatTime(setupTime)}`,
|
|
651
599
|
`collect ${formatTime(collectTime)}`,
|
|
@@ -654,41 +602,25 @@ class BaseReporter {
|
|
|
654
602
|
`prepare ${formatTime(prepareTime)}`,
|
|
655
603
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
656
604
|
].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(""));
|
|
605
|
+
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
606
|
}
|
|
660
607
|
this.log();
|
|
661
608
|
}
|
|
662
609
|
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);
|
|
610
|
+
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
611
|
let current = 1;
|
|
669
612
|
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
|
-
}
|
|
613
|
+
if (failedSuites.length) this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`), this.printTaskErrors(failedSuites, errorDivider);
|
|
614
|
+
if (failedTests.length) this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`), this.printTaskErrors(failedTests, errorDivider);
|
|
615
|
+
if (errors.length) this.ctx.logger.printUnhandledErrors(errors), this.error();
|
|
682
616
|
}
|
|
683
617
|
reportBenchmarkSummary(files) {
|
|
684
|
-
const benches = getTests(files);
|
|
685
|
-
const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
618
|
+
const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
686
619
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
687
620
|
for (const bench of topBenches) {
|
|
688
621
|
const group = bench.suite || bench.file;
|
|
689
622
|
if (!group) continue;
|
|
690
|
-
const groupName = this.getFullName(group,
|
|
691
|
-
const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
623
|
+
const groupName = this.getFullName(group, separator), project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
692
624
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
693
625
|
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
626
|
for (const sibling of siblings) {
|
|
@@ -706,10 +638,7 @@ class BaseReporter {
|
|
|
706
638
|
let previous;
|
|
707
639
|
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
708
640
|
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;
|
|
641
|
+
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
642
|
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
714
643
|
});
|
|
715
644
|
if (previous) previous[1].push(task);
|
|
@@ -717,24 +646,20 @@ class BaseReporter {
|
|
|
717
646
|
});
|
|
718
647
|
for (const [error, tasks] of errorsQueue) {
|
|
719
648
|
for (const task of tasks) {
|
|
720
|
-
const filepath = task?.filepath || "";
|
|
721
|
-
|
|
722
|
-
const project = this.ctx.projects.find((p) => p.name === projectName);
|
|
723
|
-
let name = this.getFullName(task, c.dim(" > "));
|
|
649
|
+
const filepath = task?.filepath || "", projectName = task?.projectName || task.file?.projectName || "", project = this.ctx.projects.find((p) => p.name === projectName);
|
|
650
|
+
let name = this.getFullName(task, separator);
|
|
724
651
|
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
725
652
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
726
653
|
}
|
|
727
654
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
728
|
-
this.ctx.logger.printError(error, {
|
|
655
|
+
if (this.ctx.logger.printError(error, {
|
|
729
656
|
project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
|
|
730
657
|
verbose: this.verbose,
|
|
731
658
|
screenshotPaths,
|
|
732
659
|
task: tasks[0]
|
|
733
|
-
})
|
|
734
|
-
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
660
|
+
}), tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
735
661
|
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
736
|
-
this.printAnnotations(test, "error", 1);
|
|
737
|
-
this.error();
|
|
662
|
+
this.printAnnotations(test, "error", 1), this.error();
|
|
738
663
|
}
|
|
739
664
|
errorDivider();
|
|
740
665
|
}
|
|
@@ -743,8 +668,7 @@ class BaseReporter {
|
|
|
743
668
|
function deepEqual(a, b) {
|
|
744
669
|
if (a === b) return true;
|
|
745
670
|
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);
|
|
671
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
748
672
|
if (keysA.length !== keysB.length) return false;
|
|
749
673
|
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
750
674
|
return true;
|
|
@@ -754,13 +678,11 @@ function sum(items, cb) {
|
|
|
754
678
|
return total + Math.max(cb(next) || 0, 0);
|
|
755
679
|
}, 0);
|
|
756
680
|
}
|
|
681
|
+
function getIndentation(suite, level = 1) {
|
|
682
|
+
return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
|
|
683
|
+
}
|
|
757
684
|
|
|
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`;
|
|
685
|
+
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
686
|
/**
|
|
765
687
|
* Renders content of `getWindow` at the bottom of the terminal and
|
|
766
688
|
* forwards all other intercepted `stdout` and `stderr` logs above it.
|
|
@@ -772,53 +694,41 @@ class WindowRenderer {
|
|
|
772
694
|
renderInterval = void 0;
|
|
773
695
|
renderScheduled = false;
|
|
774
696
|
windowHeight = 0;
|
|
697
|
+
started = false;
|
|
775
698
|
finished = false;
|
|
776
699
|
cleanups = [];
|
|
777
700
|
constructor(options) {
|
|
701
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
778
702
|
this.options = {
|
|
779
703
|
interval: DEFAULT_RENDER_INTERVAL_MS,
|
|
780
704
|
...options
|
|
781
|
-
}
|
|
782
|
-
this.streams = {
|
|
705
|
+
}, this.streams = {
|
|
783
706
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
784
707
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
788
|
-
this.options.logger.onTerminalCleanup(() => {
|
|
789
|
-
this.flushBuffer();
|
|
790
|
-
this.stop();
|
|
708
|
+
}, this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error")), this.options.logger.onTerminalCleanup(() => {
|
|
709
|
+
this.flushBuffer(), this.stop();
|
|
791
710
|
});
|
|
792
|
-
this.start();
|
|
793
711
|
}
|
|
794
712
|
start() {
|
|
795
|
-
this.finished = false;
|
|
796
|
-
this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
713
|
+
this.started = true, this.finished = false, this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
797
714
|
}
|
|
798
715
|
stop() {
|
|
799
|
-
this.cleanups.splice(0).map((fn) => fn());
|
|
800
|
-
clearInterval(this.renderInterval);
|
|
716
|
+
this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
|
|
801
717
|
}
|
|
802
718
|
/**
|
|
803
719
|
* Write all buffered output and stop buffering.
|
|
804
720
|
* All intercepted writes are forwarded to actual write after this.
|
|
805
721
|
*/
|
|
806
722
|
finish() {
|
|
807
|
-
this.finished = true;
|
|
808
|
-
this.flushBuffer();
|
|
809
|
-
clearInterval(this.renderInterval);
|
|
723
|
+
this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
|
|
810
724
|
}
|
|
811
725
|
/**
|
|
812
726
|
* Queue new render update
|
|
813
727
|
*/
|
|
814
728
|
schedule() {
|
|
815
|
-
if (!this.renderScheduled) {
|
|
816
|
-
this.renderScheduled =
|
|
817
|
-
|
|
818
|
-
setTimeout(() => {
|
|
819
|
-
this.renderScheduled = false;
|
|
820
|
-
}, 100).unref();
|
|
821
|
-
}
|
|
729
|
+
if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
|
|
730
|
+
this.renderScheduled = false;
|
|
731
|
+
}, 100).unref();
|
|
822
732
|
}
|
|
823
733
|
flushBuffer() {
|
|
824
734
|
if (this.buffer.length === 0) return this.render();
|
|
@@ -830,8 +740,7 @@ class WindowRenderer {
|
|
|
830
740
|
continue;
|
|
831
741
|
}
|
|
832
742
|
if (current.type !== next.type) {
|
|
833
|
-
this.render(current.message, current.type);
|
|
834
|
-
current = next;
|
|
743
|
+
this.render(current.message, current.type), current = next;
|
|
835
744
|
continue;
|
|
836
745
|
}
|
|
837
746
|
current.message += next.message;
|
|
@@ -839,40 +748,31 @@ class WindowRenderer {
|
|
|
839
748
|
if (current) this.render(current?.message, current?.type);
|
|
840
749
|
}
|
|
841
750
|
render(message, type = "output") {
|
|
842
|
-
if (this.finished)
|
|
843
|
-
|
|
844
|
-
return this.write(message || "", type);
|
|
845
|
-
}
|
|
846
|
-
const windowContent = this.options.getWindow();
|
|
847
|
-
const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
751
|
+
if (this.finished) return this.clearWindow(), this.write(message || "", type);
|
|
752
|
+
const windowContent = this.options.getWindow(), rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
848
753
|
let padding = this.windowHeight - rowCount;
|
|
849
754
|
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
850
|
-
this.write(SYNC_START);
|
|
851
|
-
this.clearWindow();
|
|
852
|
-
if (message) this.write(message, type);
|
|
755
|
+
if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
|
|
853
756
|
if (padding > 0) this.write("\n".repeat(padding));
|
|
854
|
-
this.write(windowContent.join("\n"));
|
|
855
|
-
this.write(SYNC_END);
|
|
856
|
-
this.windowHeight = rowCount + Math.max(0, padding);
|
|
757
|
+
this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
|
|
857
758
|
}
|
|
858
759
|
clearWindow() {
|
|
859
|
-
if (this.windowHeight
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
760
|
+
if (this.windowHeight !== 0) {
|
|
761
|
+
this.write(CLEAR_LINE);
|
|
762
|
+
for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
|
|
763
|
+
this.windowHeight = 0;
|
|
764
|
+
}
|
|
863
765
|
}
|
|
864
766
|
interceptStream(stream, type) {
|
|
865
767
|
const original = stream.write;
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (chunk) if (this.finished) this.write(chunk.toString(), type);
|
|
768
|
+
return stream.write = (chunk, _, callback) => {
|
|
769
|
+
if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
|
|
869
770
|
else this.buffer.push({
|
|
870
771
|
type,
|
|
871
772
|
message: chunk.toString()
|
|
872
773
|
});
|
|
873
774
|
callback?.();
|
|
874
|
-
}
|
|
875
|
-
return function restore() {
|
|
775
|
+
}, function restore() {
|
|
876
776
|
stream.write = original;
|
|
877
777
|
};
|
|
878
778
|
}
|
|
@@ -890,8 +790,7 @@ function getRenderedRowCount(rows, columns) {
|
|
|
890
790
|
return count;
|
|
891
791
|
}
|
|
892
792
|
|
|
893
|
-
const DURATION_UPDATE_INTERVAL_MS = 100;
|
|
894
|
-
const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
793
|
+
const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
895
794
|
/**
|
|
896
795
|
* Reporter extension that renders summary and forwards all other logs above itself.
|
|
897
796
|
* Intended to be used by other reporters, not as a standalone reporter.
|
|
@@ -912,34 +811,21 @@ class SummaryReporter {
|
|
|
912
811
|
duration = 0;
|
|
913
812
|
durationInterval = void 0;
|
|
914
813
|
onInit(ctx, options = {}) {
|
|
915
|
-
this.ctx = ctx
|
|
916
|
-
this.options = {
|
|
814
|
+
this.ctx = ctx, this.options = {
|
|
917
815
|
verbose: false,
|
|
918
816
|
...options
|
|
919
|
-
}
|
|
920
|
-
this.renderer = new WindowRenderer({
|
|
817
|
+
}, this.renderer = new WindowRenderer({
|
|
921
818
|
logger: ctx.logger,
|
|
922
819
|
getWindow: () => this.createSummary()
|
|
923
|
-
})
|
|
924
|
-
|
|
925
|
-
clearInterval(this.durationInterval);
|
|
926
|
-
this.renderer.stop();
|
|
820
|
+
}), this.ctx.onClose(() => {
|
|
821
|
+
clearInterval(this.durationInterval), this.renderer.stop();
|
|
927
822
|
});
|
|
928
823
|
}
|
|
929
824
|
onTestRunStart(specifications) {
|
|
930
|
-
this.runningModules.clear();
|
|
931
|
-
this.finishedModules.clear();
|
|
932
|
-
this.modules = emptyCounters();
|
|
933
|
-
this.tests = emptyCounters();
|
|
934
|
-
this.startTimers();
|
|
935
|
-
this.renderer.start();
|
|
936
|
-
this.modules.total = specifications.length;
|
|
825
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.modules = emptyCounters(), this.tests = emptyCounters(), this.startTimers(), this.renderer.start(), this.modules.total = specifications.length;
|
|
937
826
|
}
|
|
938
827
|
onTestRunEnd() {
|
|
939
|
-
this.runningModules.clear();
|
|
940
|
-
this.finishedModules.clear();
|
|
941
|
-
this.renderer.finish();
|
|
942
|
-
clearInterval(this.durationInterval);
|
|
828
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
|
|
943
829
|
}
|
|
944
830
|
onTestModuleQueued(module) {
|
|
945
831
|
// When new test module starts, take the place of previously finished test module, if any
|
|
@@ -947,20 +833,13 @@ class SummaryReporter {
|
|
|
947
833
|
const finished = this.finishedModules.keys().next().value;
|
|
948
834
|
this.removeTestModule(finished);
|
|
949
835
|
}
|
|
950
|
-
this.runningModules.set(module.id, initializeStats(module));
|
|
951
|
-
this.renderer.schedule();
|
|
836
|
+
this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
|
|
952
837
|
}
|
|
953
838
|
onTestModuleCollected(module) {
|
|
954
839
|
let stats = this.runningModules.get(module.id);
|
|
955
|
-
if (!stats)
|
|
956
|
-
stats = initializeStats(module);
|
|
957
|
-
this.runningModules.set(module.id, stats);
|
|
958
|
-
}
|
|
840
|
+
if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
|
|
959
841
|
const total = Array.from(module.children.allTests()).length;
|
|
960
|
-
this.tests.total += total;
|
|
961
|
-
stats.total = total;
|
|
962
|
-
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
963
|
-
this.renderer.schedule();
|
|
842
|
+
this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
|
|
964
843
|
}
|
|
965
844
|
onHookStart(options) {
|
|
966
845
|
const stats = this.getHookStats(options);
|
|
@@ -971,8 +850,7 @@ class SummaryReporter {
|
|
|
971
850
|
startTime: performance.now(),
|
|
972
851
|
onFinish: () => {}
|
|
973
852
|
};
|
|
974
|
-
stats.hook?.onFinish?.();
|
|
975
|
-
stats.hook = hook;
|
|
853
|
+
stats.hook?.onFinish?.(), stats.hook = hook;
|
|
976
854
|
const timeout = setTimeout(() => {
|
|
977
855
|
hook.visible = true;
|
|
978
856
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
@@ -980,9 +858,7 @@ class SummaryReporter {
|
|
|
980
858
|
}
|
|
981
859
|
onHookEnd(options) {
|
|
982
860
|
const stats = this.getHookStats(options);
|
|
983
|
-
|
|
984
|
-
stats.hook.onFinish();
|
|
985
|
-
stats.hook.visible = false;
|
|
861
|
+
stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
|
|
986
862
|
}
|
|
987
863
|
onTestCaseReady(test) {
|
|
988
864
|
// Track slow running tests only on verbose mode
|
|
@@ -994,22 +870,17 @@ class SummaryReporter {
|
|
|
994
870
|
visible: false,
|
|
995
871
|
startTime: performance.now(),
|
|
996
872
|
onFinish: () => {}
|
|
997
|
-
}
|
|
998
|
-
const timeout = setTimeout(() => {
|
|
873
|
+
}, timeout = setTimeout(() => {
|
|
999
874
|
slowTest.visible = true;
|
|
1000
875
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
1001
876
|
slowTest.onFinish = () => {
|
|
1002
|
-
slowTest.hook?.onFinish();
|
|
1003
|
-
|
|
1004
|
-
};
|
|
1005
|
-
stats.tests.set(test.id, slowTest);
|
|
877
|
+
slowTest.hook?.onFinish(), clearTimeout(timeout);
|
|
878
|
+
}, stats.tests.set(test.id, slowTest);
|
|
1006
879
|
}
|
|
1007
880
|
onTestCaseResult(test) {
|
|
1008
881
|
const stats = this.runningModules.get(test.module.id);
|
|
1009
882
|
if (!stats) return;
|
|
1010
|
-
stats.tests.get(test.id)?.onFinish()
|
|
1011
|
-
stats.tests.delete(test.id);
|
|
1012
|
-
stats.completed++;
|
|
883
|
+
stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
|
|
1013
884
|
const result = test.result();
|
|
1014
885
|
if (result?.state === "passed") this.tests.passed++;
|
|
1015
886
|
else if (result?.state === "failed") this.tests.failed++;
|
|
@@ -1018,8 +889,7 @@ class SummaryReporter {
|
|
|
1018
889
|
}
|
|
1019
890
|
onTestModuleEnd(module) {
|
|
1020
891
|
const state = module.state();
|
|
1021
|
-
this.modules.completed++;
|
|
1022
|
-
if (state === "passed") this.modules.passed++;
|
|
892
|
+
if (this.modules.completed++, state === "passed") this.modules.passed++;
|
|
1023
893
|
else if (state === "failed") this.modules.failed++;
|
|
1024
894
|
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1025
895
|
else if (state === "skipped") this.modules.skipped++;
|
|
@@ -1039,10 +909,8 @@ class SummaryReporter {
|
|
|
1039
909
|
getHookStats({ entity }) {
|
|
1040
910
|
// Track slow running hooks only on verbose mode
|
|
1041
911
|
if (!this.options.verbose) return;
|
|
1042
|
-
const module = entity.type === "module" ? entity : entity.module;
|
|
1043
|
-
|
|
1044
|
-
if (!stats) return;
|
|
1045
|
-
return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
912
|
+
const module = entity.type === "module" ? entity : entity.module, stats = this.runningModules.get(module.id);
|
|
913
|
+
if (stats) return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
1046
914
|
}
|
|
1047
915
|
createSummary() {
|
|
1048
916
|
const summary = [""];
|
|
@@ -1054,36 +922,23 @@ class SummaryReporter {
|
|
|
1054
922
|
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1055
923
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1056
924
|
for (const [index, task] of slowTasks.entries()) {
|
|
1057
|
-
const elapsed = this.currentTime - task.startTime;
|
|
1058
|
-
|
|
1059
|
-
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
1060
|
-
if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
925
|
+
const elapsed = this.currentTime - task.startTime, icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
926
|
+
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);
|
|
1061
927
|
}
|
|
1062
928
|
}
|
|
1063
929
|
if (this.runningModules.size > 0) summary.push("");
|
|
1064
|
-
summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
|
|
1065
|
-
summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
|
|
1066
|
-
summary.push(padSummaryTitle("Start at") + this.startTime);
|
|
1067
|
-
summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
|
|
1068
|
-
summary.push("");
|
|
1069
|
-
return summary;
|
|
930
|
+
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;
|
|
1070
931
|
}
|
|
1071
932
|
startTimers() {
|
|
1072
933
|
const start = performance.now();
|
|
1073
|
-
this.startTime = formatTimeString(/* @__PURE__ */ new Date())
|
|
1074
|
-
|
|
1075
|
-
this.currentTime = performance.now();
|
|
1076
|
-
this.duration = this.currentTime - start;
|
|
934
|
+
this.startTime = formatTimeString(/* @__PURE__ */ new Date()), this.durationInterval = setInterval(() => {
|
|
935
|
+
this.currentTime = performance.now(), this.duration = this.currentTime - start;
|
|
1077
936
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1078
937
|
}
|
|
1079
938
|
removeTestModule(id) {
|
|
1080
939
|
if (!id) return;
|
|
1081
940
|
const testFile = this.runningModules.get(id);
|
|
1082
|
-
testFile?.hook?.onFinish();
|
|
1083
|
-
testFile?.tests?.forEach((test) => test.onFinish());
|
|
1084
|
-
this.runningModules.delete(id);
|
|
1085
|
-
clearTimeout(this.finishedModules.get(id));
|
|
1086
|
-
this.finishedModules.delete(id);
|
|
941
|
+
testFile?.hook?.onFinish(), testFile?.tests?.forEach((test) => test.onFinish()), this.runningModules.delete(id), clearTimeout(this.finishedModules.get(id)), this.finishedModules.delete(id);
|
|
1087
942
|
}
|
|
1088
943
|
}
|
|
1089
944
|
function emptyCounters() {
|
|
@@ -1105,9 +960,7 @@ function getStateString(entry) {
|
|
|
1105
960
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1106
961
|
}
|
|
1107
962
|
function sortRunningModules(a, b) {
|
|
1108
|
-
|
|
1109
|
-
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1110
|
-
return a.filename.localeCompare(b.filename);
|
|
963
|
+
return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
|
|
1111
964
|
}
|
|
1112
965
|
function initializeStats(module) {
|
|
1113
966
|
return {
|
|
@@ -1125,12 +978,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
1125
978
|
options;
|
|
1126
979
|
summary;
|
|
1127
980
|
constructor(options = {}) {
|
|
1128
|
-
super(options)
|
|
1129
|
-
this.options = {
|
|
981
|
+
if (super(options), this.options = {
|
|
1130
982
|
summary: true,
|
|
1131
983
|
...options
|
|
1132
|
-
};
|
|
1133
|
-
if (!this.isTTY) this.options.summary = false;
|
|
984
|
+
}, !this.isTTY) this.options.summary = false;
|
|
1134
985
|
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1135
986
|
}
|
|
1136
987
|
onTestRunStart(specifications) {
|
|
@@ -1140,6 +991,9 @@ class DefaultReporter extends BaseReporter {
|
|
|
1140
991
|
}
|
|
1141
992
|
this.summary?.onTestRunStart(specifications);
|
|
1142
993
|
}
|
|
994
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
995
|
+
super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
|
|
996
|
+
}
|
|
1143
997
|
onTestModuleQueued(file) {
|
|
1144
998
|
this.summary?.onTestModuleQueued(file);
|
|
1145
999
|
}
|
|
@@ -1147,15 +1001,13 @@ class DefaultReporter extends BaseReporter {
|
|
|
1147
1001
|
this.summary?.onTestModuleCollected(module);
|
|
1148
1002
|
}
|
|
1149
1003
|
onTestModuleEnd(module) {
|
|
1150
|
-
super.onTestModuleEnd(module);
|
|
1151
|
-
this.summary?.onTestModuleEnd(module);
|
|
1004
|
+
super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
|
|
1152
1005
|
}
|
|
1153
1006
|
onTestCaseReady(test) {
|
|
1154
1007
|
this.summary?.onTestCaseReady(test);
|
|
1155
1008
|
}
|
|
1156
1009
|
onTestCaseResult(test) {
|
|
1157
|
-
super.onTestCaseResult(test);
|
|
1158
|
-
this.summary?.onTestCaseResult(test);
|
|
1010
|
+
super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
|
|
1159
1011
|
}
|
|
1160
1012
|
onHookStart(hook) {
|
|
1161
1013
|
this.summary?.onHookStart(hook);
|
|
@@ -1164,11 +1016,7 @@ class DefaultReporter extends BaseReporter {
|
|
|
1164
1016
|
this.summary?.onHookEnd(hook);
|
|
1165
1017
|
}
|
|
1166
1018
|
onInit(ctx) {
|
|
1167
|
-
super.onInit(ctx);
|
|
1168
|
-
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1169
|
-
}
|
|
1170
|
-
onTestRunEnd() {
|
|
1171
|
-
this.summary?.onTestRunEnd();
|
|
1019
|
+
super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1172
1020
|
}
|
|
1173
1021
|
}
|
|
1174
1022
|
|
|
@@ -1177,30 +1025,22 @@ class DotReporter extends BaseReporter {
|
|
|
1177
1025
|
tests = /* @__PURE__ */ new Map();
|
|
1178
1026
|
finishedTests = /* @__PURE__ */ new Set();
|
|
1179
1027
|
onInit(ctx) {
|
|
1180
|
-
super.onInit(ctx)
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
getWindow: () => this.createSummary()
|
|
1185
|
-
});
|
|
1186
|
-
this.ctx.onClose(() => this.renderer?.stop());
|
|
1187
|
-
}
|
|
1028
|
+
if (super.onInit(ctx), this.isTTY) this.renderer = new WindowRenderer({
|
|
1029
|
+
logger: ctx.logger,
|
|
1030
|
+
getWindow: () => this.createSummary()
|
|
1031
|
+
}), this.ctx.onClose(() => this.renderer?.stop());
|
|
1188
1032
|
}
|
|
1189
1033
|
// Ignore default logging of base reporter
|
|
1190
1034
|
printTestModule() {}
|
|
1191
1035
|
onWatcherRerun(files, trigger) {
|
|
1192
|
-
this.tests.clear();
|
|
1193
|
-
this.renderer?.start();
|
|
1194
|
-
super.onWatcherRerun(files, trigger);
|
|
1036
|
+
this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
|
|
1195
1037
|
}
|
|
1196
|
-
|
|
1038
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
1197
1039
|
if (this.isTTY) {
|
|
1198
1040
|
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1199
1041
|
this.ctx.logger.log(finalLog);
|
|
1200
1042
|
} else this.ctx.logger.log();
|
|
1201
|
-
this.tests.clear();
|
|
1202
|
-
this.renderer?.finish();
|
|
1203
|
-
super.onFinished(files, errors);
|
|
1043
|
+
this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
1204
1044
|
}
|
|
1205
1045
|
onTestModuleCollected(module) {
|
|
1206
1046
|
for (const test of module.children.allTests())
|
|
@@ -1208,22 +1048,16 @@ class DotReporter extends BaseReporter {
|
|
|
1208
1048
|
this.onTestCaseReady(test);
|
|
1209
1049
|
}
|
|
1210
1050
|
onTestCaseReady(test) {
|
|
1211
|
-
|
|
1212
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1213
|
-
this.renderer?.schedule();
|
|
1051
|
+
this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
|
|
1214
1052
|
}
|
|
1215
1053
|
onTestCaseResult(test) {
|
|
1216
1054
|
const result = test.result().state;
|
|
1217
1055
|
// On non-TTY the finished tests are printed immediately
|
|
1218
1056
|
if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
|
|
1219
|
-
super.onTestCaseResult(test);
|
|
1220
|
-
this.finishedTests.add(test.id);
|
|
1221
|
-
this.tests.set(test.id, result || "skipped");
|
|
1222
|
-
this.renderer?.schedule();
|
|
1057
|
+
super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
|
|
1223
1058
|
}
|
|
1224
1059
|
onTestModuleEnd(testModule) {
|
|
1225
|
-
super.onTestModuleEnd(testModule);
|
|
1226
|
-
if (!this.isTTY) return;
|
|
1060
|
+
if (super.onTestModuleEnd(testModule), !this.isTTY) return;
|
|
1227
1061
|
const columns = this.ctx.logger.getColumns();
|
|
1228
1062
|
if (this.tests.size < columns) return;
|
|
1229
1063
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
@@ -1233,11 +1067,9 @@ class DotReporter extends BaseReporter {
|
|
|
1233
1067
|
let count = 0;
|
|
1234
1068
|
for (const [id, state] of finishedTests) {
|
|
1235
1069
|
if (count++ >= columns) break;
|
|
1236
|
-
this.tests.delete(id);
|
|
1237
|
-
states.push(state);
|
|
1070
|
+
this.tests.delete(id), states.push(state);
|
|
1238
1071
|
}
|
|
1239
|
-
this.ctx.logger.log(formatTests(states));
|
|
1240
|
-
this.renderer?.schedule();
|
|
1072
|
+
this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
|
|
1241
1073
|
}
|
|
1242
1074
|
createSummary() {
|
|
1243
1075
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
@@ -1247,16 +1079,13 @@ class DotReporter extends BaseReporter {
|
|
|
1247
1079
|
const pass = {
|
|
1248
1080
|
char: "·",
|
|
1249
1081
|
color: c.green
|
|
1250
|
-
}
|
|
1251
|
-
const fail = {
|
|
1082
|
+
}, fail = {
|
|
1252
1083
|
char: "x",
|
|
1253
1084
|
color: c.red
|
|
1254
|
-
}
|
|
1255
|
-
const pending = {
|
|
1085
|
+
}, pending = {
|
|
1256
1086
|
char: "*",
|
|
1257
1087
|
color: c.yellow
|
|
1258
|
-
}
|
|
1259
|
-
const skip = {
|
|
1088
|
+
}, skip = {
|
|
1260
1089
|
char: "-",
|
|
1261
1090
|
color: (char) => c.dim(c.gray(char))
|
|
1262
1091
|
};
|
|
@@ -1273,37 +1102,27 @@ function getIcon(state) {
|
|
|
1273
1102
|
* Sibling icons with same color are merged into a single c.color() call.
|
|
1274
1103
|
*/
|
|
1275
1104
|
function formatTests(states) {
|
|
1276
|
-
let currentIcon = pending;
|
|
1277
|
-
let count = 0;
|
|
1278
|
-
let output = "";
|
|
1105
|
+
let currentIcon = pending, count = 0, output = "";
|
|
1279
1106
|
for (const state of states) {
|
|
1280
1107
|
const icon = getIcon(state);
|
|
1281
1108
|
if (currentIcon === icon) {
|
|
1282
1109
|
count++;
|
|
1283
1110
|
continue;
|
|
1284
1111
|
}
|
|
1285
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1286
|
-
// Start tracking new group
|
|
1287
|
-
count = 1;
|
|
1288
|
-
currentIcon = icon;
|
|
1112
|
+
output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
|
|
1289
1113
|
}
|
|
1290
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1291
|
-
return output;
|
|
1114
|
+
return output += currentIcon.color(currentIcon.char.repeat(count)), output;
|
|
1292
1115
|
}
|
|
1293
1116
|
|
|
1294
1117
|
// use Logger with custom Console to capture entire error printing
|
|
1295
1118
|
function capturePrintError(error, ctx, options) {
|
|
1296
1119
|
let output = "";
|
|
1297
1120
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1298
|
-
output += String(chunk);
|
|
1299
|
-
|
|
1300
|
-
} });
|
|
1301
|
-
const console = new Console(writable);
|
|
1302
|
-
const logger = {
|
|
1121
|
+
output += String(chunk), callback();
|
|
1122
|
+
} }), console = new Console(writable), logger = {
|
|
1303
1123
|
error: console.error.bind(console),
|
|
1304
1124
|
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1305
|
-
}
|
|
1306
|
-
const result = printError(error, ctx, logger, {
|
|
1125
|
+
}, result = printError(error, ctx, logger, {
|
|
1307
1126
|
showCodeFrame: false,
|
|
1308
1127
|
...options
|
|
1309
1128
|
});
|
|
@@ -1321,11 +1140,13 @@ function printError(error, ctx, logger, options) {
|
|
|
1321
1140
|
screenshotPaths: options.screenshotPaths,
|
|
1322
1141
|
printProperties: options.verbose,
|
|
1323
1142
|
parseErrorStacktrace(error) {
|
|
1324
|
-
// browser stack trace needs to be processed differently,
|
|
1325
|
-
// so there is a separate method for that
|
|
1326
|
-
if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
|
|
1327
1143
|
// node.js stack trace already has correct source map locations
|
|
1328
|
-
return
|
|
1144
|
+
return error.stacks ? options.fullStack ? error.stacks : error.stacks.filter((stack) => {
|
|
1145
|
+
return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
|
|
1146
|
+
}) : options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, {
|
|
1147
|
+
frameFilter: project.config.onStackTrace,
|
|
1148
|
+
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1149
|
+
}) : parseErrorStacktrace(error, {
|
|
1329
1150
|
frameFilter: project.config.onStackTrace,
|
|
1330
1151
|
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1331
1152
|
});
|
|
@@ -1333,15 +1154,14 @@ function printError(error, ctx, logger, options) {
|
|
|
1333
1154
|
});
|
|
1334
1155
|
}
|
|
1335
1156
|
function printErrorInner(error, project, options) {
|
|
1336
|
-
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1337
|
-
const logger = options.logger;
|
|
1157
|
+
const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
|
|
1338
1158
|
let e = error;
|
|
1339
1159
|
if (isPrimitive(e)) e = {
|
|
1340
1160
|
message: String(error).split(/\n/g)[0],
|
|
1341
1161
|
stack: String(error)
|
|
1342
1162
|
};
|
|
1343
1163
|
if (!e) {
|
|
1344
|
-
const error = new Error("unknown error");
|
|
1164
|
+
const error = /* @__PURE__ */ new Error("unknown error");
|
|
1345
1165
|
e = {
|
|
1346
1166
|
message: e ?? error.message,
|
|
1347
1167
|
stack: error.stack
|
|
@@ -1352,21 +1172,22 @@ function printErrorInner(error, project, options) {
|
|
|
1352
1172
|
printErrorMessage(e, logger);
|
|
1353
1173
|
return;
|
|
1354
1174
|
}
|
|
1355
|
-
const stacks = options.parseErrorStacktrace(e)
|
|
1356
|
-
|
|
1175
|
+
const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1176
|
+
// we are checking that this module was processed by us at one point
|
|
1357
1177
|
try {
|
|
1358
|
-
|
|
1178
|
+
const environments = [...Object.values(project._vite?.environments || {}), ...Object.values(project.browser?.vite.environments || {})], hasResult = environments.some((environment) => {
|
|
1179
|
+
const modules = environment.moduleGraph.getModulesByFile(stack.file);
|
|
1180
|
+
return [...modules?.values() || []].some((module) => !!module.transformResult);
|
|
1181
|
+
});
|
|
1182
|
+
return hasResult && existsSync(stack.file);
|
|
1359
1183
|
} catch {
|
|
1360
1184
|
return false;
|
|
1361
1185
|
}
|
|
1362
1186
|
});
|
|
1363
1187
|
if (type) printErrorType(type, project.vitest);
|
|
1364
|
-
printErrorMessage(e, logger)
|
|
1365
|
-
if (options.screenshotPaths?.length) {
|
|
1188
|
+
if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
|
|
1366
1189
|
const length = options.screenshotPaths.length;
|
|
1367
|
-
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1368
|
-
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1369
|
-
if (!e.diff) logger.error();
|
|
1190
|
+
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();
|
|
1370
1191
|
}
|
|
1371
1192
|
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1372
1193
|
if ("__vitest_rollup_error__" in e) {
|
|
@@ -1391,25 +1212,19 @@ function printErrorInner(error, project, options) {
|
|
|
1391
1212
|
}
|
|
1392
1213
|
});
|
|
1393
1214
|
}
|
|
1394
|
-
const testPath = e.VITEST_TEST_PATH;
|
|
1395
|
-
const testName = e.VITEST_TEST_NAME;
|
|
1396
|
-
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1215
|
+
const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1397
1216
|
// testName has testPath inside
|
|
1398
1217
|
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.`));
|
|
1399
1218
|
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:
|
|
1400
1219
|
- The error was thrown, while Vitest was running this test.
|
|
1401
1220
|
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1402
1221
|
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"));
|
|
1403
|
-
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
handleImportOutsideModuleError(e.stack || "", logger);
|
|
1412
|
-
return { nearest };
|
|
1222
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
|
|
1223
|
+
showCodeFrame: false,
|
|
1224
|
+
logger: options.logger,
|
|
1225
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1226
|
+
});
|
|
1227
|
+
return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
|
|
1413
1228
|
}
|
|
1414
1229
|
function printErrorType(type, ctx) {
|
|
1415
1230
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
@@ -1426,6 +1241,7 @@ const skipErrorProperties = new Set([
|
|
|
1426
1241
|
"actual",
|
|
1427
1242
|
"expected",
|
|
1428
1243
|
"diffOptions",
|
|
1244
|
+
"runnerError",
|
|
1429
1245
|
"sourceURL",
|
|
1430
1246
|
"column",
|
|
1431
1247
|
"line",
|
|
@@ -1453,7 +1269,7 @@ function handleImportOutsideModuleError(stack, logger) {
|
|
|
1453
1269
|
if (!esmErrors.some((e) => stack.includes(e))) return;
|
|
1454
1270
|
const path = normalize(stack.split("\n")[0].trim());
|
|
1455
1271
|
let name = path.split("/node_modules/").pop() || "";
|
|
1456
|
-
if (name
|
|
1272
|
+
if (name[0] === "@") name = name.split("/").slice(0, 2).join("/");
|
|
1457
1273
|
else name = name.split("/")[0];
|
|
1458
1274
|
if (name) printModuleWarningForPackage(logger, path, name);
|
|
1459
1275
|
else printModuleWarningForSourceCode(logger, path);
|
|
@@ -1491,10 +1307,8 @@ function printErrorMessage(error, logger) {
|
|
|
1491
1307
|
}
|
|
1492
1308
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1493
1309
|
for (const frame of stack) {
|
|
1494
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
1495
|
-
|
|
1496
|
-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1497
|
-
onStack?.(frame);
|
|
1310
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
|
|
1311
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
|
|
1498
1312
|
}
|
|
1499
1313
|
if (stack.length) logger.error();
|
|
1500
1314
|
if (hasProperties(errorProperties)) {
|
|
@@ -1509,26 +1323,19 @@ function hasProperties(obj) {
|
|
|
1509
1323
|
return false;
|
|
1510
1324
|
}
|
|
1511
1325
|
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1512
|
-
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1513
|
-
|
|
1514
|
-
const lines = source.split(lineSplitRE);
|
|
1515
|
-
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1516
|
-
let count = 0;
|
|
1517
|
-
let res = [];
|
|
1326
|
+
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;
|
|
1327
|
+
let count = 0, res = [];
|
|
1518
1328
|
const columns = process.stdout?.columns || 80;
|
|
1519
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
const lineLength = lines[j].length;
|
|
1329
|
+
for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
|
|
1330
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1331
|
+
if (j < 0 || j >= lines.length) continue;
|
|
1332
|
+
const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
|
|
1333
|
+
if (!strippedContent.startsWith("//# sourceMappingURL")) {
|
|
1525
1334
|
// too long, maybe it's a minified file, skip for codeframe
|
|
1526
|
-
if (
|
|
1527
|
-
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent))
|
|
1528
|
-
if (j === i) {
|
|
1335
|
+
if (strippedContent.length > 200) return "";
|
|
1336
|
+
if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
|
|
1529
1337
|
// push underline
|
|
1530
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
1531
|
-
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1338
|
+
const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1532
1339
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1533
1340
|
} else if (j > i) {
|
|
1534
1341
|
if (end > count) {
|
|
@@ -1538,8 +1345,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1538
1345
|
count += lineLength + 1;
|
|
1539
1346
|
}
|
|
1540
1347
|
}
|
|
1541
|
-
break;
|
|
1542
1348
|
}
|
|
1349
|
+
break;
|
|
1543
1350
|
}
|
|
1544
1351
|
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1545
1352
|
return res.join("\n");
|
|
@@ -1559,8 +1366,7 @@ class GithubActionsReporter {
|
|
|
1559
1366
|
}
|
|
1560
1367
|
onTestCaseAnnotate(testCase, annotation) {
|
|
1561
1368
|
if (!annotation.location) return;
|
|
1562
|
-
const type = getTitle(annotation.type)
|
|
1563
|
-
const formatted = formatMessage({
|
|
1369
|
+
const type = getTitle(annotation.type), formatted = formatMessage({
|
|
1564
1370
|
command: getType(annotation.type),
|
|
1565
1371
|
properties: {
|
|
1566
1372
|
file: annotation.location.file,
|
|
@@ -1572,17 +1378,15 @@ class GithubActionsReporter {
|
|
|
1572
1378
|
});
|
|
1573
1379
|
this.ctx.logger.log(`\n${formatted}`);
|
|
1574
1380
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
const projectErrors = new Array();
|
|
1381
|
+
onTestRunEnd(testModules, unhandledErrors) {
|
|
1382
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
|
|
1578
1383
|
for (const error of errors) projectErrors.push({
|
|
1579
1384
|
project: this.ctx.getRootProject(),
|
|
1580
1385
|
title: "Unhandled error",
|
|
1581
1386
|
error
|
|
1582
1387
|
});
|
|
1583
1388
|
for (const file of files) {
|
|
1584
|
-
const tasks = getTasks(file);
|
|
1585
|
-
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1389
|
+
const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
|
|
1586
1390
|
for (const task of tasks) {
|
|
1587
1391
|
if (task.result?.state !== "fail") continue;
|
|
1588
1392
|
const title = getFullName(task, " > ");
|
|
@@ -1600,8 +1404,7 @@ class GithubActionsReporter {
|
|
|
1600
1404
|
const result = capturePrintError(error, this.ctx, {
|
|
1601
1405
|
project,
|
|
1602
1406
|
task: file
|
|
1603
|
-
});
|
|
1604
|
-
const stack = result?.nearest;
|
|
1407
|
+
}), stack = result?.nearest;
|
|
1605
1408
|
if (!stack) continue;
|
|
1606
1409
|
const formatted = formatMessage({
|
|
1607
1410
|
command: "error",
|
|
@@ -1623,12 +1426,10 @@ const BUILT_IN_TYPES = [
|
|
|
1623
1426
|
"warning"
|
|
1624
1427
|
];
|
|
1625
1428
|
function getTitle(type) {
|
|
1626
|
-
|
|
1627
|
-
return type;
|
|
1429
|
+
return BUILT_IN_TYPES.includes(type) ? void 0 : type;
|
|
1628
1430
|
}
|
|
1629
1431
|
function getType(type) {
|
|
1630
|
-
|
|
1631
|
-
return "notice";
|
|
1432
|
+
return BUILT_IN_TYPES.includes(type) ? type : "notice";
|
|
1632
1433
|
}
|
|
1633
1434
|
function defaultOnWritePath(path) {
|
|
1634
1435
|
return path;
|
|
@@ -1638,12 +1439,9 @@ function defaultOnWritePath(path) {
|
|
|
1638
1439
|
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1639
1440
|
function formatMessage({ command, properties, message }) {
|
|
1640
1441
|
let result = `::${command}`;
|
|
1641
|
-
Object.entries(properties).forEach(([k, v], i) => {
|
|
1642
|
-
result += i === 0 ? " " : ","
|
|
1643
|
-
|
|
1644
|
-
});
|
|
1645
|
-
result += `::${escapeData(message)}`;
|
|
1646
|
-
return result;
|
|
1442
|
+
return Object.entries(properties).forEach(([k, v], i) => {
|
|
1443
|
+
result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
|
|
1444
|
+
}), result += `::${escapeData(message)}`, result;
|
|
1647
1445
|
}
|
|
1648
1446
|
function escapeData(s) {
|
|
1649
1447
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -1676,41 +1474,27 @@ class JsonReporter {
|
|
|
1676
1474
|
start = 0;
|
|
1677
1475
|
ctx;
|
|
1678
1476
|
options;
|
|
1477
|
+
coverageMap;
|
|
1679
1478
|
constructor(options) {
|
|
1680
1479
|
this.options = options;
|
|
1681
1480
|
}
|
|
1682
1481
|
onInit(ctx) {
|
|
1683
|
-
this.ctx = ctx;
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
const tests = getTests(files);
|
|
1690
|
-
const numTotalTests = tests.length;
|
|
1691
|
-
const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
|
|
1692
|
-
const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
|
|
1693
|
-
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
|
|
1694
|
-
const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
|
|
1695
|
-
const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
|
|
1696
|
-
const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
|
|
1697
|
-
const numTodoTests = tests.filter((t) => t.mode === "todo").length;
|
|
1698
|
-
const testResults = [];
|
|
1699
|
-
const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
|
|
1482
|
+
this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
|
|
1483
|
+
}
|
|
1484
|
+
onCoverage(coverageMap) {
|
|
1485
|
+
this.coverageMap = coverageMap;
|
|
1486
|
+
}
|
|
1487
|
+
async onTestRunEnd(testModules) {
|
|
1488
|
+
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;
|
|
1700
1489
|
for (const file of files) {
|
|
1701
1490
|
const tests = getTests([file]);
|
|
1702
1491
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1703
1492
|
if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
|
|
1704
|
-
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime)
|
|
1705
|
-
const assertionResults = tests.map((t) => {
|
|
1493
|
+
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
|
|
1706
1494
|
const ancestorTitles = [];
|
|
1707
1495
|
let iter = t.suite;
|
|
1708
|
-
while (iter)
|
|
1709
|
-
|
|
1710
|
-
iter = iter.suite;
|
|
1711
|
-
}
|
|
1712
|
-
ancestorTitles.reverse();
|
|
1713
|
-
return {
|
|
1496
|
+
while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
|
|
1497
|
+
return ancestorTitles.reverse(), {
|
|
1714
1498
|
ancestorTitles,
|
|
1715
1499
|
fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
|
|
1716
1500
|
status: StatusMap[t.result?.state || t.mode] || "skipped",
|
|
@@ -1746,13 +1530,10 @@ class JsonReporter {
|
|
|
1746
1530
|
startTime: this.start,
|
|
1747
1531
|
success,
|
|
1748
1532
|
testResults,
|
|
1749
|
-
coverageMap
|
|
1533
|
+
coverageMap: this.coverageMap
|
|
1750
1534
|
};
|
|
1751
1535
|
await this.writeReport(JSON.stringify(result));
|
|
1752
1536
|
}
|
|
1753
|
-
async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
|
|
1754
|
-
await this.logTasks(files, coverageMap);
|
|
1755
|
-
}
|
|
1756
1537
|
/**
|
|
1757
1538
|
* Writes the report to an output file if specified in the config,
|
|
1758
1539
|
* or logs it to the console otherwise.
|
|
@@ -1761,11 +1542,9 @@ class JsonReporter {
|
|
|
1761
1542
|
async writeReport(report) {
|
|
1762
1543
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
1763
1544
|
if (outputFile) {
|
|
1764
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1765
|
-
const outputDirectory = dirname(reportFile);
|
|
1545
|
+
const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
|
|
1766
1546
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1767
|
-
await promises.writeFile(reportFile, report, "utf-8");
|
|
1768
|
-
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1547
|
+
await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1769
1548
|
} else this.ctx.logger.log(report);
|
|
1770
1549
|
}
|
|
1771
1550
|
}
|
|
@@ -1788,8 +1567,7 @@ class IndentedLogger {
|
|
|
1788
1567
|
|
|
1789
1568
|
function flattenTasks$1(task, baseName = "") {
|
|
1790
1569
|
const base = baseName ? `${baseName} > ` : "";
|
|
1791
|
-
|
|
1792
|
-
else return [{
|
|
1570
|
+
return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
|
|
1793
1571
|
...task,
|
|
1794
1572
|
name: `${base}${task.name}`
|
|
1795
1573
|
}];
|
|
@@ -1797,21 +1575,16 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1797
1575
|
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1798
1576
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1799
1577
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1800
|
-
value = String(value || "").replace(regex, "")
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
"g"
|
|
1807
|
-
/* eslint-enable */
|
|
1808
|
-
);
|
|
1809
|
-
value = value.replace(regex, "");
|
|
1810
|
-
}
|
|
1578
|
+
if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
|
|
1579
|
+
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1580
|
+
"([\\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]))",
|
|
1581
|
+
"g"
|
|
1582
|
+
/* eslint-enable */
|
|
1583
|
+
), value = value.replace(regex, "");
|
|
1811
1584
|
return value;
|
|
1812
1585
|
}
|
|
1813
1586
|
function escapeXML(value) {
|
|
1814
|
-
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
1587
|
+
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"), true);
|
|
1815
1588
|
}
|
|
1816
1589
|
function executionTime(durationMS) {
|
|
1817
1590
|
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
@@ -1832,8 +1605,7 @@ class JUnitReporter {
|
|
|
1832
1605
|
fileFd;
|
|
1833
1606
|
options;
|
|
1834
1607
|
constructor(options) {
|
|
1835
|
-
this.options = { ...options };
|
|
1836
|
-
this.options.includeConsoleOutput ??= true;
|
|
1608
|
+
this.options = { ...options }, this.options.includeConsoleOutput ??= true;
|
|
1837
1609
|
}
|
|
1838
1610
|
async onInit(ctx) {
|
|
1839
1611
|
this.ctx = ctx;
|
|
@@ -1843,14 +1615,12 @@ class JUnitReporter {
|
|
|
1843
1615
|
const outputDirectory = dirname(this.reportFile);
|
|
1844
1616
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1845
1617
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
1846
|
-
this.fileFd = fileFd
|
|
1847
|
-
this.baseLog = async (text) => {
|
|
1618
|
+
this.fileFd = fileFd, this.baseLog = async (text) => {
|
|
1848
1619
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
1849
1620
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
1850
1621
|
};
|
|
1851
1622
|
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1852
|
-
this._timeStart = /* @__PURE__ */ new Date();
|
|
1853
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
1623
|
+
this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
|
|
1854
1624
|
}
|
|
1855
1625
|
async writeElement(name, attrs, children) {
|
|
1856
1626
|
const pairs = [];
|
|
@@ -1859,18 +1629,12 @@ class JUnitReporter {
|
|
|
1859
1629
|
if (attr === void 0) continue;
|
|
1860
1630
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
1861
1631
|
}
|
|
1862
|
-
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
|
|
1863
|
-
this.logger.indent();
|
|
1864
|
-
await children.call(this);
|
|
1865
|
-
this.logger.unindent();
|
|
1866
|
-
await this.logger.log(`</${name}>`);
|
|
1632
|
+
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`), this.logger.indent(), await children.call(this), this.logger.unindent(), await this.logger.log(`</${name}>`);
|
|
1867
1633
|
}
|
|
1868
1634
|
async writeLogs(task, type) {
|
|
1869
1635
|
if (task.logs == null || task.logs.length === 0) return;
|
|
1870
|
-
const logType = type === "err" ? "stderr" : "stdout";
|
|
1871
|
-
|
|
1872
|
-
if (logs.length === 0) return;
|
|
1873
|
-
await this.writeElement(`system-${type}`, {}, async () => {
|
|
1636
|
+
const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
|
|
1637
|
+
logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
|
|
1874
1638
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
1875
1639
|
});
|
|
1876
1640
|
}
|
|
@@ -1883,27 +1647,18 @@ class JUnitReporter {
|
|
|
1883
1647
|
};
|
|
1884
1648
|
if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
|
|
1885
1649
|
else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
|
|
1886
|
-
else if (typeof this.options.classname === "string") classname = this.options.classname;
|
|
1887
1650
|
await this.writeElement("testcase", {
|
|
1888
1651
|
classname,
|
|
1889
1652
|
file: this.options.addFileAttribute ? filename : void 0,
|
|
1890
1653
|
name: task.name,
|
|
1891
1654
|
time: getDuration(task)
|
|
1892
1655
|
}, async () => {
|
|
1893
|
-
if (this.options.includeConsoleOutput)
|
|
1894
|
-
await this.writeLogs(task, "out");
|
|
1895
|
-
await this.writeLogs(task, "err");
|
|
1896
|
-
}
|
|
1656
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
|
|
1897
1657
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1898
1658
|
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>");
|
|
1659
|
+
await this.logger.log("<properties>"), this.logger.indent();
|
|
1660
|
+
for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
|
|
1661
|
+
this.logger.unindent(), await this.logger.log("</properties>");
|
|
1907
1662
|
}
|
|
1908
1663
|
if (task.result?.state === "fail") {
|
|
1909
1664
|
const errors = task.result.errors || [];
|
|
@@ -1922,11 +1677,11 @@ class JUnitReporter {
|
|
|
1922
1677
|
});
|
|
1923
1678
|
}
|
|
1924
1679
|
}
|
|
1925
|
-
async
|
|
1680
|
+
async onTestRunEnd(testModules) {
|
|
1681
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1926
1682
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
1927
1683
|
const transformed = files.map((file) => {
|
|
1928
|
-
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task))
|
|
1929
|
-
const stats = tasks.reduce((stats, task) => {
|
|
1684
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
|
|
1930
1685
|
return {
|
|
1931
1686
|
passed: stats.passed + Number(task.result?.state === "pass"),
|
|
1932
1687
|
failures: stats.failures + Number(task.result?.state === "fail"),
|
|
@@ -1936,41 +1691,29 @@ class JUnitReporter {
|
|
|
1936
1691
|
passed: 0,
|
|
1937
1692
|
failures: 0,
|
|
1938
1693
|
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
|
-
}
|
|
1694
|
+
}), suites = getSuites(file);
|
|
1695
|
+
for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
|
|
1946
1696
|
// 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
|
-
}
|
|
1697
|
+
if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
|
|
1698
|
+
id: file.id,
|
|
1699
|
+
type: "test",
|
|
1700
|
+
name: file.name,
|
|
1701
|
+
mode: "run",
|
|
1702
|
+
result: file.result,
|
|
1703
|
+
meta: {},
|
|
1704
|
+
timeout: 0,
|
|
1705
|
+
context: null,
|
|
1706
|
+
suite: null,
|
|
1707
|
+
file: null,
|
|
1708
|
+
annotations: []
|
|
1709
|
+
});
|
|
1963
1710
|
return {
|
|
1964
1711
|
...file,
|
|
1965
1712
|
tasks,
|
|
1966
1713
|
stats
|
|
1967
1714
|
};
|
|
1968
|
-
})
|
|
1969
|
-
|
|
1970
|
-
stats.tests += file.tasks.length;
|
|
1971
|
-
stats.failures += file.stats.failures;
|
|
1972
|
-
stats.time += file.result?.duration || 0;
|
|
1973
|
-
return stats;
|
|
1715
|
+
}), stats = transformed.reduce((stats, file) => {
|
|
1716
|
+
return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
|
|
1974
1717
|
}, {
|
|
1975
1718
|
name: this.options.suiteName || "vitest tests",
|
|
1976
1719
|
tests: 0,
|
|
@@ -1978,7 +1721,7 @@ class JUnitReporter {
|
|
|
1978
1721
|
errors: 0,
|
|
1979
1722
|
time: 0
|
|
1980
1723
|
});
|
|
1981
|
-
await this.writeElement("testsuites", {
|
|
1724
|
+
if (await this.writeElement("testsuites", {
|
|
1982
1725
|
...stats,
|
|
1983
1726
|
time: executionTime(stats.time)
|
|
1984
1727
|
}, async () => {
|
|
@@ -1997,16 +1740,13 @@ class JUnitReporter {
|
|
|
1997
1740
|
await this.writeTasks(file.tasks, filename);
|
|
1998
1741
|
});
|
|
1999
1742
|
}
|
|
2000
|
-
});
|
|
2001
|
-
|
|
2002
|
-
await this.fileFd?.close();
|
|
2003
|
-
this.fileFd = void 0;
|
|
1743
|
+
}), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
1744
|
+
await this.fileFd?.close(), this.fileFd = void 0;
|
|
2004
1745
|
}
|
|
2005
1746
|
}
|
|
2006
1747
|
|
|
2007
1748
|
function yamlString(str) {
|
|
2008
|
-
|
|
2009
|
-
return `"${str.replace(/"/g, "\\\"")}"`;
|
|
1749
|
+
return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
|
|
2010
1750
|
}
|
|
2011
1751
|
function tapString(str) {
|
|
2012
1752
|
return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
|
|
@@ -2015,77 +1755,45 @@ class TapReporter {
|
|
|
2015
1755
|
ctx;
|
|
2016
1756
|
logger;
|
|
2017
1757
|
onInit(ctx) {
|
|
2018
|
-
this.ctx = ctx;
|
|
2019
|
-
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
1758
|
+
this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2020
1759
|
}
|
|
2021
1760
|
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 "";
|
|
1761
|
+
return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2026
1762
|
}
|
|
2027
1763
|
logErrorDetails(error, stack) {
|
|
2028
1764
|
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)
|
|
1765
|
+
if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
|
|
2032
1766
|
// For compatibility with tap-mocha-reporter
|
|
2033
1767
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2034
1768
|
}
|
|
2035
1769
|
logTasks(tasks) {
|
|
2036
1770
|
this.logger.log(`1..${tasks.length}`);
|
|
2037
1771
|
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 {
|
|
1772
|
+
const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
|
|
1773
|
+
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("}");
|
|
1774
|
+
else {
|
|
2048
1775
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2049
1776
|
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
|
-
}
|
|
1777
|
+
if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
|
|
1778
|
+
this.logger.log(`# ${type}: ${message}`);
|
|
1779
|
+
}), this.logger.unindent();
|
|
1780
|
+
if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
|
|
1781
|
+
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
|
|
1782
|
+
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}`)}`);
|
|
1783
|
+
if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
1784
|
+
}), this.logger.log("..."), this.logger.unindent();
|
|
2076
1785
|
}
|
|
2077
1786
|
}
|
|
2078
1787
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
this.logTasks(files);
|
|
1788
|
+
onTestRunEnd(testModules) {
|
|
1789
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1790
|
+
this.logger.log("TAP version 13"), this.logTasks(files);
|
|
2082
1791
|
}
|
|
2083
1792
|
}
|
|
2084
1793
|
|
|
2085
1794
|
function flattenTasks(task, baseName = "") {
|
|
2086
1795
|
const base = baseName ? `${baseName} > ` : "";
|
|
2087
|
-
|
|
2088
|
-
else return [{
|
|
1796
|
+
return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
|
|
2089
1797
|
...task,
|
|
2090
1798
|
name: `${base}${task.name}`
|
|
2091
1799
|
}];
|
|
@@ -2094,65 +1802,33 @@ class TapFlatReporter extends TapReporter {
|
|
|
2094
1802
|
onInit(ctx) {
|
|
2095
1803
|
super.onInit(ctx);
|
|
2096
1804
|
}
|
|
2097
|
-
|
|
1805
|
+
onTestRunEnd(testModules) {
|
|
2098
1806
|
this.ctx.logger.log("TAP version 13");
|
|
2099
|
-
const flatTasks =
|
|
1807
|
+
const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
|
|
2100
1808
|
this.logTasks(flatTasks);
|
|
2101
1809
|
}
|
|
2102
1810
|
}
|
|
2103
1811
|
|
|
1812
|
+
class TreeReporter extends DefaultReporter {
|
|
1813
|
+
verbose = true;
|
|
1814
|
+
renderSucceed = true;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
2104
1817
|
class VerboseReporter extends DefaultReporter {
|
|
2105
1818
|
verbose = true;
|
|
2106
1819
|
renderSucceed = true;
|
|
2107
|
-
printTestModule(
|
|
2108
|
-
//
|
|
2109
|
-
// but don't print it in the CLI because we
|
|
2110
|
-
// print all the tests when they finish
|
|
2111
|
-
// instead of printing them when the test file finishes
|
|
2112
|
-
if (this.isTTY) return super.printTestModule(module);
|
|
1820
|
+
printTestModule(_module) {
|
|
1821
|
+
// don't print test module, only print tests
|
|
2113
1822
|
}
|
|
2114
1823
|
onTestCaseResult(test) {
|
|
2115
1824
|
super.onTestCaseResult(test);
|
|
2116
|
-
// don't print tests in TTY as they go, only print them
|
|
2117
|
-
// in the CLI when they finish
|
|
2118
|
-
if (this.isTTY) return;
|
|
2119
1825
|
const testResult = test.result();
|
|
2120
1826
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2121
|
-
let title = ` ${
|
|
2122
|
-
if (test.
|
|
2123
|
-
title +=
|
|
2124
|
-
|
|
2125
|
-
const diagnostic = test.diagnostic();
|
|
2126
|
-
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2127
|
-
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2128
|
-
this.log(title);
|
|
2129
|
-
if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
|
|
2130
|
-
if (test.annotations().length) {
|
|
2131
|
-
this.log();
|
|
2132
|
-
this.printAnnotations(test, "log", 3);
|
|
2133
|
-
this.log();
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
printTestSuite(testSuite) {
|
|
2137
|
-
const indentation = " ".repeat(getIndentation(testSuite.task));
|
|
2138
|
-
const tests = Array.from(testSuite.children.allTests());
|
|
2139
|
-
const state = getStateSymbol(testSuite.task);
|
|
2140
|
-
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
2141
|
-
}
|
|
2142
|
-
getTestName(test) {
|
|
2143
|
-
return test.name;
|
|
2144
|
-
}
|
|
2145
|
-
getTestIndentation(test) {
|
|
2146
|
-
return " ".repeat(getIndentation(test));
|
|
1827
|
+
let title = ` ${this.getEntityPrefix(test)} `;
|
|
1828
|
+
if (title += test.module.task.name, test.location) title += c.dim(`:${test.location.line}:${test.location.column}`);
|
|
1829
|
+
if (title += separator, title += getTestName(test.task, separator), title += this.getTestCaseSuffix(test), this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error.message}`)));
|
|
1830
|
+
if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
|
|
2147
1831
|
}
|
|
2148
|
-
formatShortError() {
|
|
2149
|
-
// Short errors are not shown in tree-view
|
|
2150
|
-
return "";
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
function getIndentation(suite, level = 1) {
|
|
2154
|
-
if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
|
|
2155
|
-
return level;
|
|
2156
1832
|
}
|
|
2157
1833
|
|
|
2158
1834
|
const ReportersMap = {
|
|
@@ -2164,8 +1840,9 @@ const ReportersMap = {
|
|
|
2164
1840
|
"tap": TapReporter,
|
|
2165
1841
|
"tap-flat": TapFlatReporter,
|
|
2166
1842
|
"junit": JUnitReporter,
|
|
1843
|
+
"tree": TreeReporter,
|
|
2167
1844
|
"hanging-process": HangingProcessReporter,
|
|
2168
1845
|
"github-actions": GithubActionsReporter
|
|
2169
1846
|
};
|
|
2170
1847
|
|
|
2171
|
-
export { BlobReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c,
|
|
1848
|
+
export { BlobReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, stringify as d, printError as e, formatProjectName as f, getStateSymbol as g, errorBanner as h, divider as i, generateCodeFrame as j, parse as p, readBlobs as r, separator as s, truncateString as t, utils as u, withLabel as w };
|