vitest 3.2.4 → 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 -16
- 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.yFAklsD1.d.ts +18 -0
- package/dist/chunks/{cac.Cb-PYCCB.js → cac.DCxo_nSu.js} +72 -163
- package/dist/chunks/{cli-api.BkDphVBG.js → cli-api.BJJXh9BV.js} +1331 -1678
- package/dist/chunks/{config.d.D2ROskhv.d.ts → config.d.B_LthbQq.d.ts} +59 -65
- 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.DL5VHqXY.js → coverage.BCU-r2QL.js} +538 -765
- 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/{defaults.B7q_naMc.js → defaults.CXFFjsi8.js} +2 -42
- package/dist/chunks/environment.d.BsToaxti.d.ts +65 -0
- package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
- package/dist/chunks/{global.d.MAmajcmJ.d.ts → global.d.BK3X7FW1.d.ts} +7 -32
- package/dist/chunks/{globals.DEHgCU4V.js → globals.DG-S3xFe.js} +8 -8
- package/dist/chunks/{index.VByaPkjc.js → index.BIP7prJq.js} +472 -803
- package/dist/chunks/{index.B521nVV-.js → index.Bgo3tNWt.js} +23 -4
- package/dist/chunks/{index.BCWujgDG.js → index.BjKEiSn0.js} +14 -24
- package/dist/chunks/{index.CdQS2e2Q.js → index.CMfqw92x.js} +7 -8
- 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.BMVSnsGV.d.ts +9 -0
- package/dist/chunks/{reporters.d.BFLkQcL6.d.ts → reporters.d.BUWjmRYq.d.ts} +2086 -2146
- 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.Dd054P77.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.DRKU1-1g.js → typechecker.DB-fIMaH.js} +165 -234
- 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.1GmBbd7G.d.ts → worker.d.BDsXGkwh.d.ts} +31 -32
- package/dist/chunks/{worker.d.CKwWzBSj.d.ts → worker.d.BNcX_2mH.d.ts} +1 -1
- package/dist/cli.js +10 -10
- package/dist/config.cjs +5 -58
- package/dist/config.d.ts +72 -71
- package/dist/config.js +3 -9
- package/dist/coverage.d.ts +31 -24
- package/dist/coverage.js +9 -9
- package/dist/environments.d.ts +9 -14
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +52 -213
- 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 +62 -51
- package/dist/node.js +26 -42
- package/dist/reporters.d.ts +11 -12
- package/dist/reporters.js +12 -12
- package/dist/runners.d.ts +3 -4
- package/dist/runners.js +13 -231
- 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 -7
- package/dist/workers.js +35 -17
- package/globals.d.ts +17 -17
- package/package.json +32 -31
- package/dist/chunks/base.DfmxU-tU.js +0 -38
- package/dist/chunks/environment.d.cL3nLXbE.d.ts +0 -119
- package/dist/chunks/execute.B7h3T_Hc.js +0 -708
- package/dist/chunks/index.CwejwG0H.js +0 -105
- package/dist/chunks/rpc.-pEldfrD.js +0 -83
- package/dist/chunks/runBaseTests.9Ij9_de-.js +0 -129
- package/dist/chunks/vite.d.CMLlLIFP.d.ts +0 -25
- package/dist/execute.d.ts +0 -150
- package/dist/execute.js +0 -13
|
@@ -1,19 +1,219 @@
|
|
|
1
|
+
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
2
|
+
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve, dirname, isAbsolute, relative, basename, normalize } from 'pathe';
|
|
4
|
+
import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.DB-fIMaH.js';
|
|
1
5
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
2
6
|
import { getTestName, getFullName, hasFailed, getTests, getSuites, getTasks } from '@vitest/runner/utils';
|
|
3
7
|
import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
|
|
4
|
-
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
5
|
-
import { isAbsolute, relative, dirname, basename, resolve, normalize } from 'pathe';
|
|
8
|
+
import { parseStacktrace, parseErrorStacktrace, defaultStackIgnorePatterns } from '@vitest/utils/source-map';
|
|
6
9
|
import c from 'tinyrainbow';
|
|
7
10
|
import { i as isTTY } from './env.D4Lgay0q.js';
|
|
8
|
-
import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.DRKU1-1g.js';
|
|
9
11
|
import { stripVTControlCharacters } from 'node:util';
|
|
10
|
-
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
11
|
-
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
12
12
|
import { Console } from 'node:console';
|
|
13
13
|
import { Writable } from 'node:stream';
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { hostname } from 'node:os';
|
|
16
16
|
|
|
17
|
+
/// <reference types="../types/index.d.ts" />
|
|
18
|
+
|
|
19
|
+
// (c) 2020-present Andrea Giammarchi
|
|
20
|
+
|
|
21
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
22
|
+
const {keys} = Object;
|
|
23
|
+
|
|
24
|
+
const Primitive = String; // it could be Number
|
|
25
|
+
const primitive = 'string'; // it could be 'number'
|
|
26
|
+
|
|
27
|
+
const ignore = {};
|
|
28
|
+
const object = 'object';
|
|
29
|
+
|
|
30
|
+
const noop = (_, value) => value;
|
|
31
|
+
|
|
32
|
+
const primitives = value => (
|
|
33
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const Primitives = (_, value) => (
|
|
37
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const revive = (input, parsed, output, $) => {
|
|
41
|
+
const lazy = [];
|
|
42
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
43
|
+
const k = ke[y];
|
|
44
|
+
const value = output[k];
|
|
45
|
+
if (value instanceof Primitive) {
|
|
46
|
+
const tmp = input[value];
|
|
47
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
48
|
+
parsed.add(tmp);
|
|
49
|
+
output[k] = ignore;
|
|
50
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
output[k] = $.call(output, k, tmp);
|
|
54
|
+
}
|
|
55
|
+
else if (output[k] !== ignore)
|
|
56
|
+
output[k] = $.call(output, k, value);
|
|
57
|
+
}
|
|
58
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
59
|
+
const {k, a} = lazy[i];
|
|
60
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
61
|
+
}
|
|
62
|
+
return output;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const set = (known, input, value) => {
|
|
66
|
+
const index = Primitive(input.push(value) - 1);
|
|
67
|
+
known.set(value, index);
|
|
68
|
+
return index;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Converts a specialized flatted string into a JS value.
|
|
73
|
+
* @param {string} text
|
|
74
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
75
|
+
* @returns {any}
|
|
76
|
+
*/
|
|
77
|
+
const parse = (text, reviver) => {
|
|
78
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
79
|
+
const value = input[0];
|
|
80
|
+
const $ = reviver || noop;
|
|
81
|
+
const tmp = typeof value === object && value ?
|
|
82
|
+
revive(input, new Set, value, $) :
|
|
83
|
+
value;
|
|
84
|
+
return $.call({'': tmp}, '', tmp);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Converts a JS value into a specialized flatted string.
|
|
89
|
+
* @param {any} value
|
|
90
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
91
|
+
* @param {string | number | undefined} [space]
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
const stringify = (value, replacer, space) => {
|
|
95
|
+
const $ = replacer && typeof replacer === object ?
|
|
96
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
97
|
+
(replacer || noop);
|
|
98
|
+
const known = new Map;
|
|
99
|
+
const input = [];
|
|
100
|
+
const output = [];
|
|
101
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
102
|
+
let firstRun = !i;
|
|
103
|
+
while (i < input.length) {
|
|
104
|
+
firstRun = true;
|
|
105
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
106
|
+
}
|
|
107
|
+
return '[' + output.join(',') + ']';
|
|
108
|
+
function replace(key, value) {
|
|
109
|
+
if (firstRun) {
|
|
110
|
+
firstRun = !firstRun;
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
const after = $.call(this, key, value);
|
|
114
|
+
switch (typeof after) {
|
|
115
|
+
case object:
|
|
116
|
+
if (after === null) return after;
|
|
117
|
+
case primitive:
|
|
118
|
+
return known.get(after) || set(known, input, after);
|
|
119
|
+
}
|
|
120
|
+
return after;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
class BlobReporter {
|
|
125
|
+
start = 0;
|
|
126
|
+
ctx;
|
|
127
|
+
options;
|
|
128
|
+
coverage;
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.options = options;
|
|
131
|
+
}
|
|
132
|
+
onInit(ctx) {
|
|
133
|
+
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
134
|
+
this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
|
|
135
|
+
}
|
|
136
|
+
onCoverage(coverage) {
|
|
137
|
+
this.coverage = coverage;
|
|
138
|
+
}
|
|
139
|
+
async onTestRunEnd(testModules, unhandledErrors) {
|
|
140
|
+
const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
|
|
141
|
+
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
142
|
+
if (!outputFile) {
|
|
143
|
+
const shard = this.ctx.config.shard;
|
|
144
|
+
outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
|
|
145
|
+
}
|
|
146
|
+
const modules = this.ctx.projects.map((project) => {
|
|
147
|
+
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
148
|
+
return mod[1].file ? [
|
|
149
|
+
mod[0],
|
|
150
|
+
mod[1].file,
|
|
151
|
+
mod[1].url
|
|
152
|
+
] : null;
|
|
153
|
+
}).filter((x) => x != null)];
|
|
154
|
+
}), report = [
|
|
155
|
+
this.ctx.version,
|
|
156
|
+
files,
|
|
157
|
+
errors,
|
|
158
|
+
modules,
|
|
159
|
+
coverage,
|
|
160
|
+
executionTime
|
|
161
|
+
], reportFile = resolve(this.ctx.config.root, outputFile);
|
|
162
|
+
await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function writeBlob(content, filename) {
|
|
166
|
+
const report = stringify(content), dir = dirname(filename);
|
|
167
|
+
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
168
|
+
await writeFile(filename, report, "utf-8");
|
|
169
|
+
}
|
|
170
|
+
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
171
|
+
// using process.cwd() because --merge-reports can only be used in CLI
|
|
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);
|
|
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`);
|
|
175
|
+
const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
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`);
|
|
177
|
+
return {
|
|
178
|
+
version,
|
|
179
|
+
files,
|
|
180
|
+
errors,
|
|
181
|
+
moduleKeys,
|
|
182
|
+
coverage,
|
|
183
|
+
file: filename,
|
|
184
|
+
executionTime
|
|
185
|
+
};
|
|
186
|
+
}), blobs = await Promise.all(promises);
|
|
187
|
+
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
188
|
+
const versions = new Set(blobs.map((blob) => blob.version));
|
|
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")}`);
|
|
190
|
+
if (!versions.has(currentVersion)) throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
191
|
+
// fake module graph - it is used to check if module is imported, but we don't use values inside
|
|
192
|
+
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
193
|
+
blobs.forEach((blob) => {
|
|
194
|
+
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
195
|
+
const project = projects[projectName];
|
|
196
|
+
project && moduleIds.forEach(([moduleId, file, url]) => {
|
|
197
|
+
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
198
|
+
moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
|
|
199
|
+
code: " ",
|
|
200
|
+
map: null
|
|
201
|
+
}, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
206
|
+
const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
|
|
207
|
+
return time1 - time2;
|
|
208
|
+
}), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
|
|
209
|
+
return {
|
|
210
|
+
files,
|
|
211
|
+
errors,
|
|
212
|
+
coverages,
|
|
213
|
+
executionTimes
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
17
217
|
const F_RIGHT = "→";
|
|
18
218
|
const F_DOWN = "↓";
|
|
19
219
|
const F_DOWN_RIGHT = "↳";
|
|
@@ -47,26 +247,18 @@ function errorBanner(message) {
|
|
|
47
247
|
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
48
248
|
}
|
|
49
249
|
function divider(text, left, right, color) {
|
|
50
|
-
const cols = getCols();
|
|
51
|
-
const c = color || ((text) => text);
|
|
250
|
+
const cols = getCols(), c = color || ((text) => text);
|
|
52
251
|
if (text) {
|
|
53
252
|
const textLength = stripVTControlCharacters(text).length;
|
|
54
253
|
if (left == null && right != null) left = cols - textLength - right;
|
|
55
|
-
else
|
|
56
|
-
|
|
57
|
-
right = cols - textLength - left;
|
|
58
|
-
}
|
|
59
|
-
left = Math.max(0, left);
|
|
60
|
-
right = Math.max(0, right);
|
|
61
|
-
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))}`;
|
|
62
256
|
}
|
|
63
257
|
return F_LONG_DASH.repeat(cols);
|
|
64
258
|
}
|
|
65
259
|
function formatTestPath(root, path) {
|
|
66
260
|
if (isAbsolute(path)) path = relative(root, path);
|
|
67
|
-
const dir = dirname(path);
|
|
68
|
-
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
69
|
-
const base = basename(path, ext);
|
|
261
|
+
const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
|
|
70
262
|
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
71
263
|
}
|
|
72
264
|
function renderSnapshotSummary(rootDir, snapshots) {
|
|
@@ -78,8 +270,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
78
270
|
else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
79
271
|
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
80
272
|
const [head, ...tail] = snapshots.filesRemovedList;
|
|
81
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`)
|
|
82
|
-
tail.forEach((key) => {
|
|
273
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
|
|
83
274
|
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
84
275
|
});
|
|
85
276
|
}
|
|
@@ -87,8 +278,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
|
|
|
87
278
|
if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
88
279
|
else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
89
280
|
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
90
|
-
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
91
|
-
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}`));
|
|
92
282
|
});
|
|
93
283
|
}
|
|
94
284
|
return summary;
|
|
@@ -98,10 +288,7 @@ function countTestErrors(tasks) {
|
|
|
98
288
|
}
|
|
99
289
|
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
100
290
|
if (tasks.length === 0) return c.dim(`no ${name}`);
|
|
101
|
-
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
102
|
-
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
103
|
-
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
104
|
-
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");
|
|
105
292
|
return [
|
|
106
293
|
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
107
294
|
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
@@ -115,16 +302,13 @@ function getStateSymbol(task) {
|
|
|
115
302
|
if (task.result.state === "run" || task.result.state === "queued") {
|
|
116
303
|
if (task.type === "suite") return pointer;
|
|
117
304
|
}
|
|
118
|
-
|
|
119
|
-
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
|
|
120
|
-
return " ";
|
|
305
|
+
return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
|
|
121
306
|
}
|
|
122
307
|
function formatTimeString(date) {
|
|
123
308
|
return date.toTimeString().split(" ")[0];
|
|
124
309
|
}
|
|
125
310
|
function formatTime(time) {
|
|
126
|
-
|
|
127
|
-
return `${Math.round(time)}ms`;
|
|
311
|
+
return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
|
|
128
312
|
}
|
|
129
313
|
function formatProjectName(project, suffix = " ") {
|
|
130
314
|
if (!project?.name) return "";
|
|
@@ -145,8 +329,7 @@ function padSummaryTitle(str) {
|
|
|
145
329
|
}
|
|
146
330
|
function truncateString(text, maxLength) {
|
|
147
331
|
const plainText = stripVTControlCharacters(text);
|
|
148
|
-
|
|
149
|
-
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
332
|
+
return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
|
|
150
333
|
}
|
|
151
334
|
function capitalize(text) {
|
|
152
335
|
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
@@ -192,9 +375,7 @@ class BaseReporter {
|
|
|
192
375
|
this.isTTY = options.isTTY ?? isTTY;
|
|
193
376
|
}
|
|
194
377
|
onInit(ctx) {
|
|
195
|
-
this.ctx = ctx;
|
|
196
|
-
this.ctx.logger.printBanner();
|
|
197
|
-
this.start = performance$1.now();
|
|
378
|
+
this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
|
|
198
379
|
}
|
|
199
380
|
log(...messages) {
|
|
200
381
|
this.ctx.logger.log(...messages);
|
|
@@ -205,9 +386,9 @@ class BaseReporter {
|
|
|
205
386
|
relative(path) {
|
|
206
387
|
return relative(this.ctx.config.root, path);
|
|
207
388
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
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);
|
|
211
392
|
else this.reportSummary(files, errors);
|
|
212
393
|
}
|
|
213
394
|
onTestCaseResult(testCase) {
|
|
@@ -226,13 +407,10 @@ class BaseReporter {
|
|
|
226
407
|
printTestModule(testModule) {
|
|
227
408
|
const moduleState = testModule.state();
|
|
228
409
|
if (moduleState === "queued" || moduleState === "pending") return;
|
|
229
|
-
let testsCount = 0;
|
|
230
|
-
let failedCount = 0;
|
|
231
|
-
let skippedCount = 0;
|
|
410
|
+
let testsCount = 0, failedCount = 0, skippedCount = 0;
|
|
232
411
|
// delaying logs to calculate the test stats first
|
|
233
412
|
// which minimizes the amount of for loops
|
|
234
|
-
const logs = [];
|
|
235
|
-
const originalLog = this.log.bind(this);
|
|
413
|
+
const logs = [], originalLog = this.log.bind(this);
|
|
236
414
|
this.log = (msg) => logs.push(msg);
|
|
237
415
|
const visit = (suiteState, children) => {
|
|
238
416
|
for (const child of children) if (child.type === "suite") {
|
|
@@ -242,8 +420,7 @@ class BaseReporter {
|
|
|
242
420
|
visit(suiteState, child.children);
|
|
243
421
|
} else {
|
|
244
422
|
const testResult = child.result();
|
|
245
|
-
testsCount++;
|
|
246
|
-
if (testResult.state === "failed") failedCount++;
|
|
423
|
+
if (testsCount++, testResult.state === "failed") failedCount++;
|
|
247
424
|
else if (testResult.state === "skipped") skippedCount++;
|
|
248
425
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
|
|
249
426
|
// Skipped suites are hidden when --hideSkippedTests
|
|
@@ -260,24 +437,20 @@ class BaseReporter {
|
|
|
260
437
|
tests: testsCount,
|
|
261
438
|
failed: failedCount,
|
|
262
439
|
skipped: skippedCount
|
|
263
|
-
}));
|
|
264
|
-
logs.forEach((log) => this.log(log));
|
|
440
|
+
})), logs.forEach((log) => this.log(log));
|
|
265
441
|
}
|
|
266
442
|
printTestCase(moduleState, test) {
|
|
267
|
-
const testResult = test.result();
|
|
268
|
-
const { duration, retryCount, repeatCount } = test.diagnostic() || {};
|
|
269
|
-
const padding = this.getTestIndentation(test.task);
|
|
443
|
+
const testResult = test.result(), { duration, retryCount, repeatCount } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task);
|
|
270
444
|
let suffix = this.getDurationPrefix(test.task);
|
|
271
445
|
if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
|
|
272
446
|
if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
|
|
273
|
-
if (testResult.state === "failed")
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
|
|
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}`);
|
|
281
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}]`))}`);
|
|
282
455
|
else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
|
|
283
456
|
}
|
|
@@ -336,10 +509,8 @@ class BaseReporter {
|
|
|
336
509
|
this.log(BADGE_PADDING + hints.join(c.dim(", ")));
|
|
337
510
|
}
|
|
338
511
|
onWatcherRerun(files, trigger) {
|
|
339
|
-
this.watchFilters = files;
|
|
340
|
-
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
341
512
|
// Update re-run count for each file
|
|
342
|
-
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) => {
|
|
343
514
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
344
515
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
345
516
|
});
|
|
@@ -348,35 +519,26 @@ class BaseReporter {
|
|
|
348
519
|
const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
|
|
349
520
|
banner += c.blue(`x${rerun} `);
|
|
350
521
|
}
|
|
351
|
-
this.ctx.logger.clearFullScreen();
|
|
352
|
-
this.log(withLabel("blue", "RERUN", banner));
|
|
353
|
-
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(", ")));
|
|
354
523
|
if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
355
524
|
if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
356
525
|
this.log("");
|
|
357
526
|
for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
|
|
358
|
-
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
359
|
-
this.start = performance$1.now();
|
|
527
|
+
this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
|
|
360
528
|
}
|
|
361
529
|
onUserConsoleLog(log, taskState) {
|
|
362
530
|
if (!this.shouldLog(log, taskState)) return;
|
|
363
|
-
const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
|
|
364
|
-
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);
|
|
365
532
|
let headerText = "unknown test";
|
|
366
533
|
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
|
|
367
534
|
if (task) headerText = this.getFullName(task, c.dim(" > "));
|
|
368
535
|
else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
|
|
369
|
-
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content)
|
|
370
|
-
if (log.origin) {
|
|
536
|
+
if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
|
|
371
537
|
// browser logs don't have an extra end of line at the end like Node.js does
|
|
372
538
|
if (log.browser) write("\n");
|
|
373
|
-
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
374
|
-
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
375
|
-
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);
|
|
376
540
|
for (const frame of stack) {
|
|
377
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
378
|
-
const path = relative(project.config.root, frame.file);
|
|
379
|
-
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(" ");
|
|
380
542
|
write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
|
|
381
543
|
}
|
|
382
544
|
}
|
|
@@ -386,51 +548,38 @@ class BaseReporter {
|
|
|
386
548
|
this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
|
|
387
549
|
}
|
|
388
550
|
shouldLog(log, taskState) {
|
|
389
|
-
if (this.ctx.config.silent === true) return false;
|
|
390
|
-
if (this.ctx.config.
|
|
391
|
-
|
|
392
|
-
|
|
551
|
+
if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
|
|
552
|
+
if (this.ctx.config.onConsoleLog) {
|
|
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);
|
|
554
|
+
if (shouldLog === false) return shouldLog;
|
|
555
|
+
}
|
|
393
556
|
return true;
|
|
394
557
|
}
|
|
395
558
|
onServerRestart(reason) {
|
|
396
559
|
this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
|
|
397
560
|
}
|
|
398
561
|
reportSummary(files, errors) {
|
|
399
|
-
this.printErrorsSummary(files, errors);
|
|
400
|
-
if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
562
|
+
if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
|
|
401
563
|
else this.reportTestSummary(files, errors);
|
|
402
564
|
}
|
|
403
565
|
reportTestSummary(files, errors) {
|
|
404
566
|
this.log();
|
|
405
|
-
const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
|
|
406
|
-
const tests = getTests(affectedFiles);
|
|
407
|
-
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);
|
|
408
568
|
for (const [index, snapshot] of snapshotOutput.entries()) {
|
|
409
569
|
const title = index === 0 ? "Snapshots" : "";
|
|
410
570
|
this.log(`${padSummaryTitle(title)} ${snapshot}`);
|
|
411
571
|
}
|
|
412
572
|
if (snapshotOutput.length > 1) this.log();
|
|
413
|
-
this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles))
|
|
414
|
-
this.log(padSummaryTitle("Tests"), getStateString$1(tests));
|
|
415
|
-
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)) {
|
|
416
574
|
const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
|
|
417
575
|
this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
|
|
418
576
|
}
|
|
419
577
|
if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
|
|
420
578
|
this.log(padSummaryTitle("Start at"), this._timeStart);
|
|
421
|
-
const collectTime = sum(files, (file) => file.collectDuration);
|
|
422
|
-
const testsTime = sum(files, (file) => file.result?.duration);
|
|
423
|
-
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);
|
|
424
580
|
if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
425
581
|
else {
|
|
426
|
-
const blobs = this.ctx.state.blobs
|
|
427
|
-
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
428
|
-
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
429
|
-
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
430
|
-
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
431
|
-
const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
|
|
432
|
-
const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
|
|
433
|
-
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 = [
|
|
434
583
|
`transform ${formatTime(transformTime)}`,
|
|
435
584
|
`setup ${formatTime(setupTime)}`,
|
|
436
585
|
`collect ${formatTime(collectTime)}`,
|
|
@@ -439,41 +588,25 @@ class BaseReporter {
|
|
|
439
588
|
`prepare ${formatTime(prepareTime)}`,
|
|
440
589
|
typecheck && `typecheck ${formatTime(typecheck)}`
|
|
441
590
|
].filter(Boolean).join(", ");
|
|
442
|
-
this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
|
|
443
|
-
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(""));
|
|
444
592
|
}
|
|
445
593
|
this.log();
|
|
446
594
|
}
|
|
447
595
|
printErrorsSummary(files, errors) {
|
|
448
|
-
const suites = getSuites(files);
|
|
449
|
-
const tests = getTests(files);
|
|
450
|
-
const failedSuites = suites.filter((i) => i.result?.errors);
|
|
451
|
-
const failedTests = tests.filter((i) => i.result?.state === "fail");
|
|
452
|
-
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);
|
|
453
597
|
let current = 1;
|
|
454
598
|
const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
|
|
455
|
-
if (failedSuites.length) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
if (failedTests.length) {
|
|
460
|
-
this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
|
|
461
|
-
this.printTaskErrors(failedTests, errorDivider);
|
|
462
|
-
}
|
|
463
|
-
if (errors.length) {
|
|
464
|
-
this.ctx.logger.printUnhandledErrors(errors);
|
|
465
|
-
this.error();
|
|
466
|
-
}
|
|
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();
|
|
467
602
|
}
|
|
468
603
|
reportBenchmarkSummary(files) {
|
|
469
|
-
const benches = getTests(files);
|
|
470
|
-
const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
604
|
+
const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
|
|
471
605
|
this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
|
|
472
606
|
for (const bench of topBenches) {
|
|
473
607
|
const group = bench.suite || bench.file;
|
|
474
608
|
if (!group) continue;
|
|
475
|
-
const groupName = this.getFullName(group, c.dim(" > "));
|
|
476
|
-
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);
|
|
477
610
|
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
478
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);
|
|
479
612
|
for (const sibling of siblings) {
|
|
@@ -491,10 +624,7 @@ class BaseReporter {
|
|
|
491
624
|
let previous;
|
|
492
625
|
if (error?.stack) previous = errorsQueue.find((i) => {
|
|
493
626
|
if (i[0]?.stack !== error.stack) return false;
|
|
494
|
-
const currentProjectName = task?.projectName || task.file?.projectName || "";
|
|
495
|
-
const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
|
|
496
|
-
const currentAnnotations = task.type === "test" && task.annotations;
|
|
497
|
-
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;
|
|
498
628
|
return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
|
|
499
629
|
});
|
|
500
630
|
if (previous) previous[1].push(task);
|
|
@@ -502,24 +632,20 @@ class BaseReporter {
|
|
|
502
632
|
});
|
|
503
633
|
for (const [error, tasks] of errorsQueue) {
|
|
504
634
|
for (const task of tasks) {
|
|
505
|
-
const filepath = task?.filepath || "";
|
|
506
|
-
const projectName = task?.projectName || task.file?.projectName || "";
|
|
507
|
-
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);
|
|
508
636
|
let name = this.getFullName(task, c.dim(" > "));
|
|
509
637
|
if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
510
638
|
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
511
639
|
}
|
|
512
640
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
513
|
-
this.ctx.logger.printError(error, {
|
|
641
|
+
if (this.ctx.logger.printError(error, {
|
|
514
642
|
project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
|
|
515
643
|
verbose: this.verbose,
|
|
516
644
|
screenshotPaths,
|
|
517
645
|
task: tasks[0]
|
|
518
|
-
})
|
|
519
|
-
if (tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
646
|
+
}), tasks[0].type === "test" && tasks[0].annotations.length) {
|
|
520
647
|
const test = this.ctx.state.getReportedEntity(tasks[0]);
|
|
521
|
-
this.printAnnotations(test, "error", 1);
|
|
522
|
-
this.error();
|
|
648
|
+
this.printAnnotations(test, "error", 1), this.error();
|
|
523
649
|
}
|
|
524
650
|
errorDivider();
|
|
525
651
|
}
|
|
@@ -528,8 +654,7 @@ class BaseReporter {
|
|
|
528
654
|
function deepEqual(a, b) {
|
|
529
655
|
if (a === b) return true;
|
|
530
656
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
531
|
-
const keysA = Object.keys(a);
|
|
532
|
-
const keysB = Object.keys(b);
|
|
657
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
533
658
|
if (keysA.length !== keysB.length) return false;
|
|
534
659
|
for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
|
535
660
|
return true;
|
|
@@ -540,239 +665,7 @@ function sum(items, cb) {
|
|
|
540
665
|
}, 0);
|
|
541
666
|
}
|
|
542
667
|
|
|
543
|
-
|
|
544
|
-
constructor() {
|
|
545
|
-
super();
|
|
546
|
-
this.isTTY = false;
|
|
547
|
-
}
|
|
548
|
-
onInit(ctx) {
|
|
549
|
-
super.onInit(ctx);
|
|
550
|
-
ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\nRemove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`);
|
|
551
|
-
}
|
|
552
|
-
reportSummary(files, errors) {
|
|
553
|
-
// non-tty mode doesn't add a new line
|
|
554
|
-
this.ctx.logger.log();
|
|
555
|
-
return super.reportSummary(files, errors);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/// <reference types="../types/index.d.ts" />
|
|
560
|
-
|
|
561
|
-
// (c) 2020-present Andrea Giammarchi
|
|
562
|
-
|
|
563
|
-
const {parse: $parse, stringify: $stringify} = JSON;
|
|
564
|
-
const {keys} = Object;
|
|
565
|
-
|
|
566
|
-
const Primitive = String; // it could be Number
|
|
567
|
-
const primitive = 'string'; // it could be 'number'
|
|
568
|
-
|
|
569
|
-
const ignore = {};
|
|
570
|
-
const object = 'object';
|
|
571
|
-
|
|
572
|
-
const noop = (_, value) => value;
|
|
573
|
-
|
|
574
|
-
const primitives = value => (
|
|
575
|
-
value instanceof Primitive ? Primitive(value) : value
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
const Primitives = (_, value) => (
|
|
579
|
-
typeof value === primitive ? new Primitive(value) : value
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
const revive = (input, parsed, output, $) => {
|
|
583
|
-
const lazy = [];
|
|
584
|
-
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
585
|
-
const k = ke[y];
|
|
586
|
-
const value = output[k];
|
|
587
|
-
if (value instanceof Primitive) {
|
|
588
|
-
const tmp = input[value];
|
|
589
|
-
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
590
|
-
parsed.add(tmp);
|
|
591
|
-
output[k] = ignore;
|
|
592
|
-
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
593
|
-
}
|
|
594
|
-
else
|
|
595
|
-
output[k] = $.call(output, k, tmp);
|
|
596
|
-
}
|
|
597
|
-
else if (output[k] !== ignore)
|
|
598
|
-
output[k] = $.call(output, k, value);
|
|
599
|
-
}
|
|
600
|
-
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
601
|
-
const {k, a} = lazy[i];
|
|
602
|
-
output[k] = $.call(output, k, revive.apply(null, a));
|
|
603
|
-
}
|
|
604
|
-
return output;
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
const set = (known, input, value) => {
|
|
608
|
-
const index = Primitive(input.push(value) - 1);
|
|
609
|
-
known.set(value, index);
|
|
610
|
-
return index;
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Converts a specialized flatted string into a JS value.
|
|
615
|
-
* @param {string} text
|
|
616
|
-
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
617
|
-
* @returns {any}
|
|
618
|
-
*/
|
|
619
|
-
const parse = (text, reviver) => {
|
|
620
|
-
const input = $parse(text, Primitives).map(primitives);
|
|
621
|
-
const value = input[0];
|
|
622
|
-
const $ = reviver || noop;
|
|
623
|
-
const tmp = typeof value === object && value ?
|
|
624
|
-
revive(input, new Set, value, $) :
|
|
625
|
-
value;
|
|
626
|
-
return $.call({'': tmp}, '', tmp);
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Converts a JS value into a specialized flatted string.
|
|
631
|
-
* @param {any} value
|
|
632
|
-
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
633
|
-
* @param {string | number | undefined} [space]
|
|
634
|
-
* @returns {string}
|
|
635
|
-
*/
|
|
636
|
-
const stringify = (value, replacer, space) => {
|
|
637
|
-
const $ = replacer && typeof replacer === object ?
|
|
638
|
-
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
639
|
-
(replacer || noop);
|
|
640
|
-
const known = new Map;
|
|
641
|
-
const input = [];
|
|
642
|
-
const output = [];
|
|
643
|
-
let i = +set(known, input, $.call({'': value}, '', value));
|
|
644
|
-
let firstRun = !i;
|
|
645
|
-
while (i < input.length) {
|
|
646
|
-
firstRun = true;
|
|
647
|
-
output[i] = $stringify(input[i++], replace, space);
|
|
648
|
-
}
|
|
649
|
-
return '[' + output.join(',') + ']';
|
|
650
|
-
function replace(key, value) {
|
|
651
|
-
if (firstRun) {
|
|
652
|
-
firstRun = !firstRun;
|
|
653
|
-
return value;
|
|
654
|
-
}
|
|
655
|
-
const after = $.call(this, key, value);
|
|
656
|
-
switch (typeof after) {
|
|
657
|
-
case object:
|
|
658
|
-
if (after === null) return after;
|
|
659
|
-
case primitive:
|
|
660
|
-
return known.get(after) || set(known, input, after);
|
|
661
|
-
}
|
|
662
|
-
return after;
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
class BlobReporter {
|
|
667
|
-
start = 0;
|
|
668
|
-
ctx;
|
|
669
|
-
options;
|
|
670
|
-
constructor(options) {
|
|
671
|
-
this.options = options;
|
|
672
|
-
}
|
|
673
|
-
onInit(ctx) {
|
|
674
|
-
if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
|
|
675
|
-
this.ctx = ctx;
|
|
676
|
-
this.start = performance.now();
|
|
677
|
-
}
|
|
678
|
-
async onFinished(files = [], errors = [], coverage) {
|
|
679
|
-
const executionTime = performance.now() - this.start;
|
|
680
|
-
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
681
|
-
if (!outputFile) {
|
|
682
|
-
const shard = this.ctx.config.shard;
|
|
683
|
-
outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
|
|
684
|
-
}
|
|
685
|
-
const modules = this.ctx.projects.map((project) => {
|
|
686
|
-
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
687
|
-
if (!mod[1].file) return null;
|
|
688
|
-
return [
|
|
689
|
-
mod[0],
|
|
690
|
-
mod[1].file,
|
|
691
|
-
mod[1].url
|
|
692
|
-
];
|
|
693
|
-
}).filter((x) => x != null)];
|
|
694
|
-
});
|
|
695
|
-
const report = [
|
|
696
|
-
this.ctx.version,
|
|
697
|
-
files,
|
|
698
|
-
errors,
|
|
699
|
-
modules,
|
|
700
|
-
coverage,
|
|
701
|
-
executionTime
|
|
702
|
-
];
|
|
703
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
704
|
-
await writeBlob(report, reportFile);
|
|
705
|
-
this.ctx.logger.log("blob report written to", reportFile);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
async function writeBlob(content, filename) {
|
|
709
|
-
const report = stringify(content);
|
|
710
|
-
const dir = dirname(filename);
|
|
711
|
-
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
712
|
-
await writeFile(filename, report, "utf-8");
|
|
713
|
-
}
|
|
714
|
-
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
715
|
-
// using process.cwd() because --merge-reports can only be used in CLI
|
|
716
|
-
const resolvedDir = resolve(process.cwd(), blobsDirectory);
|
|
717
|
-
const blobsFiles = await readdir(resolvedDir);
|
|
718
|
-
const promises = blobsFiles.map(async (filename) => {
|
|
719
|
-
const fullPath = resolve(resolvedDir, filename);
|
|
720
|
-
const stats = await stat(fullPath);
|
|
721
|
-
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`);
|
|
722
|
-
const content = await readFile(fullPath, "utf-8");
|
|
723
|
-
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
724
|
-
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`);
|
|
725
|
-
return {
|
|
726
|
-
version,
|
|
727
|
-
files,
|
|
728
|
-
errors,
|
|
729
|
-
moduleKeys,
|
|
730
|
-
coverage,
|
|
731
|
-
file: filename,
|
|
732
|
-
executionTime
|
|
733
|
-
};
|
|
734
|
-
});
|
|
735
|
-
const blobs = await Promise.all(promises);
|
|
736
|
-
if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
737
|
-
const versions = new Set(blobs.map((blob) => blob.version));
|
|
738
|
-
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")}`);
|
|
739
|
-
if (!versions.has(currentVersion)) throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
740
|
-
// fake module graph - it is used to check if module is imported, but we don't use values inside
|
|
741
|
-
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
742
|
-
blobs.forEach((blob) => {
|
|
743
|
-
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
744
|
-
const project = projects[projectName];
|
|
745
|
-
if (!project) return;
|
|
746
|
-
moduleIds.forEach(([moduleId, file, url]) => {
|
|
747
|
-
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
748
|
-
moduleNode.url = url;
|
|
749
|
-
moduleNode.id = moduleId;
|
|
750
|
-
project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
751
|
-
});
|
|
752
|
-
});
|
|
753
|
-
});
|
|
754
|
-
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
755
|
-
const time1 = f1.result?.startTime || 0;
|
|
756
|
-
const time2 = f2.result?.startTime || 0;
|
|
757
|
-
return time1 - time2;
|
|
758
|
-
});
|
|
759
|
-
const errors = blobs.flatMap((blob) => blob.errors);
|
|
760
|
-
const coverages = blobs.map((blob) => blob.coverage);
|
|
761
|
-
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
762
|
-
return {
|
|
763
|
-
files,
|
|
764
|
-
errors,
|
|
765
|
-
coverages,
|
|
766
|
-
executionTimes
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const DEFAULT_RENDER_INTERVAL_MS = 1e3;
|
|
771
|
-
const ESC = "\x1B[";
|
|
772
|
-
const CLEAR_LINE = `${ESC}K`;
|
|
773
|
-
const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
|
|
774
|
-
const SYNC_START = `${ESC}?2026h`;
|
|
775
|
-
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`;
|
|
776
669
|
/**
|
|
777
670
|
* Renders content of `getWindow` at the bottom of the terminal and
|
|
778
671
|
* forwards all other intercepted `stdout` and `stderr` logs above it.
|
|
@@ -784,53 +677,41 @@ class WindowRenderer {
|
|
|
784
677
|
renderInterval = void 0;
|
|
785
678
|
renderScheduled = false;
|
|
786
679
|
windowHeight = 0;
|
|
680
|
+
started = false;
|
|
787
681
|
finished = false;
|
|
788
682
|
cleanups = [];
|
|
789
683
|
constructor(options) {
|
|
684
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
790
685
|
this.options = {
|
|
791
686
|
interval: DEFAULT_RENDER_INTERVAL_MS,
|
|
792
687
|
...options
|
|
793
|
-
}
|
|
794
|
-
this.streams = {
|
|
688
|
+
}, this.streams = {
|
|
795
689
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
796
690
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
800
|
-
this.options.logger.onTerminalCleanup(() => {
|
|
801
|
-
this.flushBuffer();
|
|
802
|
-
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();
|
|
803
693
|
});
|
|
804
|
-
this.start();
|
|
805
694
|
}
|
|
806
695
|
start() {
|
|
807
|
-
this.finished = false;
|
|
808
|
-
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();
|
|
809
697
|
}
|
|
810
698
|
stop() {
|
|
811
|
-
this.cleanups.splice(0).map((fn) => fn());
|
|
812
|
-
clearInterval(this.renderInterval);
|
|
699
|
+
this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
|
|
813
700
|
}
|
|
814
701
|
/**
|
|
815
702
|
* Write all buffered output and stop buffering.
|
|
816
703
|
* All intercepted writes are forwarded to actual write after this.
|
|
817
704
|
*/
|
|
818
705
|
finish() {
|
|
819
|
-
this.finished = true;
|
|
820
|
-
this.flushBuffer();
|
|
821
|
-
clearInterval(this.renderInterval);
|
|
706
|
+
this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
|
|
822
707
|
}
|
|
823
708
|
/**
|
|
824
709
|
* Queue new render update
|
|
825
710
|
*/
|
|
826
711
|
schedule() {
|
|
827
|
-
if (!this.renderScheduled) {
|
|
828
|
-
this.renderScheduled =
|
|
829
|
-
|
|
830
|
-
setTimeout(() => {
|
|
831
|
-
this.renderScheduled = false;
|
|
832
|
-
}, 100).unref();
|
|
833
|
-
}
|
|
712
|
+
if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
|
|
713
|
+
this.renderScheduled = false;
|
|
714
|
+
}, 100).unref();
|
|
834
715
|
}
|
|
835
716
|
flushBuffer() {
|
|
836
717
|
if (this.buffer.length === 0) return this.render();
|
|
@@ -842,8 +723,7 @@ class WindowRenderer {
|
|
|
842
723
|
continue;
|
|
843
724
|
}
|
|
844
725
|
if (current.type !== next.type) {
|
|
845
|
-
this.render(current.message, current.type);
|
|
846
|
-
current = next;
|
|
726
|
+
this.render(current.message, current.type), current = next;
|
|
847
727
|
continue;
|
|
848
728
|
}
|
|
849
729
|
current.message += next.message;
|
|
@@ -851,40 +731,31 @@ class WindowRenderer {
|
|
|
851
731
|
if (current) this.render(current?.message, current?.type);
|
|
852
732
|
}
|
|
853
733
|
render(message, type = "output") {
|
|
854
|
-
if (this.finished)
|
|
855
|
-
|
|
856
|
-
return this.write(message || "", type);
|
|
857
|
-
}
|
|
858
|
-
const windowContent = this.options.getWindow();
|
|
859
|
-
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());
|
|
860
736
|
let padding = this.windowHeight - rowCount;
|
|
861
737
|
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
862
|
-
this.write(SYNC_START);
|
|
863
|
-
this.clearWindow();
|
|
864
|
-
if (message) this.write(message, type);
|
|
738
|
+
if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
|
|
865
739
|
if (padding > 0) this.write("\n".repeat(padding));
|
|
866
|
-
this.write(windowContent.join("\n"));
|
|
867
|
-
this.write(SYNC_END);
|
|
868
|
-
this.windowHeight = rowCount + Math.max(0, padding);
|
|
740
|
+
this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
|
|
869
741
|
}
|
|
870
742
|
clearWindow() {
|
|
871
|
-
if (this.windowHeight
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
+
}
|
|
875
748
|
}
|
|
876
749
|
interceptStream(stream, type) {
|
|
877
750
|
const original = stream.write;
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
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);
|
|
881
753
|
else this.buffer.push({
|
|
882
754
|
type,
|
|
883
755
|
message: chunk.toString()
|
|
884
756
|
});
|
|
885
757
|
callback?.();
|
|
886
|
-
}
|
|
887
|
-
return function restore() {
|
|
758
|
+
}, function restore() {
|
|
888
759
|
stream.write = original;
|
|
889
760
|
};
|
|
890
761
|
}
|
|
@@ -902,8 +773,7 @@ function getRenderedRowCount(rows, columns) {
|
|
|
902
773
|
return count;
|
|
903
774
|
}
|
|
904
775
|
|
|
905
|
-
const DURATION_UPDATE_INTERVAL_MS = 100;
|
|
906
|
-
const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
776
|
+
const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
|
|
907
777
|
/**
|
|
908
778
|
* Reporter extension that renders summary and forwards all other logs above itself.
|
|
909
779
|
* Intended to be used by other reporters, not as a standalone reporter.
|
|
@@ -924,34 +794,21 @@ class SummaryReporter {
|
|
|
924
794
|
duration = 0;
|
|
925
795
|
durationInterval = void 0;
|
|
926
796
|
onInit(ctx, options = {}) {
|
|
927
|
-
this.ctx = ctx
|
|
928
|
-
this.options = {
|
|
797
|
+
this.ctx = ctx, this.options = {
|
|
929
798
|
verbose: false,
|
|
930
799
|
...options
|
|
931
|
-
}
|
|
932
|
-
this.renderer = new WindowRenderer({
|
|
800
|
+
}, this.renderer = new WindowRenderer({
|
|
933
801
|
logger: ctx.logger,
|
|
934
802
|
getWindow: () => this.createSummary()
|
|
935
|
-
})
|
|
936
|
-
|
|
937
|
-
clearInterval(this.durationInterval);
|
|
938
|
-
this.renderer.stop();
|
|
803
|
+
}), this.ctx.onClose(() => {
|
|
804
|
+
clearInterval(this.durationInterval), this.renderer.stop();
|
|
939
805
|
});
|
|
940
806
|
}
|
|
941
807
|
onTestRunStart(specifications) {
|
|
942
|
-
this.runningModules.clear();
|
|
943
|
-
this.finishedModules.clear();
|
|
944
|
-
this.modules = emptyCounters();
|
|
945
|
-
this.tests = emptyCounters();
|
|
946
|
-
this.startTimers();
|
|
947
|
-
this.renderer.start();
|
|
948
|
-
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;
|
|
949
809
|
}
|
|
950
810
|
onTestRunEnd() {
|
|
951
|
-
this.runningModules.clear();
|
|
952
|
-
this.finishedModules.clear();
|
|
953
|
-
this.renderer.finish();
|
|
954
|
-
clearInterval(this.durationInterval);
|
|
811
|
+
this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
|
|
955
812
|
}
|
|
956
813
|
onTestModuleQueued(module) {
|
|
957
814
|
// When new test module starts, take the place of previously finished test module, if any
|
|
@@ -959,20 +816,13 @@ class SummaryReporter {
|
|
|
959
816
|
const finished = this.finishedModules.keys().next().value;
|
|
960
817
|
this.removeTestModule(finished);
|
|
961
818
|
}
|
|
962
|
-
this.runningModules.set(module.id, initializeStats(module));
|
|
963
|
-
this.renderer.schedule();
|
|
819
|
+
this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
|
|
964
820
|
}
|
|
965
821
|
onTestModuleCollected(module) {
|
|
966
822
|
let stats = this.runningModules.get(module.id);
|
|
967
|
-
if (!stats)
|
|
968
|
-
stats = initializeStats(module);
|
|
969
|
-
this.runningModules.set(module.id, stats);
|
|
970
|
-
}
|
|
823
|
+
if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
|
|
971
824
|
const total = Array.from(module.children.allTests()).length;
|
|
972
|
-
this.tests.total += total;
|
|
973
|
-
stats.total = total;
|
|
974
|
-
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
975
|
-
this.renderer.schedule();
|
|
825
|
+
this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
|
|
976
826
|
}
|
|
977
827
|
onHookStart(options) {
|
|
978
828
|
const stats = this.getHookStats(options);
|
|
@@ -983,8 +833,7 @@ class SummaryReporter {
|
|
|
983
833
|
startTime: performance.now(),
|
|
984
834
|
onFinish: () => {}
|
|
985
835
|
};
|
|
986
|
-
stats.hook?.onFinish?.();
|
|
987
|
-
stats.hook = hook;
|
|
836
|
+
stats.hook?.onFinish?.(), stats.hook = hook;
|
|
988
837
|
const timeout = setTimeout(() => {
|
|
989
838
|
hook.visible = true;
|
|
990
839
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
@@ -992,9 +841,7 @@ class SummaryReporter {
|
|
|
992
841
|
}
|
|
993
842
|
onHookEnd(options) {
|
|
994
843
|
const stats = this.getHookStats(options);
|
|
995
|
-
|
|
996
|
-
stats.hook.onFinish();
|
|
997
|
-
stats.hook.visible = false;
|
|
844
|
+
stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
|
|
998
845
|
}
|
|
999
846
|
onTestCaseReady(test) {
|
|
1000
847
|
// Track slow running tests only on verbose mode
|
|
@@ -1006,22 +853,17 @@ class SummaryReporter {
|
|
|
1006
853
|
visible: false,
|
|
1007
854
|
startTime: performance.now(),
|
|
1008
855
|
onFinish: () => {}
|
|
1009
|
-
}
|
|
1010
|
-
const timeout = setTimeout(() => {
|
|
856
|
+
}, timeout = setTimeout(() => {
|
|
1011
857
|
slowTest.visible = true;
|
|
1012
858
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
1013
859
|
slowTest.onFinish = () => {
|
|
1014
|
-
slowTest.hook?.onFinish();
|
|
1015
|
-
|
|
1016
|
-
};
|
|
1017
|
-
stats.tests.set(test.id, slowTest);
|
|
860
|
+
slowTest.hook?.onFinish(), clearTimeout(timeout);
|
|
861
|
+
}, stats.tests.set(test.id, slowTest);
|
|
1018
862
|
}
|
|
1019
863
|
onTestCaseResult(test) {
|
|
1020
864
|
const stats = this.runningModules.get(test.module.id);
|
|
1021
865
|
if (!stats) return;
|
|
1022
|
-
stats.tests.get(test.id)?.onFinish()
|
|
1023
|
-
stats.tests.delete(test.id);
|
|
1024
|
-
stats.completed++;
|
|
866
|
+
stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
|
|
1025
867
|
const result = test.result();
|
|
1026
868
|
if (result?.state === "passed") this.tests.passed++;
|
|
1027
869
|
else if (result?.state === "failed") this.tests.failed++;
|
|
@@ -1030,8 +872,7 @@ class SummaryReporter {
|
|
|
1030
872
|
}
|
|
1031
873
|
onTestModuleEnd(module) {
|
|
1032
874
|
const state = module.state();
|
|
1033
|
-
this.modules.completed++;
|
|
1034
|
-
if (state === "passed") this.modules.passed++;
|
|
875
|
+
if (this.modules.completed++, state === "passed") this.modules.passed++;
|
|
1035
876
|
else if (state === "failed") this.modules.failed++;
|
|
1036
877
|
else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
|
|
1037
878
|
else if (state === "skipped") this.modules.skipped++;
|
|
@@ -1051,10 +892,8 @@ class SummaryReporter {
|
|
|
1051
892
|
getHookStats({ entity }) {
|
|
1052
893
|
// Track slow running hooks only on verbose mode
|
|
1053
894
|
if (!this.options.verbose) return;
|
|
1054
|
-
const module = entity.type === "module" ? entity : entity.module;
|
|
1055
|
-
|
|
1056
|
-
if (!stats) return;
|
|
1057
|
-
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;
|
|
1058
897
|
}
|
|
1059
898
|
createSummary() {
|
|
1060
899
|
const summary = [""];
|
|
@@ -1066,36 +905,23 @@ class SummaryReporter {
|
|
|
1066
905
|
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1067
906
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1068
907
|
for (const [index, task] of slowTasks.entries()) {
|
|
1069
|
-
const elapsed = this.currentTime - task.startTime;
|
|
1070
|
-
|
|
1071
|
-
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
1072
|
-
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);
|
|
1073
910
|
}
|
|
1074
911
|
}
|
|
1075
912
|
if (this.runningModules.size > 0) summary.push("");
|
|
1076
|
-
summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
|
|
1077
|
-
summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
|
|
1078
|
-
summary.push(padSummaryTitle("Start at") + this.startTime);
|
|
1079
|
-
summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
|
|
1080
|
-
summary.push("");
|
|
1081
|
-
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;
|
|
1082
914
|
}
|
|
1083
915
|
startTimers() {
|
|
1084
916
|
const start = performance.now();
|
|
1085
|
-
this.startTime = formatTimeString(/* @__PURE__ */ new Date())
|
|
1086
|
-
|
|
1087
|
-
this.currentTime = performance.now();
|
|
1088
|
-
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;
|
|
1089
919
|
}, DURATION_UPDATE_INTERVAL_MS).unref();
|
|
1090
920
|
}
|
|
1091
921
|
removeTestModule(id) {
|
|
1092
922
|
if (!id) return;
|
|
1093
923
|
const testFile = this.runningModules.get(id);
|
|
1094
|
-
testFile?.hook?.onFinish();
|
|
1095
|
-
testFile?.tests?.forEach((test) => test.onFinish());
|
|
1096
|
-
this.runningModules.delete(id);
|
|
1097
|
-
clearTimeout(this.finishedModules.get(id));
|
|
1098
|
-
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);
|
|
1099
925
|
}
|
|
1100
926
|
}
|
|
1101
927
|
function emptyCounters() {
|
|
@@ -1117,9 +943,7 @@ function getStateString(entry) {
|
|
|
1117
943
|
].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
|
|
1118
944
|
}
|
|
1119
945
|
function sortRunningModules(a, b) {
|
|
1120
|
-
|
|
1121
|
-
if ((a.projectName || "") < (b.projectName || "")) return -1;
|
|
1122
|
-
return a.filename.localeCompare(b.filename);
|
|
946
|
+
return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
|
|
1123
947
|
}
|
|
1124
948
|
function initializeStats(module) {
|
|
1125
949
|
return {
|
|
@@ -1137,12 +961,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
1137
961
|
options;
|
|
1138
962
|
summary;
|
|
1139
963
|
constructor(options = {}) {
|
|
1140
|
-
super(options)
|
|
1141
|
-
this.options = {
|
|
964
|
+
if (super(options), this.options = {
|
|
1142
965
|
summary: true,
|
|
1143
966
|
...options
|
|
1144
|
-
};
|
|
1145
|
-
if (!this.isTTY) this.options.summary = false;
|
|
967
|
+
}, !this.isTTY) this.options.summary = false;
|
|
1146
968
|
if (this.options.summary) this.summary = new SummaryReporter();
|
|
1147
969
|
}
|
|
1148
970
|
onTestRunStart(specifications) {
|
|
@@ -1152,6 +974,9 @@ class DefaultReporter extends BaseReporter {
|
|
|
1152
974
|
}
|
|
1153
975
|
this.summary?.onTestRunStart(specifications);
|
|
1154
976
|
}
|
|
977
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
978
|
+
super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
|
|
979
|
+
}
|
|
1155
980
|
onTestModuleQueued(file) {
|
|
1156
981
|
this.summary?.onTestModuleQueued(file);
|
|
1157
982
|
}
|
|
@@ -1159,15 +984,13 @@ class DefaultReporter extends BaseReporter {
|
|
|
1159
984
|
this.summary?.onTestModuleCollected(module);
|
|
1160
985
|
}
|
|
1161
986
|
onTestModuleEnd(module) {
|
|
1162
|
-
super.onTestModuleEnd(module);
|
|
1163
|
-
this.summary?.onTestModuleEnd(module);
|
|
987
|
+
super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
|
|
1164
988
|
}
|
|
1165
989
|
onTestCaseReady(test) {
|
|
1166
990
|
this.summary?.onTestCaseReady(test);
|
|
1167
991
|
}
|
|
1168
992
|
onTestCaseResult(test) {
|
|
1169
|
-
super.onTestCaseResult(test);
|
|
1170
|
-
this.summary?.onTestCaseResult(test);
|
|
993
|
+
super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
|
|
1171
994
|
}
|
|
1172
995
|
onHookStart(hook) {
|
|
1173
996
|
this.summary?.onHookStart(hook);
|
|
@@ -1176,11 +999,7 @@ class DefaultReporter extends BaseReporter {
|
|
|
1176
999
|
this.summary?.onHookEnd(hook);
|
|
1177
1000
|
}
|
|
1178
1001
|
onInit(ctx) {
|
|
1179
|
-
super.onInit(ctx);
|
|
1180
|
-
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1181
|
-
}
|
|
1182
|
-
onTestRunEnd() {
|
|
1183
|
-
this.summary?.onTestRunEnd();
|
|
1002
|
+
super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1184
1003
|
}
|
|
1185
1004
|
}
|
|
1186
1005
|
|
|
@@ -1189,30 +1008,22 @@ class DotReporter extends BaseReporter {
|
|
|
1189
1008
|
tests = /* @__PURE__ */ new Map();
|
|
1190
1009
|
finishedTests = /* @__PURE__ */ new Set();
|
|
1191
1010
|
onInit(ctx) {
|
|
1192
|
-
super.onInit(ctx)
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
getWindow: () => this.createSummary()
|
|
1197
|
-
});
|
|
1198
|
-
this.ctx.onClose(() => this.renderer?.stop());
|
|
1199
|
-
}
|
|
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());
|
|
1200
1015
|
}
|
|
1201
1016
|
// Ignore default logging of base reporter
|
|
1202
1017
|
printTestModule() {}
|
|
1203
1018
|
onWatcherRerun(files, trigger) {
|
|
1204
|
-
this.tests.clear();
|
|
1205
|
-
this.renderer?.start();
|
|
1206
|
-
super.onWatcherRerun(files, trigger);
|
|
1019
|
+
this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
|
|
1207
1020
|
}
|
|
1208
|
-
|
|
1021
|
+
onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
1209
1022
|
if (this.isTTY) {
|
|
1210
1023
|
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1211
1024
|
this.ctx.logger.log(finalLog);
|
|
1212
1025
|
} else this.ctx.logger.log();
|
|
1213
|
-
this.tests.clear();
|
|
1214
|
-
this.renderer?.finish();
|
|
1215
|
-
super.onFinished(files, errors);
|
|
1026
|
+
this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
1216
1027
|
}
|
|
1217
1028
|
onTestModuleCollected(module) {
|
|
1218
1029
|
for (const test of module.children.allTests())
|
|
@@ -1220,22 +1031,16 @@ class DotReporter extends BaseReporter {
|
|
|
1220
1031
|
this.onTestCaseReady(test);
|
|
1221
1032
|
}
|
|
1222
1033
|
onTestCaseReady(test) {
|
|
1223
|
-
|
|
1224
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1225
|
-
this.renderer?.schedule();
|
|
1034
|
+
this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
|
|
1226
1035
|
}
|
|
1227
1036
|
onTestCaseResult(test) {
|
|
1228
1037
|
const result = test.result().state;
|
|
1229
1038
|
// On non-TTY the finished tests are printed immediately
|
|
1230
1039
|
if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
|
|
1231
|
-
super.onTestCaseResult(test);
|
|
1232
|
-
this.finishedTests.add(test.id);
|
|
1233
|
-
this.tests.set(test.id, result || "skipped");
|
|
1234
|
-
this.renderer?.schedule();
|
|
1040
|
+
super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
|
|
1235
1041
|
}
|
|
1236
1042
|
onTestModuleEnd(testModule) {
|
|
1237
|
-
super.onTestModuleEnd(testModule);
|
|
1238
|
-
if (!this.isTTY) return;
|
|
1043
|
+
if (super.onTestModuleEnd(testModule), !this.isTTY) return;
|
|
1239
1044
|
const columns = this.ctx.logger.getColumns();
|
|
1240
1045
|
if (this.tests.size < columns) return;
|
|
1241
1046
|
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
@@ -1245,11 +1050,9 @@ class DotReporter extends BaseReporter {
|
|
|
1245
1050
|
let count = 0;
|
|
1246
1051
|
for (const [id, state] of finishedTests) {
|
|
1247
1052
|
if (count++ >= columns) break;
|
|
1248
|
-
this.tests.delete(id);
|
|
1249
|
-
states.push(state);
|
|
1053
|
+
this.tests.delete(id), states.push(state);
|
|
1250
1054
|
}
|
|
1251
|
-
this.ctx.logger.log(formatTests(states));
|
|
1252
|
-
this.renderer?.schedule();
|
|
1055
|
+
this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
|
|
1253
1056
|
}
|
|
1254
1057
|
createSummary() {
|
|
1255
1058
|
return [formatTests(Array.from(this.tests.values())), ""];
|
|
@@ -1259,16 +1062,13 @@ class DotReporter extends BaseReporter {
|
|
|
1259
1062
|
const pass = {
|
|
1260
1063
|
char: "·",
|
|
1261
1064
|
color: c.green
|
|
1262
|
-
}
|
|
1263
|
-
const fail = {
|
|
1065
|
+
}, fail = {
|
|
1264
1066
|
char: "x",
|
|
1265
1067
|
color: c.red
|
|
1266
|
-
}
|
|
1267
|
-
const pending = {
|
|
1068
|
+
}, pending = {
|
|
1268
1069
|
char: "*",
|
|
1269
1070
|
color: c.yellow
|
|
1270
|
-
}
|
|
1271
|
-
const skip = {
|
|
1071
|
+
}, skip = {
|
|
1272
1072
|
char: "-",
|
|
1273
1073
|
color: (char) => c.dim(c.gray(char))
|
|
1274
1074
|
};
|
|
@@ -1285,37 +1085,27 @@ function getIcon(state) {
|
|
|
1285
1085
|
* Sibling icons with same color are merged into a single c.color() call.
|
|
1286
1086
|
*/
|
|
1287
1087
|
function formatTests(states) {
|
|
1288
|
-
let currentIcon = pending;
|
|
1289
|
-
let count = 0;
|
|
1290
|
-
let output = "";
|
|
1088
|
+
let currentIcon = pending, count = 0, output = "";
|
|
1291
1089
|
for (const state of states) {
|
|
1292
1090
|
const icon = getIcon(state);
|
|
1293
1091
|
if (currentIcon === icon) {
|
|
1294
1092
|
count++;
|
|
1295
1093
|
continue;
|
|
1296
1094
|
}
|
|
1297
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1298
|
-
// Start tracking new group
|
|
1299
|
-
count = 1;
|
|
1300
|
-
currentIcon = icon;
|
|
1095
|
+
output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
|
|
1301
1096
|
}
|
|
1302
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1303
|
-
return output;
|
|
1097
|
+
return output += currentIcon.color(currentIcon.char.repeat(count)), output;
|
|
1304
1098
|
}
|
|
1305
1099
|
|
|
1306
1100
|
// use Logger with custom Console to capture entire error printing
|
|
1307
1101
|
function capturePrintError(error, ctx, options) {
|
|
1308
1102
|
let output = "";
|
|
1309
1103
|
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1310
|
-
output += String(chunk);
|
|
1311
|
-
|
|
1312
|
-
} });
|
|
1313
|
-
const console = new Console(writable);
|
|
1314
|
-
const logger = {
|
|
1104
|
+
output += String(chunk), callback();
|
|
1105
|
+
} }), console = new Console(writable), logger = {
|
|
1315
1106
|
error: console.error.bind(console),
|
|
1316
1107
|
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1317
|
-
}
|
|
1318
|
-
const result = printError(error, ctx, logger, {
|
|
1108
|
+
}, result = printError(error, ctx, logger, {
|
|
1319
1109
|
showCodeFrame: false,
|
|
1320
1110
|
...options
|
|
1321
1111
|
});
|
|
@@ -1333,11 +1123,13 @@ function printError(error, ctx, logger, options) {
|
|
|
1333
1123
|
screenshotPaths: options.screenshotPaths,
|
|
1334
1124
|
printProperties: options.verbose,
|
|
1335
1125
|
parseErrorStacktrace(error) {
|
|
1336
|
-
// browser stack trace needs to be processed differently,
|
|
1337
|
-
// so there is a separate method for that
|
|
1338
|
-
if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
|
|
1339
1126
|
// node.js stack trace already has correct source map locations
|
|
1340
|
-
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, {
|
|
1341
1133
|
frameFilter: project.config.onStackTrace,
|
|
1342
1134
|
ignoreStackEntries: options.fullStack ? [] : void 0
|
|
1343
1135
|
});
|
|
@@ -1345,15 +1137,14 @@ function printError(error, ctx, logger, options) {
|
|
|
1345
1137
|
});
|
|
1346
1138
|
}
|
|
1347
1139
|
function printErrorInner(error, project, options) {
|
|
1348
|
-
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1349
|
-
const logger = options.logger;
|
|
1140
|
+
const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
|
|
1350
1141
|
let e = error;
|
|
1351
1142
|
if (isPrimitive(e)) e = {
|
|
1352
1143
|
message: String(error).split(/\n/g)[0],
|
|
1353
1144
|
stack: String(error)
|
|
1354
1145
|
};
|
|
1355
1146
|
if (!e) {
|
|
1356
|
-
const error = new Error("unknown error");
|
|
1147
|
+
const error = /* @__PURE__ */ new Error("unknown error");
|
|
1357
1148
|
e = {
|
|
1358
1149
|
message: e ?? error.message,
|
|
1359
1150
|
stack: error.stack
|
|
@@ -1364,21 +1155,22 @@ function printErrorInner(error, project, options) {
|
|
|
1364
1155
|
printErrorMessage(e, logger);
|
|
1365
1156
|
return;
|
|
1366
1157
|
}
|
|
1367
|
-
const stacks = options.parseErrorStacktrace(e)
|
|
1368
|
-
|
|
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
|
|
1369
1160
|
try {
|
|
1370
|
-
|
|
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);
|
|
1371
1166
|
} catch {
|
|
1372
1167
|
return false;
|
|
1373
1168
|
}
|
|
1374
1169
|
});
|
|
1375
1170
|
if (type) printErrorType(type, project.vitest);
|
|
1376
|
-
printErrorMessage(e, logger)
|
|
1377
|
-
if (options.screenshotPaths?.length) {
|
|
1171
|
+
if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
|
|
1378
1172
|
const length = options.screenshotPaths.length;
|
|
1379
|
-
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1380
|
-
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1381
|
-
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();
|
|
1382
1174
|
}
|
|
1383
1175
|
if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
|
|
1384
1176
|
if ("__vitest_rollup_error__" in e) {
|
|
@@ -1403,25 +1195,19 @@ function printErrorInner(error, project, options) {
|
|
|
1403
1195
|
}
|
|
1404
1196
|
});
|
|
1405
1197
|
}
|
|
1406
|
-
const testPath = e.VITEST_TEST_PATH;
|
|
1407
|
-
const testName = e.VITEST_TEST_NAME;
|
|
1408
|
-
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;
|
|
1409
1199
|
// testName has testPath inside
|
|
1410
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.`));
|
|
1411
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:
|
|
1412
1202
|
- The error was thrown, while Vitest was running this test.
|
|
1413
1203
|
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
|
|
1414
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"));
|
|
1415
|
-
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
handleImportOutsideModuleError(e.stack || "", logger);
|
|
1424
|
-
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 };
|
|
1425
1211
|
}
|
|
1426
1212
|
function printErrorType(type, ctx) {
|
|
1427
1213
|
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
@@ -1438,6 +1224,7 @@ const skipErrorProperties = new Set([
|
|
|
1438
1224
|
"actual",
|
|
1439
1225
|
"expected",
|
|
1440
1226
|
"diffOptions",
|
|
1227
|
+
"runnerError",
|
|
1441
1228
|
"sourceURL",
|
|
1442
1229
|
"column",
|
|
1443
1230
|
"line",
|
|
@@ -1503,10 +1290,8 @@ function printErrorMessage(error, logger) {
|
|
|
1503
1290
|
}
|
|
1504
1291
|
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1505
1292
|
for (const frame of stack) {
|
|
1506
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
1507
|
-
|
|
1508
|
-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1509
|
-
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);
|
|
1510
1295
|
}
|
|
1511
1296
|
if (stack.length) logger.error();
|
|
1512
1297
|
if (hasProperties(errorProperties)) {
|
|
@@ -1521,26 +1306,19 @@ function hasProperties(obj) {
|
|
|
1521
1306
|
return false;
|
|
1522
1307
|
}
|
|
1523
1308
|
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1524
|
-
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1525
|
-
|
|
1526
|
-
const lines = source.split(lineSplitRE);
|
|
1527
|
-
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1528
|
-
let count = 0;
|
|
1529
|
-
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 = [];
|
|
1530
1311
|
const columns = process.stdout?.columns || 80;
|
|
1531
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
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")) {
|
|
1537
1317
|
// too long, maybe it's a minified file, skip for codeframe
|
|
1538
|
-
if (
|
|
1539
|
-
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent))
|
|
1540
|
-
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) {
|
|
1541
1320
|
// push underline
|
|
1542
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
1543
|
-
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);
|
|
1544
1322
|
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1545
1323
|
} else if (j > i) {
|
|
1546
1324
|
if (end > count) {
|
|
@@ -1550,8 +1328,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
|
1550
1328
|
count += lineLength + 1;
|
|
1551
1329
|
}
|
|
1552
1330
|
}
|
|
1553
|
-
break;
|
|
1554
1331
|
}
|
|
1332
|
+
break;
|
|
1555
1333
|
}
|
|
1556
1334
|
if (indent) res = res.map((line) => " ".repeat(indent) + line);
|
|
1557
1335
|
return res.join("\n");
|
|
@@ -1571,8 +1349,7 @@ class GithubActionsReporter {
|
|
|
1571
1349
|
}
|
|
1572
1350
|
onTestCaseAnnotate(testCase, annotation) {
|
|
1573
1351
|
if (!annotation.location) return;
|
|
1574
|
-
const type = getTitle(annotation.type)
|
|
1575
|
-
const formatted = formatMessage({
|
|
1352
|
+
const type = getTitle(annotation.type), formatted = formatMessage({
|
|
1576
1353
|
command: getType(annotation.type),
|
|
1577
1354
|
properties: {
|
|
1578
1355
|
file: annotation.location.file,
|
|
@@ -1584,17 +1361,15 @@ class GithubActionsReporter {
|
|
|
1584
1361
|
});
|
|
1585
1362
|
this.ctx.logger.log(`\n${formatted}`);
|
|
1586
1363
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
const projectErrors = new Array();
|
|
1364
|
+
onTestRunEnd(testModules, unhandledErrors) {
|
|
1365
|
+
const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
|
|
1590
1366
|
for (const error of errors) projectErrors.push({
|
|
1591
1367
|
project: this.ctx.getRootProject(),
|
|
1592
1368
|
title: "Unhandled error",
|
|
1593
1369
|
error
|
|
1594
1370
|
});
|
|
1595
1371
|
for (const file of files) {
|
|
1596
|
-
const tasks = getTasks(file);
|
|
1597
|
-
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
1372
|
+
const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
|
|
1598
1373
|
for (const task of tasks) {
|
|
1599
1374
|
if (task.result?.state !== "fail") continue;
|
|
1600
1375
|
const title = getFullName(task, " > ");
|
|
@@ -1612,8 +1387,7 @@ class GithubActionsReporter {
|
|
|
1612
1387
|
const result = capturePrintError(error, this.ctx, {
|
|
1613
1388
|
project,
|
|
1614
1389
|
task: file
|
|
1615
|
-
});
|
|
1616
|
-
const stack = result?.nearest;
|
|
1390
|
+
}), stack = result?.nearest;
|
|
1617
1391
|
if (!stack) continue;
|
|
1618
1392
|
const formatted = formatMessage({
|
|
1619
1393
|
command: "error",
|
|
@@ -1635,12 +1409,10 @@ const BUILT_IN_TYPES = [
|
|
|
1635
1409
|
"warning"
|
|
1636
1410
|
];
|
|
1637
1411
|
function getTitle(type) {
|
|
1638
|
-
|
|
1639
|
-
return type;
|
|
1412
|
+
return BUILT_IN_TYPES.includes(type) ? void 0 : type;
|
|
1640
1413
|
}
|
|
1641
1414
|
function getType(type) {
|
|
1642
|
-
|
|
1643
|
-
return "notice";
|
|
1415
|
+
return BUILT_IN_TYPES.includes(type) ? type : "notice";
|
|
1644
1416
|
}
|
|
1645
1417
|
function defaultOnWritePath(path) {
|
|
1646
1418
|
return path;
|
|
@@ -1650,12 +1422,9 @@ function defaultOnWritePath(path) {
|
|
|
1650
1422
|
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1651
1423
|
function formatMessage({ command, properties, message }) {
|
|
1652
1424
|
let result = `::${command}`;
|
|
1653
|
-
Object.entries(properties).forEach(([k, v], i) => {
|
|
1654
|
-
result += i === 0 ? " " : ","
|
|
1655
|
-
|
|
1656
|
-
});
|
|
1657
|
-
result += `::${escapeData(message)}`;
|
|
1658
|
-
return result;
|
|
1425
|
+
return Object.entries(properties).forEach(([k, v], i) => {
|
|
1426
|
+
result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
|
|
1427
|
+
}), result += `::${escapeData(message)}`, result;
|
|
1659
1428
|
}
|
|
1660
1429
|
function escapeData(s) {
|
|
1661
1430
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -1688,41 +1457,27 @@ class JsonReporter {
|
|
|
1688
1457
|
start = 0;
|
|
1689
1458
|
ctx;
|
|
1690
1459
|
options;
|
|
1460
|
+
coverageMap;
|
|
1691
1461
|
constructor(options) {
|
|
1692
1462
|
this.options = options;
|
|
1693
1463
|
}
|
|
1694
1464
|
onInit(ctx) {
|
|
1695
|
-
this.ctx = ctx;
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
const tests = getTests(files);
|
|
1702
|
-
const numTotalTests = tests.length;
|
|
1703
|
-
const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
|
|
1704
|
-
const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
|
|
1705
|
-
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
|
|
1706
|
-
const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
|
|
1707
|
-
const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
|
|
1708
|
-
const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
|
|
1709
|
-
const numTodoTests = tests.filter((t) => t.mode === "todo").length;
|
|
1710
|
-
const testResults = [];
|
|
1711
|
-
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;
|
|
1712
1472
|
for (const file of files) {
|
|
1713
1473
|
const tests = getTests([file]);
|
|
1714
1474
|
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
1715
1475
|
if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
|
|
1716
|
-
const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime)
|
|
1717
|
-
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) => {
|
|
1718
1477
|
const ancestorTitles = [];
|
|
1719
1478
|
let iter = t.suite;
|
|
1720
|
-
while (iter)
|
|
1721
|
-
|
|
1722
|
-
iter = iter.suite;
|
|
1723
|
-
}
|
|
1724
|
-
ancestorTitles.reverse();
|
|
1725
|
-
return {
|
|
1479
|
+
while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
|
|
1480
|
+
return ancestorTitles.reverse(), {
|
|
1726
1481
|
ancestorTitles,
|
|
1727
1482
|
fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
|
|
1728
1483
|
status: StatusMap[t.result?.state || t.mode] || "skipped",
|
|
@@ -1758,13 +1513,10 @@ class JsonReporter {
|
|
|
1758
1513
|
startTime: this.start,
|
|
1759
1514
|
success,
|
|
1760
1515
|
testResults,
|
|
1761
|
-
coverageMap
|
|
1516
|
+
coverageMap: this.coverageMap
|
|
1762
1517
|
};
|
|
1763
1518
|
await this.writeReport(JSON.stringify(result));
|
|
1764
1519
|
}
|
|
1765
|
-
async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
|
|
1766
|
-
await this.logTasks(files, coverageMap);
|
|
1767
|
-
}
|
|
1768
1520
|
/**
|
|
1769
1521
|
* Writes the report to an output file if specified in the config,
|
|
1770
1522
|
* or logs it to the console otherwise.
|
|
@@ -1773,11 +1525,9 @@ class JsonReporter {
|
|
|
1773
1525
|
async writeReport(report) {
|
|
1774
1526
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
1775
1527
|
if (outputFile) {
|
|
1776
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
1777
|
-
const outputDirectory = dirname(reportFile);
|
|
1528
|
+
const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
|
|
1778
1529
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1779
|
-
await promises.writeFile(reportFile, report, "utf-8");
|
|
1780
|
-
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}`);
|
|
1781
1531
|
} else this.ctx.logger.log(report);
|
|
1782
1532
|
}
|
|
1783
1533
|
}
|
|
@@ -1800,8 +1550,7 @@ class IndentedLogger {
|
|
|
1800
1550
|
|
|
1801
1551
|
function flattenTasks$1(task, baseName = "") {
|
|
1802
1552
|
const base = baseName ? `${baseName} > ` : "";
|
|
1803
|
-
|
|
1804
|
-
else return [{
|
|
1553
|
+
return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
|
|
1805
1554
|
...task,
|
|
1806
1555
|
name: `${base}${task.name}`
|
|
1807
1556
|
}];
|
|
@@ -1809,21 +1558,16 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1809
1558
|
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1810
1559
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1811
1560
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1812
|
-
value = String(value || "").replace(regex, "")
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
"g"
|
|
1819
|
-
/* eslint-enable */
|
|
1820
|
-
);
|
|
1821
|
-
value = value.replace(regex, "");
|
|
1822
|
-
}
|
|
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, "");
|
|
1823
1567
|
return value;
|
|
1824
1568
|
}
|
|
1825
1569
|
function escapeXML(value) {
|
|
1826
|
-
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);
|
|
1827
1571
|
}
|
|
1828
1572
|
function executionTime(durationMS) {
|
|
1829
1573
|
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
@@ -1844,8 +1588,7 @@ class JUnitReporter {
|
|
|
1844
1588
|
fileFd;
|
|
1845
1589
|
options;
|
|
1846
1590
|
constructor(options) {
|
|
1847
|
-
this.options = { ...options };
|
|
1848
|
-
this.options.includeConsoleOutput ??= true;
|
|
1591
|
+
this.options = { ...options }, this.options.includeConsoleOutput ??= true;
|
|
1849
1592
|
}
|
|
1850
1593
|
async onInit(ctx) {
|
|
1851
1594
|
this.ctx = ctx;
|
|
@@ -1855,14 +1598,12 @@ class JUnitReporter {
|
|
|
1855
1598
|
const outputDirectory = dirname(this.reportFile);
|
|
1856
1599
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
1857
1600
|
const fileFd = await promises.open(this.reportFile, "w+");
|
|
1858
|
-
this.fileFd = fileFd
|
|
1859
|
-
this.baseLog = async (text) => {
|
|
1601
|
+
this.fileFd = fileFd, this.baseLog = async (text) => {
|
|
1860
1602
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
1861
1603
|
await promises.writeFile(this.fileFd, `${text}\n`);
|
|
1862
1604
|
};
|
|
1863
1605
|
} else this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
1864
|
-
this._timeStart = /* @__PURE__ */ new Date();
|
|
1865
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
1606
|
+
this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
|
|
1866
1607
|
}
|
|
1867
1608
|
async writeElement(name, attrs, children) {
|
|
1868
1609
|
const pairs = [];
|
|
@@ -1871,18 +1612,12 @@ class JUnitReporter {
|
|
|
1871
1612
|
if (attr === void 0) continue;
|
|
1872
1613
|
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
1873
1614
|
}
|
|
1874
|
-
await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
|
|
1875
|
-
this.logger.indent();
|
|
1876
|
-
await children.call(this);
|
|
1877
|
-
this.logger.unindent();
|
|
1878
|
-
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}>`);
|
|
1879
1616
|
}
|
|
1880
1617
|
async writeLogs(task, type) {
|
|
1881
1618
|
if (task.logs == null || task.logs.length === 0) return;
|
|
1882
|
-
const logType = type === "err" ? "stderr" : "stdout";
|
|
1883
|
-
|
|
1884
|
-
if (logs.length === 0) return;
|
|
1885
|
-
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 () => {
|
|
1886
1621
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
1887
1622
|
});
|
|
1888
1623
|
}
|
|
@@ -1895,27 +1630,18 @@ class JUnitReporter {
|
|
|
1895
1630
|
};
|
|
1896
1631
|
if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
|
|
1897
1632
|
else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
|
|
1898
|
-
else if (typeof this.options.classname === "string") classname = this.options.classname;
|
|
1899
1633
|
await this.writeElement("testcase", {
|
|
1900
1634
|
classname,
|
|
1901
1635
|
file: this.options.addFileAttribute ? filename : void 0,
|
|
1902
1636
|
name: task.name,
|
|
1903
1637
|
time: getDuration(task)
|
|
1904
1638
|
}, async () => {
|
|
1905
|
-
if (this.options.includeConsoleOutput)
|
|
1906
|
-
await this.writeLogs(task, "out");
|
|
1907
|
-
await this.writeLogs(task, "err");
|
|
1908
|
-
}
|
|
1639
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
|
|
1909
1640
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
1910
1641
|
if (task.type === "test" && task.annotations.length) {
|
|
1911
|
-
await this.logger.log("<properties>");
|
|
1912
|
-
this.logger.
|
|
1913
|
-
|
|
1914
|
-
await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
|
|
1915
|
-
await this.logger.log("</property>");
|
|
1916
|
-
}
|
|
1917
|
-
this.logger.unindent();
|
|
1918
|
-
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>");
|
|
1919
1645
|
}
|
|
1920
1646
|
if (task.result?.state === "fail") {
|
|
1921
1647
|
const errors = task.result.errors || [];
|
|
@@ -1934,11 +1660,11 @@ class JUnitReporter {
|
|
|
1934
1660
|
});
|
|
1935
1661
|
}
|
|
1936
1662
|
}
|
|
1937
|
-
async
|
|
1663
|
+
async onTestRunEnd(testModules) {
|
|
1664
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1938
1665
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
1939
1666
|
const transformed = files.map((file) => {
|
|
1940
|
-
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task))
|
|
1941
|
-
const stats = tasks.reduce((stats, task) => {
|
|
1667
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
|
|
1942
1668
|
return {
|
|
1943
1669
|
passed: stats.passed + Number(task.result?.state === "pass"),
|
|
1944
1670
|
failures: stats.failures + Number(task.result?.state === "fail"),
|
|
@@ -1948,41 +1674,29 @@ class JUnitReporter {
|
|
|
1948
1674
|
passed: 0,
|
|
1949
1675
|
failures: 0,
|
|
1950
1676
|
skipped: 0
|
|
1951
|
-
});
|
|
1952
|
-
|
|
1953
|
-
const suites = getSuites(file);
|
|
1954
|
-
for (const suite of suites) if (suite.result?.errors) {
|
|
1955
|
-
tasks.push(suite);
|
|
1956
|
-
stats.failures += 1;
|
|
1957
|
-
}
|
|
1677
|
+
}), suites = getSuites(file);
|
|
1678
|
+
for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
|
|
1958
1679
|
// If there are no tests, but the file failed to load, we still want to report it as a failure
|
|
1959
|
-
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
annotations: []
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
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
|
+
});
|
|
1975
1693
|
return {
|
|
1976
1694
|
...file,
|
|
1977
1695
|
tasks,
|
|
1978
1696
|
stats
|
|
1979
1697
|
};
|
|
1980
|
-
})
|
|
1981
|
-
|
|
1982
|
-
stats.tests += file.tasks.length;
|
|
1983
|
-
stats.failures += file.stats.failures;
|
|
1984
|
-
stats.time += file.result?.duration || 0;
|
|
1985
|
-
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;
|
|
1986
1700
|
}, {
|
|
1987
1701
|
name: this.options.suiteName || "vitest tests",
|
|
1988
1702
|
tests: 0,
|
|
@@ -1990,7 +1704,7 @@ class JUnitReporter {
|
|
|
1990
1704
|
errors: 0,
|
|
1991
1705
|
time: 0
|
|
1992
1706
|
});
|
|
1993
|
-
await this.writeElement("testsuites", {
|
|
1707
|
+
if (await this.writeElement("testsuites", {
|
|
1994
1708
|
...stats,
|
|
1995
1709
|
time: executionTime(stats.time)
|
|
1996
1710
|
}, async () => {
|
|
@@ -2009,15 +1723,13 @@ class JUnitReporter {
|
|
|
2009
1723
|
await this.writeTasks(file.tasks, filename);
|
|
2010
1724
|
});
|
|
2011
1725
|
}
|
|
2012
|
-
});
|
|
2013
|
-
|
|
2014
|
-
await this.fileFd?.close();
|
|
2015
|
-
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;
|
|
2016
1728
|
}
|
|
2017
1729
|
}
|
|
2018
1730
|
|
|
2019
1731
|
function yamlString(str) {
|
|
2020
|
-
return `"${str.replace(/"/g, "\\\"")}"
|
|
1732
|
+
return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
|
|
2021
1733
|
}
|
|
2022
1734
|
function tapString(str) {
|
|
2023
1735
|
return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
|
|
@@ -2026,77 +1738,45 @@ class TapReporter {
|
|
|
2026
1738
|
ctx;
|
|
2027
1739
|
logger;
|
|
2028
1740
|
onInit(ctx) {
|
|
2029
|
-
this.ctx = ctx;
|
|
2030
|
-
this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
1741
|
+
this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
|
|
2031
1742
|
}
|
|
2032
1743
|
static getComment(task) {
|
|
2033
|
-
|
|
2034
|
-
else if (task.mode === "todo") return " # TODO";
|
|
2035
|
-
else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2036
|
-
else return "";
|
|
1744
|
+
return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
|
|
2037
1745
|
}
|
|
2038
1746
|
logErrorDetails(error, stack) {
|
|
2039
1747
|
const errorName = error.name || "Unknown Error";
|
|
2040
|
-
this.logger.log(`name: ${yamlString(String(errorName))}`)
|
|
2041
|
-
this.logger.log(`message: ${yamlString(String(error.message))}`);
|
|
2042
|
-
if (stack)
|
|
1748
|
+
if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
|
|
2043
1749
|
// For compatibility with tap-mocha-reporter
|
|
2044
1750
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2045
1751
|
}
|
|
2046
1752
|
logTasks(tasks) {
|
|
2047
1753
|
this.logger.log(`1..${tasks.length}`);
|
|
2048
1754
|
for (const [i, task] of tasks.entries()) {
|
|
2049
|
-
const id = i + 1;
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
if (task.type === "suite" && task.tasks.length > 0) {
|
|
2053
|
-
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
|
|
2054
|
-
this.logger.indent();
|
|
2055
|
-
this.logTasks(task.tasks);
|
|
2056
|
-
this.logger.unindent();
|
|
2057
|
-
this.logger.log("}");
|
|
2058
|
-
} 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 {
|
|
2059
1758
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
2060
1759
|
const project = this.ctx.getProjectByName(task.file.projectName || "");
|
|
2061
|
-
if (task.type === "test" && task.annotations) {
|
|
2062
|
-
this.logger.
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
});
|
|
2066
|
-
this.logger.unindent();
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
this.logger.indent();
|
|
2070
|
-
task.result.errors.forEach((error) => {
|
|
2071
|
-
const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace });
|
|
2072
|
-
const stack = stacks[0];
|
|
2073
|
-
this.logger.log("---");
|
|
2074
|
-
this.logger.log("error:");
|
|
2075
|
-
this.logger.indent();
|
|
2076
|
-
this.logErrorDetails(error);
|
|
2077
|
-
this.logger.unindent();
|
|
2078
|
-
if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2079
|
-
if (error.showDiff) {
|
|
2080
|
-
this.logger.log(`actual: ${yamlString(error.actual)}`);
|
|
2081
|
-
this.logger.log(`expected: ${yamlString(error.expected)}`);
|
|
2082
|
-
}
|
|
2083
|
-
});
|
|
2084
|
-
this.logger.log("...");
|
|
2085
|
-
this.logger.unindent();
|
|
2086
|
-
}
|
|
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();
|
|
2087
1768
|
}
|
|
2088
1769
|
}
|
|
2089
1770
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
this.logTasks(files);
|
|
1771
|
+
onTestRunEnd(testModules) {
|
|
1772
|
+
const files = testModules.map((testModule) => testModule.task);
|
|
1773
|
+
this.logger.log("TAP version 13"), this.logTasks(files);
|
|
2093
1774
|
}
|
|
2094
1775
|
}
|
|
2095
1776
|
|
|
2096
1777
|
function flattenTasks(task, baseName = "") {
|
|
2097
1778
|
const base = baseName ? `${baseName} > ` : "";
|
|
2098
|
-
|
|
2099
|
-
else return [{
|
|
1779
|
+
return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
|
|
2100
1780
|
...task,
|
|
2101
1781
|
name: `${base}${task.name}`
|
|
2102
1782
|
}];
|
|
@@ -2105,9 +1785,9 @@ class TapFlatReporter extends TapReporter {
|
|
|
2105
1785
|
onInit(ctx) {
|
|
2106
1786
|
super.onInit(ctx);
|
|
2107
1787
|
}
|
|
2108
|
-
|
|
1788
|
+
onTestRunEnd(testModules) {
|
|
2109
1789
|
this.ctx.logger.log("TAP version 13");
|
|
2110
|
-
const flatTasks =
|
|
1790
|
+
const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
|
|
2111
1791
|
this.logTasks(flatTasks);
|
|
2112
1792
|
}
|
|
2113
1793
|
}
|
|
@@ -2123,31 +1803,22 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2123
1803
|
if (this.isTTY) return super.printTestModule(module);
|
|
2124
1804
|
}
|
|
2125
1805
|
onTestCaseResult(test) {
|
|
2126
|
-
super.onTestCaseResult(test);
|
|
2127
1806
|
// don't print tests in TTY as they go, only print them
|
|
2128
1807
|
// in the CLI when they finish
|
|
2129
|
-
if (this.isTTY) return;
|
|
1808
|
+
if (super.onTestCaseResult(test), this.isTTY) return;
|
|
2130
1809
|
const testResult = test.result();
|
|
2131
1810
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
|
|
2132
1811
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2133
1812
|
if (test.project.name) title += formatProjectName(test.project);
|
|
2134
|
-
title += getFullName(test.task, c.dim(" > "));
|
|
2135
|
-
title += this.getDurationPrefix(test.task);
|
|
1813
|
+
title += getFullName(test.task, c.dim(" > ")), title += this.getDurationPrefix(test.task);
|
|
2136
1814
|
const diagnostic = test.diagnostic();
|
|
2137
1815
|
if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
|
|
2138
1816
|
if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
|
|
2139
|
-
this.log(title);
|
|
2140
|
-
if (
|
|
2141
|
-
if (test.annotations().length) {
|
|
2142
|
-
this.log();
|
|
2143
|
-
this.printAnnotations(test, "log", 3);
|
|
2144
|
-
this.log();
|
|
2145
|
-
}
|
|
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();
|
|
2146
1819
|
}
|
|
2147
1820
|
printTestSuite(testSuite) {
|
|
2148
|
-
const indentation = " ".repeat(getIndentation(testSuite.task));
|
|
2149
|
-
const tests = Array.from(testSuite.children.allTests());
|
|
2150
|
-
const state = getStateSymbol(testSuite.task);
|
|
1821
|
+
const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = getStateSymbol(testSuite.task);
|
|
2151
1822
|
this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
|
|
2152
1823
|
}
|
|
2153
1824
|
getTestName(test) {
|
|
@@ -2162,13 +1833,11 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2162
1833
|
}
|
|
2163
1834
|
}
|
|
2164
1835
|
function getIndentation(suite, level = 1) {
|
|
2165
|
-
|
|
2166
|
-
return level;
|
|
1836
|
+
return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
|
|
2167
1837
|
}
|
|
2168
1838
|
|
|
2169
1839
|
const ReportersMap = {
|
|
2170
1840
|
"default": DefaultReporter,
|
|
2171
|
-
"basic": BasicReporter,
|
|
2172
1841
|
"blob": BlobReporter,
|
|
2173
1842
|
"verbose": VerboseReporter,
|
|
2174
1843
|
"dot": DotReporter,
|
|
@@ -2180,4 +1849,4 @@ const ReportersMap = {
|
|
|
2180
1849
|
"github-actions": GithubActionsReporter
|
|
2181
1850
|
};
|
|
2182
1851
|
|
|
2183
|
-
export {
|
|
1852
|
+
export { BlobReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, printError as d, errorBanner as e, formatProjectName as f, getStateSymbol as g, divider as h, generateCodeFrame as i, parse as p, readBlobs as r, stringify as s, truncateString as t, utils as u, withLabel as w };
|