vitest 4.0.0-beta.1 → 4.0.0-beta.10
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 +2 -2
- package/dist/browser.d.ts +13 -14
- package/dist/browser.js +6 -5
- package/dist/chunks/base.Cjha6usc.js +129 -0
- package/dist/chunks/{benchmark.CYdenmiT.js → benchmark.CJUa-Hsa.js} +6 -8
- 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.yFAklsD1.d.ts} +5 -5
- package/dist/chunks/{cac.D3EzDDZd.js → cac.DCxo_nSu.js} +70 -152
- package/dist/chunks/{cli-api.Dn5gKePv.js → cli-api.BJJXh9BV.js} +1330 -1677
- package/dist/chunks/{config.d.HJdfX-8k.d.ts → config.d.B_LthbQq.d.ts} +58 -63
- package/dist/chunks/{console.CtFJOzRO.js → console.7h5kHUIf.js} +34 -70
- package/dist/chunks/{constants.DnKduX2e.js → constants.D_Q9UYh-.js} +1 -9
- package/dist/chunks/{coverage.Cwa-XhJt.js → coverage.BCU-r2QL.js} +515 -781
- 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.DG-S3xFe.js} +8 -8
- package/dist/chunks/{index.CZI_8rVt.js → index.BIP7prJq.js} +289 -608
- package/dist/chunks/{index.B521nVV-.js → index.Bgo3tNWt.js} +23 -4
- package/dist/chunks/{index.TfbsX-3I.js → index.BjKEiSn0.js} +14 -24
- package/dist/chunks/{index.BWf_gE5n.js → index.CMfqw92x.js} +7 -6
- 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.D9nBoC4p.d.ts +201 -0
- package/dist/chunks/moduleTransport.I-bgQy0S.js +19 -0
- package/dist/chunks/{node.fjCdwEIl.js → node.CyipiPvJ.js} +1 -1
- package/dist/chunks/{plugin.d.C2EcJUjo.d.ts → plugin.d.BMVSnsGV.d.ts} +1 -1
- package/dist/chunks/{reporters.d.DxZg19fy.d.ts → reporters.d.BUWjmRYq.d.ts} +1226 -1291
- package/dist/chunks/resolveSnapshotEnvironment.Bkht6Yor.js +81 -0
- package/dist/chunks/resolver.Bx6lE0iq.js +119 -0
- package/dist/chunks/rpc.BKr6mtxz.js +65 -0
- package/dist/chunks/{setup-common.D7ZqXFx-.js → setup-common.uiMcU3cv.js} +17 -29
- package/dist/chunks/startModuleRunner.p67gbNo9.js +665 -0
- package/dist/chunks/{suite.d.FvehnV49.d.ts → suite.d.BJWk38HB.d.ts} +1 -1
- package/dist/chunks/test.BiqSKISg.js +214 -0
- package/dist/chunks/{typechecker.CVytUJuF.js → typechecker.DB-fIMaH.js} +144 -213
- package/dist/chunks/{utils.CAioKnHs.js → utils.C2YI6McM.js} +5 -14
- package/dist/chunks/{utils.XdZDrNZV.js → utils.D2R2NiOH.js} +8 -27
- package/dist/chunks/{vi.bdSIJ99Y.js → vi.ZPgvtBao.js} +156 -305
- package/dist/chunks/{vm.BThCzidc.js → vm.Ca0Y0W5f.js} +116 -226
- package/dist/chunks/{worker.d.DoNjFAiv.d.ts → worker.d.BDsXGkwh.d.ts} +28 -22
- package/dist/chunks/{worker.d.CmvJfRGs.d.ts → worker.d.BNcX_2mH.d.ts} +1 -1
- package/dist/cli.js +4 -4
- 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 -7
- package/dist/environments.d.ts +9 -13
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +38 -45
- package/dist/index.js +7 -9
- 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 +40 -41
- package/dist/node.js +23 -33
- package/dist/reporters.d.ts +12 -13
- package/dist/reporters.js +3 -3
- package/dist/runners.d.ts +3 -3
- package/dist/runners.js +13 -232
- package/dist/snapshot.js +2 -2
- package/dist/suite.d.ts +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +90 -47
- package/dist/workers/forks.js +34 -10
- package/dist/workers/runVmTests.js +36 -56
- package/dist/workers/threads.js +34 -10
- package/dist/workers/vmForks.js +11 -10
- package/dist/workers/vmThreads.js +11 -10
- package/dist/workers.d.ts +5 -4
- package/dist/workers.js +35 -17
- package/globals.d.ts +17 -17
- package/package.json +32 -31
- 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/execute.d.ts +0 -148
- package/dist/execute.js +0 -13
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
2
2
|
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve, dirname, isAbsolute, relative, basename, normalize } from 'pathe';
|
|
4
|
-
import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.
|
|
4
|
+
import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.DB-fIMaH.js';
|
|
5
5
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
6
6
|
import { getTestName, getFullName, hasFailed, getTests, getSuites, getTasks } from '@vitest/runner/utils';
|
|
7
7
|
import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
|
|
8
|
-
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
8
|
+
import { parseStacktrace, parseErrorStacktrace, defaultStackIgnorePatterns } from '@vitest/utils/source-map';
|
|
9
9
|
import c from 'tinyrainbow';
|
|
10
10
|
import { i as isTTY } from './env.D4Lgay0q.js';
|
|
11
11
|
import { stripVTControlCharacters } from 'node:util';
|
|
@@ -125,16 +125,19 @@ class BlobReporter {
|
|
|
125
125
|
start = 0;
|
|
126
126
|
ctx;
|
|
127
127
|
options;
|
|
128
|
+
coverage;
|
|
128
129
|
constructor(options) {
|
|
129
130
|
this.options = options;
|
|
130
131
|
}
|
|
131
132
|
onInit(ctx) {
|
|
132
133
|
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
133
|
-
this.ctx = ctx;
|
|
134
|
-
|
|
134
|
+
this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
|
|
135
|
+
}
|
|
136
|
+
onCoverage(coverage) {
|
|
137
|
+
this.coverage = coverage;
|
|
135
138
|
}
|
|
136
|
-
async
|
|
137
|
-
const executionTime = performance.now() - this.start;
|
|
139
|
+
async onTestRunEnd(testModules, unhandledErrors) {
|
|
140
|
+
const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
|
|
138
141
|
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
139
142
|
if (!outputFile) {
|
|
140
143
|
const shard = this.ctx.config.shard;
|
|
@@ -142,43 +145,34 @@ class BlobReporter {
|
|
|
142
145
|
}
|
|
143
146
|
const modules = this.ctx.projects.map((project) => {
|
|
144
147
|
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
145
|
-
|
|
146
|
-
return [
|
|
148
|
+
return mod[1].file ? [
|
|
147
149
|
mod[0],
|
|
148
150
|
mod[1].file,
|
|
149
151
|
mod[1].url
|
|
150
|
-
];
|
|
152
|
+
] : null;
|
|
151
153
|
}).filter((x) => x != null)];
|
|
152
|
-
})
|
|
153
|
-
const report = [
|
|
154
|
+
}), report = [
|
|
154
155
|
this.ctx.version,
|
|
155
156
|
files,
|
|
156
157
|
errors,
|
|
157
158
|
modules,
|
|
158
159
|
coverage,
|
|
159
160
|
executionTime
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
await writeBlob(report, reportFile);
|
|
163
|
-
this.ctx.logger.log("blob report written to", reportFile);
|
|
161
|
+
], reportFile = resolve(this.ctx.config.root, outputFile);
|
|
162
|
+
await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
async function writeBlob(content, filename) {
|
|
167
|
-
const report = stringify(content);
|
|
168
|
-
const dir = dirname(filename);
|
|
166
|
+
const report = stringify(content), dir = dirname(filename);
|
|
169
167
|
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
170
168
|
await writeFile(filename, report, "utf-8");
|
|
171
169
|
}
|
|
172
170
|
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
173
171
|
// using process.cwd() because --merge-reports can only be used in CLI
|
|
174
|
-
const resolvedDir = resolve(process.cwd(), blobsDirectory)
|
|
175
|
-
|
|
176
|
-
const promises = blobsFiles.map(async (filename) => {
|
|
177
|
-
const fullPath = resolve(resolvedDir, filename);
|
|
178
|
-
const stats = await stat(fullPath);
|
|
172
|
+
const resolvedDir = resolve(process.cwd(), blobsDirectory), blobsFiles = await readdir(resolvedDir), promises = blobsFiles.map(async (filename) => {
|
|
173
|
+
const fullPath = resolve(resolvedDir, filename), stats = await stat(fullPath);
|
|
179
174
|
if (!stats.isFile()) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
180
|
-
const content = await readFile(fullPath, "utf-8");
|
|
181
|
-
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
175
|
+
const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
182
176
|
if (!version) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
183
177
|
return {
|
|
184
178
|
version,
|
|
@@ -189,8 +183,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
189
183
|
file: filename,
|
|
190
184
|
executionTime
|
|
191
185
|
};
|
|
192
|
-
});
|
|
193
|
-
const blobs = await Promise.all(promises);
|
|
186
|
+
}), blobs = await Promise.all(promises);
|
|
194
187
|
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
195
188
|
const versions = new Set(blobs.map((blob) => blob.version));
|
|
196
189
|
if (versions.size > 1) throw new Error(`vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received\n\n${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`);
|
|
@@ -200,23 +193,19 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
200
193
|
blobs.forEach((blob) => {
|
|
201
194
|
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
202
195
|
const project = projects[projectName];
|
|
203
|
-
|
|
204
|
-
moduleIds.forEach(([moduleId, file, url]) => {
|
|
196
|
+
project && moduleIds.forEach(([moduleId, file, url]) => {
|
|
205
197
|
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
206
|
-
moduleNode.url = url
|
|
207
|
-
|
|
208
|
-
|
|
198
|
+
moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
|
|
199
|
+
code: " ",
|
|
200
|
+
map: null
|
|
201
|
+
}, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
209
202
|
});
|
|
210
203
|
});
|
|
211
204
|
});
|
|
212
205
|
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
213
|
-
const time1 = f1.result?.startTime || 0;
|
|
214
|
-
const time2 = f2.result?.startTime || 0;
|
|
206
|
+
const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
|
|
215
207
|
return time1 - time2;
|
|
216
|
-
});
|
|
217
|
-
const errors = blobs.flatMap((blob) => blob.errors);
|
|
218
|
-
const coverages = blobs.map((blob) => blob.coverage);
|
|
219
|
-
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
208
|
+
}), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
|
|
220
209
|
return {
|
|
221
210
|
files,
|
|
222
211
|
errors,
|
|
@@ -258,26 +247,18 @@ function errorBanner(message) {
|
|
|
258
247
|
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
259
248
|
}
|
|
260
249
|
function divider(text, left, right, color) {
|
|
261
|
-
const cols = getCols();
|
|
262
|
-
const c = color || ((text) => text);
|
|
250
|
+
const cols = getCols(), c = color || ((text) => text);
|
|
263
251
|
if (text) {
|
|
264
252
|
const textLength = stripVTControlCharacters(text).length;
|
|
265
253
|
if (left == null && right != null) left = cols - textLength - right;
|
|
266
|
-
else
|
|
267
|
-
|
|
268
|
-
right = cols - textLength - left;
|
|
269
|
-
}
|
|
270
|
-
left = Math.max(0, left);
|
|
271
|
-
right = Math.max(0, right);
|
|
272
|
-
return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
|
|
254
|
+
else left = left ?? Math.floor((cols - textLength) / 2), right = cols - textLength - left;
|
|
255
|
+
return left = Math.max(0, left), right = Math.max(0, right), `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
|
|
273
256
|
}
|
|
274
257
|
return F_LONG_DASH.repeat(cols);
|
|
275
258
|
}
|
|
276
259
|
function formatTestPath(root, path) {
|
|
277
260
|
if (isAbsolute(path)) path = relative(root, path);
|
|
278
|
-
const dir = dirname(path);
|
|
279
|
-
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
280
|
-
const base = basename(path, ext);
|
|
261
|
+
const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
|
|
281
262
|
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
282
263
|
}
|
|
283
264
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
@@ -289,8 +270,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
289
270
|
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
290
271
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
291
272
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
292
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`)
|
|
293
|
-
tail.forEach((key) => {
|
|
273
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
|
|
294
274
|
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
295
275
|
});
|
|
296
276
|
}
|
|
@@ -298,8 +278,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
298
278
|
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
299
279
|
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
300
280
|
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
301
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
302
|
-
uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
281
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`), uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
303
282
|
});
|
|
304
283
|
}
|
|
305
284
|
return summary;
|
|
@@ -309,10 +288,7 @@ function countTestErrors(tasks) {
|
|
|
309
288
|
}
|
|
310
289
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
311
290
|
if (tasks.length === 0) return c.dim(`no ${name}`);
|
|
312
|
-
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
313
|
-
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
314
|
-
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
315
|
-
const todo = tasks.filter((i) => i.mode === "todo");
|
|
291
|
+
const passed = tasks.filter((i) => i.result?.state === "pass"), failed = tasks.filter((i) => i.result?.state === "fail"), skipped = tasks.filter((i) => i.mode === "skip"), todo = tasks.filter((i) => i.mode === "todo");
|
|
316
292
|
return [
|
|
317
293
|
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
318
294
|
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
@@ -326,16 +302,13 @@ function getStateSymbol(task) {
|
|
|
326
302
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
327
303
|
if (task.type === "suite") return pointer;
|
|
328
304
|
}
|
|
329
|
-
|
|
330
|
-
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
331
|
-
return " ";
|
|
305
|
+
return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
|
|
332
306
|
}
|
|
333
307
|
function formatTimeString(date) {
|
|
334
308
|
return date.toTimeString().split(" ")[0];
|
|
335
309
|
}
|
|
336
310
|
function formatTime(time) {
|
|
337
|
-
|
|
338
|
-
return `${Math.round(time)}ms`;
|
|
311
|
+
return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
|
|
339
312
|
}
|
|
340
313
|
function formatProjectName(project, suffix = " ") {
|
|
341
314
|
if (!project?.name) return "";
|
|
@@ -356,8 +329,7 @@ function padSummaryTitle(str) {
|
|
|
356
329
|
}
|
|
357
330
|
function truncateString(text, maxLength) {
|
|
358
331
|
const plainText = stripVTControlCharacters(text);
|
|
359
|
-
|
|
360
|
-
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
332
|
+
return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
|
|
361
333
|
}
|
|
362
334
|
function capitalize(text) {
|
|
363
335
|
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
@@ -403,9 +375,7 @@ class BaseReporter {
|
|
|
403
375
|
this.isTTY = options.isTTY ?? isTTY;
|
|
404
376
|
}
|
|
405
377
|
onInit(ctx) {
|
|
406
|
-
this.ctx = ctx;
|
|
407
|
-
this.ctx.logger.printBanner();
|
|
408
|
-
this.start = performance$1.now();
|
|
378
|
+
this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
|
|
409
379
|
}
|
|
410
380
|
log(...messages) {
|
|
411
381
|
this.ctx.logger.log(...messages);
|
|
@@ -416,9 +386,9 @@ class BaseReporter {
|
|
|
416
386
|
relative(path) {
|
|
417
387
|
return relative(this.ctx.config.root, path);
|
|
418
388
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
389
|
+
onTestRunEnd(testModules, unhandledErrors, _reason) {
|
|
390
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors];
|
|
391
|
+
if (this.end = performance$1.now(), !files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
422
392
|
else this.reportSummary(files, errors);
|
|
423
393
|
}
|
|
424
394
|
onTestCaseResult(testCase) {
|
|
@@ -437,13 +407,10 @@ class BaseReporter {
|
|
|
437
407
|
printTestModule(testModule) {
|
|
438
408
|
const moduleState = testModule.state();
|
|
439
409
|
if (moduleState === "queued" || moduleState === "pending") return;
|
|
440
|
-
let testsCount = 0;
|
|
441
|
-
let failedCount = 0;
|
|
442
|
-
let skippedCount = 0;
|
|
410
|
+
let testsCount = 0, failedCount = 0, skippedCount = 0;
|
|
443
411
|
// delaying logs to calculate the test stats first
|
|
444
412
|
// which minimizes the amount of for loops
|
|
445
|
-
const logs = [];
|
|
446
|
-
const originalLog = this.log.bind(this);
|
|
413
|
+
const logs = [], originalLog = this.log.bind(this);
|
|
447
414
|
this.log = (msg) => logs.push(msg);
|
|
448
415
|
const visit = (suiteState, children) => {
|
|
449
416
|
for (const child of children) if (child.type === "suite") {
|
|
@@ -453,8 +420,7 @@ class BaseReporter {
|
|
|
453
420
|
visit(suiteState, child.children);
|
|
454
421
|
} else {
|
|
455
422
|
const testResult = child.result();
|
|
456
|
-
testsCount++;
|
|
457
|
-
if (testResult.state === "failed") failedCount++;
|
|
423
|
+
if (testsCount++, testResult.state === "failed") failedCount++;
|
|
458
424
|
else if (testResult.state === "skipped") skippedCount++;
|
|
459
425
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
460
426
|
// Skipped suites are hidden when --hideSkippedTests
|
|
@@ -471,24 +437,20 @@ class BaseReporter {
|
|
|
471
437
|
tests: testsCount,
|
|
472
438
|
failed: failedCount,
|
|
473
439
|
skipped: skippedCount
|
|
474
|
-
}));
|
|
475
|
-
logs.forEach((log) => this.log(log));
|
|
440
|
+
})), logs.forEach((log) => this.log(log));
|
|
476
441
|
}
|
|
477
442
|
printTestCase(moduleState, test) {
|
|
478
|
-
const testResult = test.result();
|
|
479
|
-
const { duration, retryCount, repeatCount } = test.diagnostic() || {};
|
|
480
|
-
const padding = this.getTestIndentation(test.task);
|
|
443
|
+
const testResult = test.result(), { duration, retryCount, repeatCount } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task);
|
|
481
444
|
let suffix = this.getDurationPrefix(test.task);
|
|
482
445
|
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
483
446
|
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
484
|
-
if (testResult.state === "failed")
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
447
|
+
if (testResult.state === "failed")
|
|
448
|
+
// print short errors, full errors will be at the end in summary
|
|
449
|
+
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix), testResult.errors.forEach((error) => {
|
|
450
|
+
const message = this.formatShortError(error);
|
|
451
|
+
if (message) this.log(c.red(` ${padding}${message}`));
|
|
452
|
+
});
|
|
453
|
+
else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
492
454
|
else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (testResult.state === "skipped" && testResult.note) this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${c.dim(c.gray(` [${testResult.note}]`))}`);
|
|
493
455
|
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
494
456
|
}
|
|
@@ -547,10 +509,8 @@ class BaseReporter {
|
|
|
547
509
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
548
510
|
}
|
|
549
511
|
onWatcherRerun(files, trigger) {
|
|
550
|
-
this.watchFilters = files;
|
|
551
|
-
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
552
512
|
// Update re-run count for each file
|
|
553
|
-
files.forEach((filepath) => {
|
|
513
|
+
this.watchFilters = files, this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed"), files.forEach((filepath) => {
|
|
554
514
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
555
515
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
556
516
|
});
|
|
@@ -559,35 +519,26 @@ class BaseReporter {
|
|
|
559
519
|
const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
|
|
560
520
|
banner += c.blue(`x${rerun} `);
|
|
561
521
|
}
|
|
562
|
-
this.ctx.logger.clearFullScreen();
|
|
563
|
-
this.log(withLabel("blue", "RERUN", banner));
|
|
564
|
-
if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
522
|
+
if (this.ctx.logger.clearFullScreen(), this.log(withLabel("blue", "RERUN", banner)), this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
565
523
|
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
566
524
|
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
567
525
|
this.log("");
|
|
568
526
|
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
569
|
-
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
570
|
-
this.start = performance$1.now();
|
|
527
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
|
|
571
528
|
}
|
|
572
529
|
onUserConsoleLog(log, taskState) {
|
|
573
530
|
if (!this.shouldLog(log, taskState)) return;
|
|
574
|
-
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
|
|
575
|
-
const write = (msg) => output.write(msg);
|
|
531
|
+
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream, write = (msg) => output.write(msg);
|
|
576
532
|
let headerText = "unknown test";
|
|
577
533
|
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
578
534
|
if (task) headerText = this.getFullName(task, c.dim(" > "));
|
|
579
535
|
else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
|
|
580
|
-
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content)
|
|
581
|
-
if (log.origin) {
|
|
536
|
+
if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
|
|
582
537
|
// browser logs don't have an extra end of line at the end like Node.js does
|
|
583
538
|
if (log.browser) write("\n");
|
|
584
|
-
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
585
|
-
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
586
|
-
const highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
539
|
+
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject(), stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin), highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
587
540
|
for (const frame of stack) {
|
|
588
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
589
|
-
const path = relative(project.config.root, frame.file);
|
|
590
|
-
const positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
|
|
541
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file), positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
|
|
591
542
|
write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
|
|
592
543
|
}
|
|
593
544
|
}
|
|
@@ -597,12 +548,9 @@ class BaseReporter {
|
|
|
597
548
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
598
549
|
}
|
|
599
550
|
shouldLog(log, taskState) {
|
|
600
|
-
if (this.ctx.config.silent === true) return false;
|
|
601
|
-
if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
551
|
+
if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
602
552
|
if (this.ctx.config.onConsoleLog) {
|
|
603
|
-
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
604
|
-
const entity = task && this.ctx.state.getReportedEntity(task);
|
|
605
|
-
const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
|
|
553
|
+
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0, entity = task && this.ctx.state.getReportedEntity(task), shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
|
|
606
554
|
if (shouldLog === false) return shouldLog;
|
|
607
555
|
}
|
|
608
556
|
return true;
|
|
@@ -611,41 +559,27 @@ class BaseReporter {
|
|
|
611
559
|
this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
|
|
612
560
|
}
|
|
613
561
|
reportSummary(files, errors) {
|
|
614
|
-
this.printErrorsSummary(files, errors);
|
|
615
|
-
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
562
|
+
if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
616
563
|
else this.reportTestSummary(files, errors);
|
|
617
564
|
}
|
|
618
565
|
reportTestSummary(files, errors) {
|
|
619
566
|
this.log();
|
|
620
|
-
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
|
|
621
|
-
const tests = getTests(affectedFiles);
|
|
622
|
-
const snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
|
|
567
|
+
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files], tests = getTests(affectedFiles), snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
|
|
623
568
|
for (const [index, snapshot] of snapshotOutput.entries()) {
|
|
624
569
|
const title = index === 0 ? "Snapshots" : "";
|
|
625
570
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
626
571
|
}
|
|
627
572
|
if (snapshotOutput.length > 1) this.log();
|
|
628
|
-
this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles))
|
|
629
|
-
this.log(padSummaryTitle("Tests"), getStateString$1(tests));
|
|
630
|
-
if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
|
|
573
|
+
if (this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles)), this.log(padSummaryTitle("Tests"), getStateString$1(tests)), this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
|
|
631
574
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
632
575
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
633
576
|
}
|
|
634
577
|
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
635
578
|
this.log(padSummaryTitle("Start at"), this._timeStart);
|
|
636
|
-
const collectTime = sum(files, (file) => file.collectDuration);
|
|
637
|
-
const testsTime = sum(files, (file) => file.result?.duration);
|
|
638
|
-
const setupTime = sum(files, (file) => file.setupDuration);
|
|
579
|
+
const collectTime = sum(files, (file) => file.collectDuration), testsTime = sum(files, (file) => file.result?.duration), setupTime = sum(files, (file) => file.setupDuration);
|
|
639
580
|
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
640
581
|
else {
|
|
641
|
-
const blobs = this.ctx.state.blobs
|
|
642
|
-
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
643
|
-
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
644
|
-
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
645
|
-
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
646
|
-
const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
|
|
647
|
-
const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
|
|
648
|
-
const timers = [
|
|
582
|
+
const blobs = this.ctx.state.blobs, executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start, environmentTime = sum(files, (file) => file.environmentLoad), prepareTime = sum(files, (file) => file.prepareDuration), transformTime = this.ctx.state.transformTime, typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time), timers = [
|
|
649
583
|
`transform ${formatTime(transformTime)}`,
|
|
650
584
|
`setup ${formatTime(setupTime)}`,
|
|
651
585
|
`collect ${formatTime(collectTime)}`,
|
|
@@ -654,41 +588,25 @@ class BaseReporter {
|
|
|
654
588
|
`prepare ${formatTime(prepareTime)}`,
|
|
655
589
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
656
590
|
].filter(Boolean).join(", ");
|
|
657
|
-
this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
|
|
658
|
-
if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
591
|
+
if (this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`)), blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
|
|
659
592
|
}
|
|
660
593
|
this.log();
|
|
661
594
|
}
|
|
662
595
|
printErrorsSummary(files, errors) {
|
|
663
|
-
const suites = getSuites(files);
|
|
664
|
-
const tests = getTests(files);
|
|
665
|
-
const failedSuites = suites.filter((i) => i.result?.errors);
|
|
666
|
-
const failedTests = tests.filter((i) => i.result?.state === "fail");
|
|
667
|
-
const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
|
|
596
|
+
const suites = getSuites(files), tests = getTests(files), failedSuites = suites.filter((i) => i.result?.errors), failedTests = tests.filter((i) => i.result?.state === "fail"), failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
|
|
668
597
|
let current = 1;
|
|
669
598
|
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
|
|
670
|
-
if (failedSuites.length) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
if (failedTests.length) {
|
|
675
|
-
this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
|
|
676
|
-
this.printTaskErrors(failedTests, errorDivider);
|
|
677
|
-
}
|
|
678
|
-
if (errors.length) {
|
|
679
|
-
this.ctx.logger.printUnhandledErrors(errors);
|
|
680
|
-
this.error();
|
|
681
|
-
}
|
|
599
|
+
if (failedSuites.length) this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`), this.printTaskErrors(failedSuites, errorDivider);
|
|
600
|
+
if (failedTests.length) this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`), this.printTaskErrors(failedTests, errorDivider);
|
|
601
|
+
if (errors.length) this.ctx.logger.printUnhandledErrors(errors), this.error();
|
|
682
602
|
}
|
|
683
603
|
reportBenchmarkSummary(files) {
|
|
684
|
-
const benches = getTests(files);
|
|
685
|
-
const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
604
|
+
const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
686
605
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
687
606
|
for (const bench of topBenches) {
|
|
688
607
|
const group = bench.suite || bench.file;
|
|
689
608
|
if (!group) continue;
|
|
690
|
-
const groupName = this.getFullName(group, c.dim(" > "));
|
|
691
|
-
const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
609
|
+
const groupName = this.getFullName(group, c.dim(" > ")), project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
692
610
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
693
611
|
const siblings = group.tasks.filter((i) => i.meta.benchmark && i.result?.benchmark && i !== bench).sort((a, b) => a.result.benchmark.rank - b.result.benchmark.rank);
|
|
694
612
|
for (const sibling of siblings) {
|
|
@@ -706,10 +624,7 @@ class BaseReporter {
|
|
|
706
624
|
let previous;
|
|
707
625
|
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
708
626
|
if (i[0]?.stack !== error.stack) return false;
|
|
709
|
-
const currentProjectName = task?.projectName || task.file?.projectName || "";
|
|
710
|
-
const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
|
|
711
|
-
const currentAnnotations = task.type === "test" && task.annotations;
|
|
712
|
-
const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
|
|
627
|
+
const currentProjectName = task?.projectName || task.file?.projectName || "", projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "", currentAnnotations = task.type === "test" && task.annotations, itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
|
|
713
628
|
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
714
629
|
});
|
|
715
630
|
if (previous) previous[1].push(task);
|
|
@@ -717,24 +632,20 @@ class BaseReporter {
|
|
|
717
632
|
});
|
|
718
633
|
for (const [error, tasks] of errorsQueue) {
|
|
719
634
|
for (const task of tasks) {
|
|
720
|
-
const filepath = task?.filepath || "";
|
|
721
|
-
const projectName = task?.projectName || task.file?.projectName || "";
|
|
722
|
-
const project = this.ctx.projects.find((p) => p.name === projectName);
|
|
635
|
+
const filepath = task?.filepath || "", projectName = task?.projectName || task.file?.projectName || "", project = this.ctx.projects.find((p) => p.name === projectName);
|
|
723
636
|
let name = this.getFullName(task, c.dim(" > "));
|
|
724
637
|
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
725
638
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
726
639
|
}
|
|
727
640
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
728
|
-
this.ctx.logger.printError(error, {
|
|
641
|
+
if (this.ctx.logger.printError(error, {
|
|
729
642
|
project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
|
|
730
643
|
verbose: this.verbose,
|
|
731
644
|
screenshotPaths,
|
|
732
645
|
task: tasks[0]
|
|
733
|
-
})
|
|
734
|
-
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
646
|
+
}), tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
735
647
|
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
736
|
-
this.printAnnotations(test, "error", 1);
|
|
737
|
-
this.error();
|
|
648
|
+
this.printAnnotations(test, "error", 1), this.error();
|
|
738
649
|
}
|
|
739
650
|
errorDivider();
|
|
740
651
|
}
|
|
@@ -743,8 +654,7 @@ class BaseReporter {
|
|
|
743
654
|
function deepEqual(a, b) {
|
|
744
655
|
if (a === b) return true;
|
|
745
656
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
746
|
-
const keysA = Object.keys(a);
|
|
747
|
-
const keysB = Object.keys(b);
|
|
657
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
748
658
|
if (keysA.length !== keysB.length) return false;
|
|
749
659
|
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
750
660
|
return true;
|
|
@@ -755,12 +665,7 @@ function sum(items, cb) {
|
|
|
755
665
|
}, 0);
|
|
756
666
|
}
|
|
757
667
|
|
|
758
|
-
const DEFAULT_RENDER_INTERVAL_MS = 1e3
|
|
759
|
-
const ESC = "\x1B[";
|
|
760
|
-
const CLEAR_LINE = `${ESC}K`;
|
|
761
|
-
const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
|
|
762
|
-
const SYNC_START = `${ESC}?2026h`;
|
|
763
|
-
const SYNC_END = `${ESC}?2026l`;
|
|
668
|
+
const DEFAULT_RENDER_INTERVAL_MS = 1e3, ESC = "\x1B[", CLEAR_LINE = `${ESC}K`, MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`, SYNC_START = `${ESC}?2026h`, SYNC_END = `${ESC}?2026l`;
|
|
764
669
|
/**
|
|
765
670
|
* Renders content of `getWindow` at the bottom of the terminal and
|
|
766
671
|
* forwards all other intercepted `stdout` and `stderr` logs above it.
|
|
@@ -772,53 +677,41 @@ class WindowRenderer {
|
|
|
772
677
|
renderInterval = void 0;
|
|
773
678
|
renderScheduled = false;
|
|
774
679
|
windowHeight = 0;
|
|
680
|
+
started = false;
|
|
775
681
|
finished = false;
|
|
776
682
|
cleanups = [];
|
|
777
683
|
constructor(options) {
|
|
684
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
778
685
|
this.options = {
|
|
779
686
|
interval: DEFAULT_RENDER_INTERVAL_MS,
|
|
780
687
|
...options
|
|
781
|
-
}
|
|
782
|
-
this.streams = {
|
|
688
|
+
}, this.streams = {
|
|
783
689
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
784
690
|
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();
|
|
691
|
+
}, this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error")), this.options.logger.onTerminalCleanup(() => {
|
|
692
|
+
this.flushBuffer(), this.stop();
|
|
791
693
|
});
|
|
792
|
-
this.start();
|
|
793
694
|
}
|
|
794
695
|
start() {
|
|
795
|
-
this.finished = false;
|
|
796
|
-
this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
696
|
+
this.started = true, this.finished = false, this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
|
|
797
697
|
}
|
|
798
698
|
stop() {
|
|
799
|
-
this.cleanups.splice(0).map((fn) => fn());
|
|
800
|
-
clearInterval(this.renderInterval);
|
|
699
|
+
this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
|
|
801
700
|
}
|
|
802
701
|
/**
|
|
803
702
|
* Write all buffered output and stop buffering.
|
|
804
703
|
* All intercepted writes are forwarded to actual write after this.
|
|
805
704
|
*/
|
|
806
705
|
finish() {
|
|
807
|
-
this.finished = true;
|
|
808
|
-
this.flushBuffer();
|
|
809
|
-
clearInterval(this.renderInterval);
|
|
706
|
+
this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
|
|
810
707
|
}
|
|
811
708
|
/**
|
|
812
709
|
* Queue new render update
|
|
813
710
|
*/
|
|
814
711
|
schedule() {
|
|
815
|
-
if (!this.renderScheduled) {
|
|
816
|
-
this.renderScheduled =
|
|
817
|
-
|
|
818
|
-
setTimeout(() => {
|
|
819
|
-
this.renderScheduled = false;
|
|
820
|
-
}, 100).unref();
|
|
821
|
-
}
|
|
712
|
+
if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
|
|
713
|
+
this.renderScheduled = false;
|
|
714
|
+
}, 100).unref();
|
|
822
715
|
}
|
|
823
716
|
flushBuffer() {
|
|
824
717
|
if (this.buffer.length === 0) return this.render();
|
|
@@ -830,8 +723,7 @@ class WindowRenderer {
|
|
|
830
723
|
continue;
|
|
831
724
|
}
|
|
832
725
|
if (current.type !== next.type) {
|
|
833
|
-
this.render(current.message, current.type);
|
|
834
|
-
current = next;
|
|
726
|
+
this.render(current.message, current.type), current = next;
|
|
835
727
|
continue;
|
|
836
728
|
}
|
|
837
729
|
current.message += next.message;
|
|
@@ -839,40 +731,31 @@ class WindowRenderer {
|
|
|
839
731
|
if (current) this.render(current?.message, current?.type);
|
|
840
732
|
}
|
|
841
733
|
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());
|
|
734
|
+
if (this.finished) return this.clearWindow(), this.write(message || "", type);
|
|
735
|
+
const windowContent = this.options.getWindow(), rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
848
736
|
let padding = this.windowHeight - rowCount;
|
|
849
737
|
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);
|
|
738
|
+
if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
|
|
853
739
|
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);
|
|
740
|
+
this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
|
|
857
741
|
}
|
|
858
742
|
clearWindow() {
|
|
859
|
-
if (this.windowHeight
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
743
|
+
if (this.windowHeight !== 0) {
|
|
744
|
+
this.write(CLEAR_LINE);
|
|
745
|
+
for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
|
|
746
|
+
this.windowHeight = 0;
|
|
747
|
+
}
|
|
863
748
|
}
|
|
864
749
|
interceptStream(stream, type) {
|
|
865
750
|
const original = stream.write;
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (chunk) if (this.finished) this.write(chunk.toString(), type);
|
|
751
|
+
return stream.write = (chunk, _, callback) => {
|
|
752
|
+
if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
|
|
869
753
|
else this.buffer.push({
|
|
870
754
|
type,
|
|
871
755
|
message: chunk.toString()
|
|
872
756
|
});
|
|
873
757
|
callback?.();
|
|
874
|
-
}
|
|
875
|
-
return function restore() {
|
|
758
|
+
}, function restore() {
|
|
876
759
|
stream.write = original;
|
|
877
760
|
};
|
|
878
761
|
}
|
|
@@ -890,8 +773,7 @@ function getRenderedRowCount(rows, columns) {
|
|
|
890
773
|
return count;
|
|
891
774
|
}
|
|
892
775
|
|
|
893
|
-
const DURATION_UPDATE_INTERVAL_MS = 100;
|
|
894
|
-
const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
776
|
+
const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
895
777
|
/**
|
|
896
778
|
* Reporter extension that renders summary and forwards all other logs above itself.
|
|
897
779
|
* Intended to be used by other reporters, not as a standalone reporter.
|
|
@@ -912,34 +794,21 @@ class SummaryReporter {
|
|
|
912
794
|
duration = 0;
|
|
913
795
|
durationInterval = void 0;
|
|
914
796
|
onInit(ctx, options = {}) {
|
|
915
|
-
this.ctx = ctx
|
|
916
|
-
this.options = {
|
|
797
|
+
this.ctx = ctx, this.options = {
|
|
917
798
|
verbose: false,
|
|
918
799
|
...options
|
|
919
|
-
}
|
|
920
|
-
this.renderer = new WindowRenderer({
|
|
800
|
+
}, this.renderer = new WindowRenderer({
|
|
921
801
|
logger: ctx.logger,
|
|
922
802
|
getWindow: () => this.createSummary()
|
|
923
|
-
})
|
|
924
|
-
|
|
925
|
-
clearInterval(this.durationInterval);
|
|
926
|
-
this.renderer.stop();
|
|
803
|
+
}), this.ctx.onClose(() => {
|
|
804
|
+
clearInterval(this.durationInterval), this.renderer.stop();
|
|
927
805
|
});
|
|
928
806
|
}
|
|
929
807
|
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;
|
|
808
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.modules = emptyCounters(), this.tests = emptyCounters(), this.startTimers(), this.renderer.start(), this.modules.total = specifications.length;
|
|
937
809
|
}
|
|
938
810
|
onTestRunEnd() {
|
|
939
|
-
this.runningModules.clear();
|
|
940
|
-
this.finishedModules.clear();
|
|
941
|
-
this.renderer.finish();
|
|
942
|
-
clearInterval(this.durationInterval);
|
|
811
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
|
|
943
812
|
}
|
|
944
813
|
onTestModuleQueued(module) {
|
|
945
814
|
// When new test module starts, take the place of previously finished test module, if any
|
|
@@ -947,20 +816,13 @@ class SummaryReporter {
|
|
|
947
816
|
const finished = this.finishedModules.keys().next().value;
|
|
948
817
|
this.removeTestModule(finished);
|
|
949
818
|
}
|
|
950
|
-
this.runningModules.set(module.id, initializeStats(module));
|
|
951
|
-
this.renderer.schedule();
|
|
819
|
+
this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
|
|
952
820
|
}
|
|
953
821
|
onTestModuleCollected(module) {
|
|
954
822
|
let stats = this.runningModules.get(module.id);
|
|
955
|
-
if (!stats)
|
|
956
|
-
stats = initializeStats(module);
|
|
957
|
-
this.runningModules.set(module.id, stats);
|
|
958
|
-
}
|
|
823
|
+
if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
|
|
959
824
|
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();
|
|
825
|
+
this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
|
|
964
826
|
}
|
|
965
827
|
onHookStart(options) {
|
|
966
828
|
const stats = this.getHookStats(options);
|
|
@@ -971,8 +833,7 @@ class SummaryReporter {
|
|
|
971
833
|
startTime: performance.now(),
|
|
972
834
|
onFinish: () => {}
|
|
973
835
|
};
|
|
974
|
-
stats.hook?.onFinish?.();
|
|
975
|
-
stats.hook = hook;
|
|
836
|
+
stats.hook?.onFinish?.(), stats.hook = hook;
|
|
976
837
|
const timeout = setTimeout(() => {
|
|
977
838
|
hook.visible = true;
|
|
978
839
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
@@ -980,9 +841,7 @@ class SummaryReporter {
|
|
|
980
841
|
}
|
|
981
842
|
onHookEnd(options) {
|
|
982
843
|
const stats = this.getHookStats(options);
|
|
983
|
-
|
|
984
|
-
stats.hook.onFinish();
|
|
985
|
-
stats.hook.visible = false;
|
|
844
|
+
stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
|
|
986
845
|
}
|
|
987
846
|
onTestCaseReady(test) {
|
|
988
847
|
// Track slow running tests only on verbose mode
|
|
@@ -994,22 +853,17 @@ class SummaryReporter {
|
|
|
994
853
|
visible: false,
|
|
995
854
|
startTime: performance.now(),
|
|
996
855
|
onFinish: () => {}
|
|
997
|
-
}
|
|
998
|
-
const timeout = setTimeout(() => {
|
|
856
|
+
}, timeout = setTimeout(() => {
|
|
999
857
|
slowTest.visible = true;
|
|
1000
858
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
1001
859
|
slowTest.onFinish = () => {
|
|
1002
|
-
slowTest.hook?.onFinish();
|
|
1003
|
-
|
|
1004
|
-
};
|
|
1005
|
-
stats.tests.set(test.id, slowTest);
|
|
860
|
+
slowTest.hook?.onFinish(), clearTimeout(timeout);
|
|
861
|
+
}, stats.tests.set(test.id, slowTest);
|
|
1006
862
|
}
|
|
1007
863
|
onTestCaseResult(test) {
|
|
1008
864
|
const stats = this.runningModules.get(test.module.id);
|
|
1009
865
|
if (!stats) return;
|
|
1010
|
-
stats.tests.get(test.id)?.onFinish()
|
|
1011
|
-
stats.tests.delete(test.id);
|
|
1012
|
-
stats.completed++;
|
|
866
|
+
stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
|
|
1013
867
|
const result = test.result();
|
|
1014
868
|
if (result?.state === "passed") this.tests.passed++;
|
|
1015
869
|
else if (result?.state === "failed") this.tests.failed++;
|
|
@@ -1018,8 +872,7 @@ class SummaryReporter {
|
|
|
1018
872
|
}
|
|
1019
873
|
onTestModuleEnd(module) {
|
|
1020
874
|
const state = module.state();
|
|
1021
|
-
this.modules.completed++;
|
|
1022
|
-
if (state === "passed") this.modules.passed++;
|
|
875
|
+
if (this.modules.completed++, state === "passed") this.modules.passed++;
|
|
1023
876
|
else if (state === "failed") this.modules.failed++;
|
|
1024
877
|
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1025
878
|
else if (state === "skipped") this.modules.skipped++;
|
|
@@ -1039,10 +892,8 @@ class SummaryReporter {
|
|
|
1039
892
|
getHookStats({ entity }) {
|
|
1040
893
|
// Track slow running hooks only on verbose mode
|
|
1041
894
|
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;
|
|
895
|
+
const module = entity.type === "module" ? entity : entity.module, stats = this.runningModules.get(module.id);
|
|
896
|
+
if (stats) return entity.type === "test" ? stats.tests.get(entity.id) : stats;
|
|
1046
897
|
}
|
|
1047
898
|
createSummary() {
|
|
1048
899
|
const summary = [""];
|
|
@@ -1054,36 +905,23 @@ class SummaryReporter {
|
|
|
1054
905
|
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1055
906
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1056
907
|
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);
|
|
908
|
+
const elapsed = this.currentTime - task.startTime, icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
909
|
+
if (summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`))), task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
|
|
1061
910
|
}
|
|
1062
911
|
}
|
|
1063
912
|
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;
|
|
913
|
+
return summary.push(padSummaryTitle("Test Files") + getStateString(this.modules)), summary.push(padSummaryTitle("Tests") + getStateString(this.tests)), summary.push(padSummaryTitle("Start at") + this.startTime), summary.push(padSummaryTitle("Duration") + formatTime(this.duration)), summary.push(""), summary;
|
|
1070
914
|
}
|
|
1071
915
|
startTimers() {
|
|
1072
916
|
const start = performance.now();
|
|
1073
|
-
this.startTime = formatTimeString(/* @__PURE__ */ new Date())
|
|
1074
|
-
|
|
1075
|
-
this.currentTime = performance.now();
|
|
1076
|
-
this.duration = this.currentTime - start;
|
|
917
|
+
this.startTime = formatTimeString(/* @__PURE__ */ new Date()), this.durationInterval = setInterval(() => {
|
|
918
|
+
this.currentTime = performance.now(), this.duration = this.currentTime - start;
|
|
1077
919
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1078
920
|
}
|
|
1079
921
|
removeTestModule(id) {
|
|
1080
922
|
if (!id) return;
|
|
1081
923
|
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);
|
|
924
|
+
testFile?.hook?.onFinish(), testFile?.tests?.forEach((test) => test.onFinish()), this.runningModules.delete(id), clearTimeout(this.finishedModules.get(id)), this.finishedModules.delete(id);
|
|
1087
925
|
}
|
|
1088
926
|
}
|
|
1089
927
|
function emptyCounters() {
|
|
@@ -1105,9 +943,7 @@ function getStateString(entry) {
|
|
|
1105
943
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1106
944
|
}
|
|
1107
945
|
function sortRunningModules(a, b) {
|
|
1108
|
-
|
|
1109
|
-
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1110
|
-
return a.filename.localeCompare(b.filename);
|
|
946
|
+
return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
|
|
1111
947
|
}
|
|
1112
948
|
function initializeStats(module) {
|
|
1113
949
|
return {
|
|
@@ -1125,12 +961,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
1125
961
|
options;
|
|
1126
962
|
summary;
|
|
1127
963
|
constructor(options = {}) {
|
|
1128
|
-
super(options)
|
|
1129
|
-
this.options = {
|
|
964
|
+
if (super(options), this.options = {
|
|
1130
965
|
summary: true,
|
|
1131
966
|
...options
|
|
1132
|
-
};
|
|
1133
|
-
if (!this.isTTY) this.options.summary = false;
|
|
967
|
+
}, !this.isTTY) this.options.summary = false;
|
|
1134
968
|
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1135
969
|
}
|
|
1136
970
|
onTestRunStart(specifications) {
|
|
@@ -1140,6 +974,9 @@ class DefaultReporter extends BaseReporter {
|
|
|
1140
974
|
}
|
|
1141
975
|
this.summary?.onTestRunStart(specifications);
|
|
1142
976
|
}
|
|
977
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
978
|
+
super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
|
|
979
|
+
}
|
|
1143
980
|
onTestModuleQueued(file) {
|
|
1144
981
|
this.summary?.onTestModuleQueued(file);
|
|
1145
982
|
}
|
|
@@ -1147,15 +984,13 @@ class DefaultReporter extends BaseReporter {
|
|
|
1147
984
|
this.summary?.onTestModuleCollected(module);
|
|
1148
985
|
}
|
|
1149
986
|
onTestModuleEnd(module) {
|
|
1150
|
-
super.onTestModuleEnd(module);
|
|
1151
|
-
this.summary?.onTestModuleEnd(module);
|
|
987
|
+
super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
|
|
1152
988
|
}
|
|
1153
989
|
onTestCaseReady(test) {
|
|
1154
990
|
this.summary?.onTestCaseReady(test);
|
|
1155
991
|
}
|
|
1156
992
|
onTestCaseResult(test) {
|
|
1157
|
-
super.onTestCaseResult(test);
|
|
1158
|
-
this.summary?.onTestCaseResult(test);
|
|
993
|
+
super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
|
|
1159
994
|
}
|
|
1160
995
|
onHookStart(hook) {
|
|
1161
996
|
this.summary?.onHookStart(hook);
|
|
@@ -1164,11 +999,7 @@ class DefaultReporter extends BaseReporter {
|
|
|
1164
999
|
this.summary?.onHookEnd(hook);
|
|
1165
1000
|
}
|
|
1166
1001
|
onInit(ctx) {
|
|
1167
|
-
super.onInit(ctx);
|
|
1168
|
-
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1169
|
-
}
|
|
1170
|
-
onTestRunEnd() {
|
|
1171
|
-
this.summary?.onTestRunEnd();
|
|
1002
|
+
super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1172
1003
|
}
|
|
1173
1004
|
}
|
|
1174
1005
|
|
|
@@ -1177,30 +1008,22 @@ class DotReporter extends BaseReporter {
|
|
|
1177
1008
|
tests = /* @__PURE__ */ new Map();
|
|
1178
1009
|
finishedTests = /* @__PURE__ */ new Set();
|
|
1179
1010
|
onInit(ctx) {
|
|
1180
|
-
super.onInit(ctx)
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
getWindow: () => this.createSummary()
|
|
1185
|
-
});
|
|
1186
|
-
this.ctx.onClose(() => this.renderer?.stop());
|
|
1187
|
-
}
|
|
1011
|
+
if (super.onInit(ctx), this.isTTY) this.renderer = new WindowRenderer({
|
|
1012
|
+
logger: ctx.logger,
|
|
1013
|
+
getWindow: () => this.createSummary()
|
|
1014
|
+
}), this.ctx.onClose(() => this.renderer?.stop());
|
|
1188
1015
|
}
|
|
1189
1016
|
// Ignore default logging of base reporter
|
|
1190
1017
|
printTestModule() {}
|
|
1191
1018
|
onWatcherRerun(files, trigger) {
|
|
1192
|
-
this.tests.clear();
|
|
1193
|
-
this.renderer?.start();
|
|
1194
|
-
super.onWatcherRerun(files, trigger);
|
|
1019
|
+
this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
|
|
1195
1020
|
}
|
|
1196
|
-
|
|
1021
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
1197
1022
|
if (this.isTTY) {
|
|
1198
1023
|
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1199
1024
|
this.ctx.logger.log(finalLog);
|
|
1200
1025
|
} else this.ctx.logger.log();
|
|
1201
|
-
this.tests.clear();
|
|
1202
|
-
this.renderer?.finish();
|
|
1203
|
-
super.onFinished(files, errors);
|
|
1026
|
+
this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
1204
1027
|
}
|
|
1205
1028
|
onTestModuleCollected(module) {
|
|
1206
1029
|
for (const test of module.children.allTests())
|
|
@@ -1208,22 +1031,16 @@ class DotReporter extends BaseReporter {
|
|
|
1208
1031
|
this.onTestCaseReady(test);
|
|
1209
1032
|
}
|
|
1210
1033
|
onTestCaseReady(test) {
|
|
1211
|
-
|
|
1212
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1213
|
-
this.renderer?.schedule();
|
|
1034
|
+
this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
|
|
1214
1035
|
}
|
|
1215
1036
|
onTestCaseResult(test) {
|
|
1216
1037
|
const result = test.result().state;
|
|
1217
1038
|
// On non-TTY the finished tests are printed immediately
|
|
1218
1039
|
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();
|
|
1040
|
+
super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
|
|
1223
1041
|
}
|
|
1224
1042
|
onTestModuleEnd(testModule) {
|
|
1225
|
-
super.onTestModuleEnd(testModule);
|
|
1226
|
-
if (!this.isTTY) return;
|
|
1043
|
+
if (super.onTestModuleEnd(testModule), !this.isTTY) return;
|
|
1227
1044
|
const columns = this.ctx.logger.getColumns();
|
|
1228
1045
|
if (this.tests.size < columns) return;
|
|
1229
1046
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
@@ -1233,11 +1050,9 @@ class DotReporter extends BaseReporter {
|
|
|
1233
1050
|
let count = 0;
|
|
1234
1051
|
for (const [id, state] of finishedTests) {
|
|
1235
1052
|
if (count++ >= columns) break;
|
|
1236
|
-
this.tests.delete(id);
|
|
1237
|
-
states.push(state);
|
|
1053
|
+
this.tests.delete(id), states.push(state);
|
|
1238
1054
|
}
|
|
1239
|
-
this.ctx.logger.log(formatTests(states));
|
|
1240
|
-
this.renderer?.schedule();
|
|
1055
|
+
this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
|
|
1241
1056
|
}
|
|
1242
1057
|
createSummary() {
|
|
1243
1058
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
@@ -1247,16 +1062,13 @@ class DotReporter extends BaseReporter {
|
|
|
1247
1062
|
const pass = {
|
|
1248
1063
|
char: "·",
|
|
1249
1064
|
color: c.green
|
|
1250
|
-
}
|
|
1251
|
-
const fail = {
|
|
1065
|
+
}, fail = {
|
|
1252
1066
|
char: "x",
|
|
1253
1067
|
color: c.red
|
|
1254
|
-
}
|
|
1255
|
-
const pending = {
|
|
1068
|
+
}, pending = {
|
|
1256
1069
|
char: "*",
|
|
1257
1070
|
color: c.yellow
|
|
1258
|
-
}
|
|
1259
|
-
const skip = {
|
|
1071
|
+
}, skip = {
|
|
1260
1072
|
char: "-",
|
|
1261
1073
|
color: (char) => c.dim(c.gray(char))
|
|
1262
1074
|
};
|
|
@@ -1273,37 +1085,27 @@ function getIcon(state) {
|
|
|
1273
1085
|
* Sibling icons with same color are merged into a single c.color() call.
|
|
1274
1086
|
*/
|
|
1275
1087
|
function formatTests(states) {
|
|
1276
|
-
let currentIcon = pending;
|
|
1277
|
-
let count = 0;
|
|
1278
|
-
let output = "";
|
|
1088
|
+
let currentIcon = pending, count = 0, output = "";
|
|
1279
1089
|
for (const state of states) {
|
|
1280
1090
|
const icon = getIcon(state);
|
|
1281
1091
|
if (currentIcon === icon) {
|
|
1282
1092
|
count++;
|
|
1283
1093
|
continue;
|
|
1284
1094
|
}
|
|
1285
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1286
|
-
// Start tracking new group
|
|
1287
|
-
count = 1;
|
|
1288
|
-
currentIcon = icon;
|
|
1095
|
+
output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
|
|
1289
1096
|
}
|
|
1290
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1291
|
-
return output;
|
|
1097
|
+
return output += currentIcon.color(currentIcon.char.repeat(count)), output;
|
|
1292
1098
|
}
|
|
1293
1099
|
|
|
1294
1100
|
// use Logger with custom Console to capture entire error printing
|
|
1295
1101
|
function capturePrintError(error, ctx, options) {
|
|
1296
1102
|
let output = "";
|
|
1297
1103
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1298
|
-
output += String(chunk);
|
|
1299
|
-
|
|
1300
|
-
} });
|
|
1301
|
-
const console = new Console(writable);
|
|
1302
|
-
const logger = {
|
|
1104
|
+
output += String(chunk), callback();
|
|
1105
|
+
} }), console = new Console(writable), logger = {
|
|
1303
1106
|
error: console.error.bind(console),
|
|
1304
1107
|
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1305
|
-
}
|
|
1306
|
-
const result = printError(error, ctx, logger, {
|
|
1108
|
+
}, result = printError(error, ctx, logger, {
|
|
1307
1109
|
showCodeFrame: false,
|
|
1308
1110
|
...options
|
|
1309
1111
|
});
|
|
@@ -1321,11 +1123,13 @@ function printError(error, ctx, logger, options) {
|
|
|
1321
1123
|
screenshotPaths: options.screenshotPaths,
|
|
1322
1124
|
printProperties: options.verbose,
|
|
1323
1125
|
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
1126
|
// node.js stack trace already has correct source map locations
|
|
1328
|
-
return
|
|
1127
|
+
return error.stacks ? options.fullStack ? error.stacks : error.stacks.filter((stack) => {
|
|
1128
|
+
return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
|
|
1129
|
+
}) : options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, {
|
|
1130
|
+
frameFilter: project.config.onStackTrace,
|
|
1131
|
+
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1132
|
+
}) : parseErrorStacktrace(error, {
|
|
1329
1133
|
frameFilter: project.config.onStackTrace,
|
|
1330
1134
|
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1331
1135
|
});
|
|
@@ -1333,15 +1137,14 @@ function printError(error, ctx, logger, options) {
|
|
|
1333
1137
|
});
|
|
1334
1138
|
}
|
|
1335
1139
|
function printErrorInner(error, project, options) {
|
|
1336
|
-
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1337
|
-
const logger = options.logger;
|
|
1140
|
+
const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
|
|
1338
1141
|
let e = error;
|
|
1339
1142
|
if (isPrimitive(e)) e = {
|
|
1340
1143
|
message: String(error).split(/\n/g)[0],
|
|
1341
1144
|
stack: String(error)
|
|
1342
1145
|
};
|
|
1343
1146
|
if (!e) {
|
|
1344
|
-
const error = new Error("unknown error");
|
|
1147
|
+
const error = /* @__PURE__ */ new Error("unknown error");
|
|
1345
1148
|
e = {
|
|
1346
1149
|
message: e ?? error.message,
|
|
1347
1150
|
stack: error.stack
|
|
@@ -1352,21 +1155,22 @@ function printErrorInner(error, project, options) {
|
|
|
1352
1155
|
printErrorMessage(e, logger);
|
|
1353
1156
|
return;
|
|
1354
1157
|
}
|
|
1355
|
-
const stacks = options.parseErrorStacktrace(e)
|
|
1356
|
-
|
|
1158
|
+
const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1159
|
+
// we are checking that this module was processed by us at one point
|
|
1357
1160
|
try {
|
|
1358
|
-
|
|
1161
|
+
const environments = [...Object.values(project._vite?.environments || {}), ...Object.values(project.browser?.vite.environments || {})], hasResult = environments.some((environment) => {
|
|
1162
|
+
const modules = environment.moduleGraph.getModulesByFile(stack.file);
|
|
1163
|
+
return [...modules?.values() || []].some((module) => !!module.transformResult);
|
|
1164
|
+
});
|
|
1165
|
+
return hasResult && existsSync(stack.file);
|
|
1359
1166
|
} catch {
|
|
1360
1167
|
return false;
|
|
1361
1168
|
}
|
|
1362
1169
|
});
|
|
1363
1170
|
if (type) printErrorType(type, project.vitest);
|
|
1364
|
-
printErrorMessage(e, logger)
|
|
1365
|
-
if (options.screenshotPaths?.length) {
|
|
1171
|
+
if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
|
|
1366
1172
|
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();
|
|
1173
|
+
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
1174
|
}
|
|
1371
1175
|
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1372
1176
|
if ("__vitest_rollup_error__" in e) {
|
|
@@ -1391,25 +1195,19 @@ function printErrorInner(error, project, options) {
|
|
|
1391
1195
|
}
|
|
1392
1196
|
});
|
|
1393
1197
|
}
|
|
1394
|
-
const testPath = e.VITEST_TEST_PATH;
|
|
1395
|
-
const testName = e.VITEST_TEST_NAME;
|
|
1396
|
-
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1198
|
+
const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1397
1199
|
// testName has testPath inside
|
|
1398
1200
|
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
1201
|
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
1202
|
- The error was thrown, while Vitest was running this test.
|
|
1401
1203
|
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1402
1204
|
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 };
|
|
1205
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
|
|
1206
|
+
showCodeFrame: false,
|
|
1207
|
+
logger: options.logger,
|
|
1208
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1209
|
+
});
|
|
1210
|
+
return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
|
|
1413
1211
|
}
|
|
1414
1212
|
function printErrorType(type, ctx) {
|
|
1415
1213
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
@@ -1426,6 +1224,7 @@ const skipErrorProperties = new Set([
|
|
|
1426
1224
|
"actual",
|
|
1427
1225
|
"expected",
|
|
1428
1226
|
"diffOptions",
|
|
1227
|
+
"runnerError",
|
|
1429
1228
|
"sourceURL",
|
|
1430
1229
|
"column",
|
|
1431
1230
|
"line",
|
|
@@ -1491,10 +1290,8 @@ function printErrorMessage(error, logger) {
|
|
|
1491
1290
|
}
|
|
1492
1291
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1493
1292
|
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);
|
|
1293
|
+
const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
|
|
1294
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
|
|
1498
1295
|
}
|
|
1499
1296
|
if (stack.length) logger.error();
|
|
1500
1297
|
if (hasProperties(errorProperties)) {
|
|
@@ -1509,26 +1306,19 @@ function hasProperties(obj) {
|
|
|
1509
1306
|
return false;
|
|
1510
1307
|
}
|
|
1511
1308
|
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 = [];
|
|
1309
|
+
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;
|
|
1310
|
+
let count = 0, res = [];
|
|
1518
1311
|
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;
|
|
1312
|
+
for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
|
|
1313
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1314
|
+
if (j < 0 || j >= lines.length) continue;
|
|
1315
|
+
const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
|
|
1316
|
+
if (!strippedContent.startsWith("//# sourceMappingURL")) {
|
|
1525
1317
|
// 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) {
|
|
1318
|
+
if (strippedContent.length > 200) return "";
|
|
1319
|
+
if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
|
|
1529
1320
|
// push underline
|
|
1530
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
1531
|
-
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1321
|
+
const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1532
1322
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1533
1323
|
} else if (j > i) {
|
|
1534
1324
|
if (end > count) {
|
|
@@ -1538,8 +1328,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1538
1328
|
count += lineLength + 1;
|
|
1539
1329
|
}
|
|
1540
1330
|
}
|
|
1541
|
-
break;
|
|
1542
1331
|
}
|
|
1332
|
+
break;
|
|
1543
1333
|
}
|
|
1544
1334
|
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1545
1335
|
return res.join("\n");
|
|
@@ -1559,8 +1349,7 @@ class GithubActionsReporter {
|
|
|
1559
1349
|
}
|
|
1560
1350
|
onTestCaseAnnotate(testCase, annotation) {
|
|
1561
1351
|
if (!annotation.location) return;
|
|
1562
|
-
const type = getTitle(annotation.type)
|
|
1563
|
-
const formatted = formatMessage({
|
|
1352
|
+
const type = getTitle(annotation.type), formatted = formatMessage({
|
|
1564
1353
|
command: getType(annotation.type),
|
|
1565
1354
|
properties: {
|
|
1566
1355
|
file: annotation.location.file,
|
|
@@ -1572,17 +1361,15 @@ class GithubActionsReporter {
|
|
|
1572
1361
|
});
|
|
1573
1362
|
this.ctx.logger.log(`\n${formatted}`);
|
|
1574
1363
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
const projectErrors = new Array();
|
|
1364
|
+
onTestRunEnd(testModules, unhandledErrors) {
|
|
1365
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
|
|
1578
1366
|
for (const error of errors) projectErrors.push({
|
|
1579
1367
|
project: this.ctx.getRootProject(),
|
|
1580
1368
|
title: "Unhandled error",
|
|
1581
1369
|
error
|
|
1582
1370
|
});
|
|
1583
1371
|
for (const file of files) {
|
|
1584
|
-
const tasks = getTasks(file);
|
|
1585
|
-
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1372
|
+
const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
|
|
1586
1373
|
for (const task of tasks) {
|
|
1587
1374
|
if (task.result?.state !== "fail") continue;
|
|
1588
1375
|
const title = getFullName(task, " > ");
|
|
@@ -1600,8 +1387,7 @@ class GithubActionsReporter {
|
|
|
1600
1387
|
const result = capturePrintError(error, this.ctx, {
|
|
1601
1388
|
project,
|
|
1602
1389
|
task: file
|
|
1603
|
-
});
|
|
1604
|
-
const stack = result?.nearest;
|
|
1390
|
+
}), stack = result?.nearest;
|
|
1605
1391
|
if (!stack) continue;
|
|
1606
1392
|
const formatted = formatMessage({
|
|
1607
1393
|
command: "error",
|
|
@@ -1623,12 +1409,10 @@ const BUILT_IN_TYPES = [
|
|
|
1623
1409
|
"warning"
|
|
1624
1410
|
];
|
|
1625
1411
|
function getTitle(type) {
|
|
1626
|
-
|
|
1627
|
-
return type;
|
|
1412
|
+
return BUILT_IN_TYPES.includes(type) ? void 0 : type;
|
|
1628
1413
|
}
|
|
1629
1414
|
function getType(type) {
|
|
1630
|
-
|
|
1631
|
-
return "notice";
|
|
1415
|
+
return BUILT_IN_TYPES.includes(type) ? type : "notice";
|
|
1632
1416
|
}
|
|
1633
1417
|
function defaultOnWritePath(path) {
|
|
1634
1418
|
return path;
|
|
@@ -1638,12 +1422,9 @@ function defaultOnWritePath(path) {
|
|
|
1638
1422
|
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1639
1423
|
function formatMessage({ command, properties, message }) {
|
|
1640
1424
|
let result = `::${command}`;
|
|
1641
|
-
Object.entries(properties).forEach(([k, v], i) => {
|
|
1642
|
-
result += i === 0 ? " " : ","
|
|
1643
|
-
|
|
1644
|
-
});
|
|
1645
|
-
result += `::${escapeData(message)}`;
|
|
1646
|
-
return result;
|
|
1425
|
+
return Object.entries(properties).forEach(([k, v], i) => {
|
|
1426
|
+
result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
|
|
1427
|
+
}), result += `::${escapeData(message)}`, result;
|
|
1647
1428
|
}
|
|
1648
1429
|
function escapeData(s) {
|
|
1649
1430
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -1676,41 +1457,27 @@ class JsonReporter {
|
|
|
1676
1457
|
start = 0;
|
|
1677
1458
|
ctx;
|
|
1678
1459
|
options;
|
|
1460
|
+
coverageMap;
|
|
1679
1461
|
constructor(options) {
|
|
1680
1462
|
this.options = options;
|
|
1681
1463
|
}
|
|
1682
1464
|
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;
|
|
1465
|
+
this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
|
|
1466
|
+
}
|
|
1467
|
+
onCoverage(coverageMap) {
|
|
1468
|
+
this.coverageMap = coverageMap;
|
|
1469
|
+
}
|
|
1470
|
+
async onTestRunEnd(testModules) {
|
|
1471
|
+
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
1472
|
for (const file of files) {
|
|
1701
1473
|
const tests = getTests([file]);
|
|
1702
1474
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1703
1475
|
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) => {
|
|
1476
|
+
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
|
|
1706
1477
|
const ancestorTitles = [];
|
|
1707
1478
|
let iter = t.suite;
|
|
1708
|
-
while (iter)
|
|
1709
|
-
|
|
1710
|
-
iter = iter.suite;
|
|
1711
|
-
}
|
|
1712
|
-
ancestorTitles.reverse();
|
|
1713
|
-
return {
|
|
1479
|
+
while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
|
|
1480
|
+
return ancestorTitles.reverse(), {
|
|
1714
1481
|
ancestorTitles,
|
|
1715
1482
|
fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
|
|
1716
1483
|
status: StatusMap[t.result?.state || t.mode] || "skipped",
|
|
@@ -1746,13 +1513,10 @@ class JsonReporter {
|
|
|
1746
1513
|
startTime: this.start,
|
|
1747
1514
|
success,
|
|
1748
1515
|
testResults,
|
|
1749
|
-
coverageMap
|
|
1516
|
+
coverageMap: this.coverageMap
|
|
1750
1517
|
};
|
|
1751
1518
|
await this.writeReport(JSON.stringify(result));
|
|
1752
1519
|
}
|
|
1753
|
-
async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
|
|
1754
|
-
await this.logTasks(files, coverageMap);
|
|
1755
|
-
}
|
|
1756
1520
|
/**
|
|
1757
1521
|
* Writes the report to an output file if specified in the config,
|
|
1758
1522
|
* or logs it to the console otherwise.
|
|
@@ -1761,11 +1525,9 @@ class JsonReporter {
|
|
|
1761
1525
|
async writeReport(report) {
|
|
1762
1526
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
1763
1527
|
if (outputFile) {
|
|
1764
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1765
|
-
const outputDirectory = dirname(reportFile);
|
|
1528
|
+
const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
|
|
1766
1529
|
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}`);
|
|
1530
|
+
await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
1769
1531
|
} else this.ctx.logger.log(report);
|
|
1770
1532
|
}
|
|
1771
1533
|
}
|
|
@@ -1788,8 +1550,7 @@ class IndentedLogger {
|
|
|
1788
1550
|
|
|
1789
1551
|
function flattenTasks$1(task, baseName = "") {
|
|
1790
1552
|
const base = baseName ? `${baseName} > ` : "";
|
|
1791
|
-
|
|
1792
|
-
else return [{
|
|
1553
|
+
return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
|
|
1793
1554
|
...task,
|
|
1794
1555
|
name: `${base}${task.name}`
|
|
1795
1556
|
}];
|
|
@@ -1797,21 +1558,16 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1797
1558
|
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1798
1559
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1799
1560
|
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
|
-
}
|
|
1561
|
+
if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
|
|
1562
|
+
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1563
|
+
"([\\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]))",
|
|
1564
|
+
"g"
|
|
1565
|
+
/* eslint-enable */
|
|
1566
|
+
), value = value.replace(regex, "");
|
|
1811
1567
|
return value;
|
|
1812
1568
|
}
|
|
1813
1569
|
function escapeXML(value) {
|
|
1814
|
-
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
1570
|
+
return removeInvalidXMLCharacters(String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"), true);
|
|
1815
1571
|
}
|
|
1816
1572
|
function executionTime(durationMS) {
|
|
1817
1573
|
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
@@ -1832,8 +1588,7 @@ class JUnitReporter {
|
|
|
1832
1588
|
fileFd;
|
|
1833
1589
|
options;
|
|
1834
1590
|
constructor(options) {
|
|
1835
|
-
this.options = { ...options };
|
|
1836
|
-
this.options.includeConsoleOutput ??= true;
|
|
1591
|
+
this.options = { ...options }, this.options.includeConsoleOutput ??= true;
|
|
1837
1592
|
}
|
|
1838
1593
|
async onInit(ctx) {
|
|
1839
1594
|
this.ctx = ctx;
|
|
@@ -1843,14 +1598,12 @@ class JUnitReporter {
|
|
|
1843
1598
|
const outputDirectory = dirname(this.reportFile);
|
|
1844
1599
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1845
1600
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
1846
|
-
this.fileFd = fileFd
|
|
1847
|
-
this.baseLog = async (text) => {
|
|
1601
|
+
this.fileFd = fileFd, this.baseLog = async (text) => {
|
|
1848
1602
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
1849
1603
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
1850
1604
|
};
|
|
1851
1605
|
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1852
|
-
this._timeStart = /* @__PURE__ */ new Date();
|
|
1853
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
1606
|
+
this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
|
|
1854
1607
|
}
|
|
1855
1608
|
async writeElement(name, attrs, children) {
|
|
1856
1609
|
const pairs = [];
|
|
@@ -1859,18 +1612,12 @@ class JUnitReporter {
|
|
|
1859
1612
|
if (attr === void 0) continue;
|
|
1860
1613
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
1861
1614
|
}
|
|
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}>`);
|
|
1615
|
+
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
1616
|
}
|
|
1868
1617
|
async writeLogs(task, type) {
|
|
1869
1618
|
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 () => {
|
|
1619
|
+
const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
|
|
1620
|
+
logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
|
|
1874
1621
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
1875
1622
|
});
|
|
1876
1623
|
}
|
|
@@ -1883,27 +1630,18 @@ class JUnitReporter {
|
|
|
1883
1630
|
};
|
|
1884
1631
|
if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
|
|
1885
1632
|
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
1633
|
await this.writeElement("testcase", {
|
|
1888
1634
|
classname,
|
|
1889
1635
|
file: this.options.addFileAttribute ? filename : void 0,
|
|
1890
1636
|
name: task.name,
|
|
1891
1637
|
time: getDuration(task)
|
|
1892
1638
|
}, async () => {
|
|
1893
|
-
if (this.options.includeConsoleOutput)
|
|
1894
|
-
await this.writeLogs(task, "out");
|
|
1895
|
-
await this.writeLogs(task, "err");
|
|
1896
|
-
}
|
|
1639
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
|
|
1897
1640
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1898
1641
|
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>");
|
|
1642
|
+
await this.logger.log("<properties>"), this.logger.indent();
|
|
1643
|
+
for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
|
|
1644
|
+
this.logger.unindent(), await this.logger.log("</properties>");
|
|
1907
1645
|
}
|
|
1908
1646
|
if (task.result?.state === "fail") {
|
|
1909
1647
|
const errors = task.result.errors || [];
|
|
@@ -1922,11 +1660,11 @@ class JUnitReporter {
|
|
|
1922
1660
|
});
|
|
1923
1661
|
}
|
|
1924
1662
|
}
|
|
1925
|
-
async
|
|
1663
|
+
async onTestRunEnd(testModules) {
|
|
1664
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1926
1665
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
1927
1666
|
const transformed = files.map((file) => {
|
|
1928
|
-
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task))
|
|
1929
|
-
const stats = tasks.reduce((stats, task) => {
|
|
1667
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
|
|
1930
1668
|
return {
|
|
1931
1669
|
passed: stats.passed + Number(task.result?.state === "pass"),
|
|
1932
1670
|
failures: stats.failures + Number(task.result?.state === "fail"),
|
|
@@ -1936,41 +1674,29 @@ class JUnitReporter {
|
|
|
1936
1674
|
passed: 0,
|
|
1937
1675
|
failures: 0,
|
|
1938
1676
|
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
|
-
}
|
|
1677
|
+
}), suites = getSuites(file);
|
|
1678
|
+
for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
|
|
1946
1679
|
// 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
|
-
}
|
|
1680
|
+
if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
|
|
1681
|
+
id: file.id,
|
|
1682
|
+
type: "test",
|
|
1683
|
+
name: file.name,
|
|
1684
|
+
mode: "run",
|
|
1685
|
+
result: file.result,
|
|
1686
|
+
meta: {},
|
|
1687
|
+
timeout: 0,
|
|
1688
|
+
context: null,
|
|
1689
|
+
suite: null,
|
|
1690
|
+
file: null,
|
|
1691
|
+
annotations: []
|
|
1692
|
+
});
|
|
1963
1693
|
return {
|
|
1964
1694
|
...file,
|
|
1965
1695
|
tasks,
|
|
1966
1696
|
stats
|
|
1967
1697
|
};
|
|
1968
|
-
})
|
|
1969
|
-
|
|
1970
|
-
stats.tests += file.tasks.length;
|
|
1971
|
-
stats.failures += file.stats.failures;
|
|
1972
|
-
stats.time += file.result?.duration || 0;
|
|
1973
|
-
return stats;
|
|
1698
|
+
}), stats = transformed.reduce((stats, file) => {
|
|
1699
|
+
return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
|
|
1974
1700
|
}, {
|
|
1975
1701
|
name: this.options.suiteName || "vitest tests",
|
|
1976
1702
|
tests: 0,
|
|
@@ -1978,7 +1704,7 @@ class JUnitReporter {
|
|
|
1978
1704
|
errors: 0,
|
|
1979
1705
|
time: 0
|
|
1980
1706
|
});
|
|
1981
|
-
await this.writeElement("testsuites", {
|
|
1707
|
+
if (await this.writeElement("testsuites", {
|
|
1982
1708
|
...stats,
|
|
1983
1709
|
time: executionTime(stats.time)
|
|
1984
1710
|
}, async () => {
|
|
@@ -1997,16 +1723,13 @@ class JUnitReporter {
|
|
|
1997
1723
|
await this.writeTasks(file.tasks, filename);
|
|
1998
1724
|
});
|
|
1999
1725
|
}
|
|
2000
|
-
});
|
|
2001
|
-
|
|
2002
|
-
await this.fileFd?.close();
|
|
2003
|
-
this.fileFd = void 0;
|
|
1726
|
+
}), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
1727
|
+
await this.fileFd?.close(), this.fileFd = void 0;
|
|
2004
1728
|
}
|
|
2005
1729
|
}
|
|
2006
1730
|
|
|
2007
1731
|
function yamlString(str) {
|
|
2008
|
-
|
|
2009
|
-
return `"${str.replace(/"/g, "\\\"")}"`;
|
|
1732
|
+
return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
|
|
2010
1733
|
}
|
|
2011
1734
|
function tapString(str) {
|
|
2012
1735
|
return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
|
|
@@ -2015,77 +1738,45 @@ class TapReporter {
|
|
|
2015
1738
|
ctx;
|
|
2016
1739
|
logger;
|
|
2017
1740
|
onInit(ctx) {
|
|
2018
|
-
this.ctx = ctx;
|
|
2019
|
-
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
1741
|
+
this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2020
1742
|
}
|
|
2021
1743
|
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 "";
|
|
1744
|
+
return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2026
1745
|
}
|
|
2027
1746
|
logErrorDetails(error, stack) {
|
|
2028
1747
|
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)
|
|
1748
|
+
if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
|
|
2032
1749
|
// For compatibility with tap-mocha-reporter
|
|
2033
1750
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2034
1751
|
}
|
|
2035
1752
|
logTasks(tasks) {
|
|
2036
1753
|
this.logger.log(`1..${tasks.length}`);
|
|
2037
1754
|
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 {
|
|
1755
|
+
const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
|
|
1756
|
+
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("}");
|
|
1757
|
+
else {
|
|
2048
1758
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2049
1759
|
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
|
-
}
|
|
1760
|
+
if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
|
|
1761
|
+
this.logger.log(`# ${type}: ${message}`);
|
|
1762
|
+
}), this.logger.unindent();
|
|
1763
|
+
if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
|
|
1764
|
+
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
|
|
1765
|
+
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}`)}`);
|
|
1766
|
+
if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
1767
|
+
}), this.logger.log("..."), this.logger.unindent();
|
|
2076
1768
|
}
|
|
2077
1769
|
}
|
|
2078
1770
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
this.logTasks(files);
|
|
1771
|
+
onTestRunEnd(testModules) {
|
|
1772
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1773
|
+
this.logger.log("TAP version 13"), this.logTasks(files);
|
|
2082
1774
|
}
|
|
2083
1775
|
}
|
|
2084
1776
|
|
|
2085
1777
|
function flattenTasks(task, baseName = "") {
|
|
2086
1778
|
const base = baseName ? `${baseName} > ` : "";
|
|
2087
|
-
|
|
2088
|
-
else return [{
|
|
1779
|
+
return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
|
|
2089
1780
|
...task,
|
|
2090
1781
|
name: `${base}${task.name}`
|
|
2091
1782
|
}];
|
|
@@ -2094,9 +1785,9 @@ class TapFlatReporter extends TapReporter {
|
|
|
2094
1785
|
onInit(ctx) {
|
|
2095
1786
|
super.onInit(ctx);
|
|
2096
1787
|
}
|
|
2097
|
-
|
|
1788
|
+
onTestRunEnd(testModules) {
|
|
2098
1789
|
this.ctx.logger.log("TAP version 13");
|
|
2099
|
-
const flatTasks =
|
|
1790
|
+
const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
|
|
2100
1791
|
this.logTasks(flatTasks);
|
|
2101
1792
|
}
|
|
2102
1793
|
}
|
|
@@ -2112,31 +1803,22 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2112
1803
|
if (this.isTTY) return super.printTestModule(module);
|
|
2113
1804
|
}
|
|
2114
1805
|
onTestCaseResult(test) {
|
|
2115
|
-
super.onTestCaseResult(test);
|
|
2116
1806
|
// don't print tests in TTY as they go, only print them
|
|
2117
1807
|
// in the CLI when they finish
|
|
2118
|
-
if (this.isTTY) return;
|
|
1808
|
+
if (super.onTestCaseResult(test), this.isTTY) return;
|
|
2119
1809
|
const testResult = test.result();
|
|
2120
1810
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2121
1811
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2122
1812
|
if (test.project.name) title += formatProjectName(test.project);
|
|
2123
|
-
title += getFullName(test.task, c.dim(" > "));
|
|
2124
|
-
title += this.getDurationPrefix(test.task);
|
|
1813
|
+
title += getFullName(test.task, c.dim(" > ")), title += this.getDurationPrefix(test.task);
|
|
2125
1814
|
const diagnostic = test.diagnostic();
|
|
2126
1815
|
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2127
1816
|
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2128
|
-
this.log(title);
|
|
2129
|
-
if (
|
|
2130
|
-
if (test.annotations().length) {
|
|
2131
|
-
this.log();
|
|
2132
|
-
this.printAnnotations(test, "log", 3);
|
|
2133
|
-
this.log();
|
|
2134
|
-
}
|
|
1817
|
+
if (this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
|
|
1818
|
+
if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
|
|
2135
1819
|
}
|
|
2136
1820
|
printTestSuite(testSuite) {
|
|
2137
|
-
const indentation = " ".repeat(getIndentation(testSuite.task));
|
|
2138
|
-
const tests = Array.from(testSuite.children.allTests());
|
|
2139
|
-
const state = getStateSymbol(testSuite.task);
|
|
1821
|
+
const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = getStateSymbol(testSuite.task);
|
|
2140
1822
|
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
2141
1823
|
}
|
|
2142
1824
|
getTestName(test) {
|
|
@@ -2151,8 +1833,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2151
1833
|
}
|
|
2152
1834
|
}
|
|
2153
1835
|
function getIndentation(suite, level = 1) {
|
|
2154
|
-
|
|
2155
|
-
return level;
|
|
1836
|
+
return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
|
|
2156
1837
|
}
|
|
2157
1838
|
|
|
2158
1839
|
const ReportersMap = {
|