vitest 4.1.5 → 5.0.0-beta.2

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.
Files changed (80) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/browser.d.ts +9 -9
  3. package/dist/browser.js +4 -4
  4. package/dist/chunks/{base.RR7zL1h0.js → base.Opc_YHkk.js} +10 -11
  5. package/dist/chunks/browser.d.BUhkKcDl.d.ts +899 -0
  6. package/dist/chunks/{cac.DJJmV0dT.js → cac.8N4bOkkB.js} +23 -11
  7. package/dist/chunks/{cli-api.Cjt90eJu.js → cli-api.B0RFke2g.js} +5799 -353
  8. package/dist/chunks/{config.d.A1h_Y6Jt.d.ts → config.d.D91DHYaD.d.ts} +11 -3
  9. package/dist/chunks/{console.3WNpx0tS.js → console.B3IRP8fX.js} +3 -1
  10. package/dist/chunks/{constants.CPYnjOGj.js → constants.-juJ8b_4.js} +1 -1
  11. package/dist/chunks/{coverage.d.BZtK59WP.d.ts → coverage.d.g2xbl2sP.d.ts} +4 -0
  12. package/dist/chunks/{creator.DgVhQm5q.js → creator.BqL2U_x4.js} +1 -1
  13. package/dist/chunks/{defaults.9aQKnqFk.js → defaults.szbHWQun.js} +4 -2
  14. package/dist/chunks/environment.d-DOJxxZV9.d.DOJxxZV9.d.ts +17 -0
  15. package/dist/chunks/general.d.DFAHgpC2.d.ts +247 -0
  16. package/dist/chunks/{global.d.DVsSRdQ5.d.ts → global.d.DhbKSQoV.d.ts} +4 -5
  17. package/dist/chunks/{globals.Dj1TGiMC.js → globals.EHmmu0nC.js} +15 -14
  18. package/dist/chunks/{index.DXx9Dtk7.js → index.CViWo__T.js} +5 -5
  19. package/dist/chunks/{startVitestModuleRunner.bRl2_oI_.js → index.CbgUM9E5.js} +731 -5
  20. package/dist/chunks/{test.DNmyFkvJ.js → index.D_7-4CaB.js} +2659 -14
  21. package/dist/chunks/{init-forks.UV3ZQGQH.js → init-forks.DMge3WTt.js} +1 -1
  22. package/dist/chunks/{init-threads.D3eCsY76.js → init-threads.eIoyCTon.js} +1 -1
  23. package/dist/chunks/{init.D98-gwRW.js → init.BVd7SaCA.js} +3 -5
  24. package/dist/chunks/{nativeModuleMocker.BRN2oBJd.js → nativeModuleMocker.DKpFw0pk.js} +3 -2
  25. package/dist/chunks/{index.BCY_7LL2.js → nativeModuleRunner.BOeMnHl4.js} +43 -12
  26. package/dist/chunks/node.CwFbQqI1.js +47 -0
  27. package/dist/chunks/{reporters.d.CEnv6XRv.d.ts → plugin.d.cIKZEZ16.d.ts} +306 -19
  28. package/dist/chunks/plugins.DrsmdUE2.js +37 -0
  29. package/dist/chunks/{rpc.MzXet3jl.js → rpc.DFRWVnRh.js} +16 -1
  30. package/dist/chunks/{rpc.d.B_8sPU0w.d.ts → rpc.d.7JZuxZ8u.d.ts} +19 -3
  31. package/dist/chunks/{setup-common.DYx3LtFI.js → setup-common.Hpq30zVk.js} +7 -3
  32. package/dist/chunks/{utils.BS4fH3nR.js → utils.DKODp04v.js} +3 -4
  33. package/dist/chunks/{vm.DVLYObm9.js → vm.2okbRRME.js} +6 -6
  34. package/dist/chunks/{worker.d.ZpHpO4yb.d.ts → worker.d.Bu1kXGw4.d.ts} +3 -3
  35. package/dist/cli.js +2 -2
  36. package/dist/config.cjs +4 -2
  37. package/dist/config.d.ts +21 -18
  38. package/dist/config.js +2 -2
  39. package/dist/index.d.ts +84 -22
  40. package/dist/index.js +15 -13
  41. package/dist/module-evaluator.d.ts +5 -3
  42. package/dist/module-evaluator.js +1 -1
  43. package/dist/node.d.ts +114 -19
  44. package/dist/node.js +21 -26
  45. package/dist/runtime.d.ts +40 -4
  46. package/dist/runtime.js +5 -6
  47. package/dist/{chunks/traces.DT5aQ62U.js → traces.js} +1 -1
  48. package/dist/worker.d.ts +5 -5
  49. package/dist/worker.js +21 -23
  50. package/dist/workers/forks.js +21 -23
  51. package/dist/workers/runVmTests.js +17 -16
  52. package/dist/workers/threads.js +21 -23
  53. package/dist/workers/vmForks.js +7 -9
  54. package/dist/workers/vmThreads.js +7 -9
  55. package/package.json +21 -38
  56. package/dist/chunks/benchmark.CX_oY03V.js +0 -40
  57. package/dist/chunks/benchmark.d.DAaHLpsq.d.ts +0 -24
  58. package/dist/chunks/browser.d.BcoexmFG.d.ts +0 -62
  59. package/dist/chunks/coverage.DM_a_rWm.js +0 -1087
  60. package/dist/chunks/evaluatedModules.Dg1zASAC.js +0 -17
  61. package/dist/chunks/index.DC7d2Pf8.js +0 -729
  62. package/dist/chunks/index.DdgEv5B1.js +0 -42
  63. package/dist/chunks/index.UpGiHP7g.js +0 -4255
  64. package/dist/chunks/nativeModuleRunner.BIakptoF.js +0 -36
  65. package/dist/chunks/node.COQbm6gK.js +0 -14
  66. package/dist/chunks/plugin.d.BM2TCi12.d.ts +0 -38
  67. package/dist/chunks/suite.d.udJtyAgw.d.ts +0 -10
  68. package/dist/chunks/traces.d.D2T_R8rx.d.ts +0 -60
  69. package/dist/coverage.d.ts +0 -123
  70. package/dist/coverage.js +0 -27
  71. package/dist/environments.d.ts +0 -22
  72. package/dist/environments.js +0 -5
  73. package/dist/reporters.d.ts +0 -27
  74. package/dist/reporters.js +0 -26
  75. package/dist/runners.d.ts +0 -70
  76. package/dist/runners.js +0 -19
  77. package/dist/snapshot.d.ts +0 -9
  78. package/dist/snapshot.js +0 -6
  79. package/dist/suite.d.ts +0 -5
  80. package/dist/suite.js +0 -8
@@ -1,4255 +0,0 @@
1
- import fs, { existsSync, readFileSync, writeFileSync, promises } from 'node:fs';
2
- import { getTests, generateHash, createTaskName, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, getTestName, hasFailed, getSuites, getTasks, getFullName } from '@vitest/runner/utils';
3
- import * as pathe from 'pathe';
4
- import { relative, basename, resolve as resolve$1, join, normalize, dirname } from 'pathe';
5
- import c from 'tinyrainbow';
6
- import { t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatTimeString, a as taskFail, s as separator, b as F_CHECK, c as F_DOWN_RIGHT, g as formatProjectName, h as getStateSymbol, w as withLabel, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.BS4fH3nR.js';
7
- import { stripVTControlCharacters } from 'node:util';
8
- import { isPrimitive, toArray, deepMerge, notNullish } from '@vitest/utils/helpers';
9
- import { readdir, stat, readFile, mkdir, writeFile } from 'node:fs/promises';
10
- import { performance as performance$1 } from 'node:perf_hooks';
11
- import { defaultStackIgnorePatterns, parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
12
- import { i as isTTY } from './env.D4Lgay0q.js';
13
- import { Console } from 'node:console';
14
- import { Writable } from 'node:stream';
15
- import { inspect } from '@vitest/utils/display';
16
- import nodeos__default, { hostname } from 'node:os';
17
- import { x } from 'tinyexec';
18
- import { distDir } from '../path.js';
19
- import { parseAstAsync } from 'vite';
20
- import { positionToOffset, lineSplitRE } from '@vitest/utils/offset';
21
- import { createRequire } from 'node:module';
22
-
23
- function groupBy(collection, iteratee) {
24
- return collection.reduce((acc, item) => {
25
- const key = iteratee(item);
26
- acc[key] ||= [];
27
- acc[key].push(item);
28
- return acc;
29
- }, {});
30
- }
31
- function stdout() {
32
- // @ts-expect-error Node.js maps process.stdout to console._stdout
33
- // eslint-disable-next-line no-console
34
- return console._stdout || process.stdout;
35
- }
36
- function escapeRegExp(s) {
37
- // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
38
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
- }
40
- function wildcardPatternToRegExp(pattern) {
41
- const negated = pattern[0] === "!";
42
- if (negated) pattern = pattern.slice(1);
43
- let regexp = `${pattern.split("*").map(escapeRegExp).join(".*")}$`;
44
- if (negated) regexp = `(?!${regexp})`;
45
- return new RegExp(`^${regexp}`, "i");
46
- }
47
- function createIndexLocationsMap(source) {
48
- const map = /* @__PURE__ */ new Map();
49
- let index = 0;
50
- let line = 1;
51
- let column = 1;
52
- for (const char of source) {
53
- map.set(index++, {
54
- line,
55
- column
56
- });
57
- if (char === "\n" || char === "\r\n") {
58
- line++;
59
- column = 0;
60
- } else column++;
61
- }
62
- return map;
63
- }
64
- function createLocationsIndexMap(source) {
65
- const map = /* @__PURE__ */ new Map();
66
- let index = 0;
67
- let line = 1;
68
- let column = 1;
69
- for (const char of source) {
70
- map.set(`${line}:${column}`, index++);
71
- if (char === "\n" || char === "\r\n") {
72
- line++;
73
- column = 0;
74
- } else column++;
75
- }
76
- return map;
77
- }
78
-
79
- function hasFailedSnapshot(suite) {
80
- return getTests(suite).some((s) => {
81
- return s.result?.errors?.some((e) => typeof e?.message === "string" && e.message.match(/Snapshot .* mismatched/));
82
- });
83
- }
84
- function convertTasksToEvents(file, onTask) {
85
- const packs = [];
86
- const events = [];
87
- function visit(suite) {
88
- onTask?.(suite);
89
- packs.push([
90
- suite.id,
91
- suite.result,
92
- suite.meta
93
- ]);
94
- events.push([
95
- suite.id,
96
- "suite-prepare",
97
- void 0
98
- ]);
99
- suite.tasks.forEach((task) => {
100
- if (task.type === "suite") visit(task);
101
- else {
102
- onTask?.(task);
103
- if (suite.mode !== "skip" && suite.mode !== "todo") {
104
- packs.push([
105
- task.id,
106
- task.result,
107
- task.meta
108
- ]);
109
- events.push([
110
- task.id,
111
- "test-prepare",
112
- void 0
113
- ]);
114
- task.annotations.forEach((annotation) => {
115
- events.push([
116
- task.id,
117
- "test-annotation",
118
- { annotation }
119
- ]);
120
- });
121
- task.artifacts.forEach((artifact) => {
122
- events.push([
123
- task.id,
124
- "test-artifact",
125
- { artifact }
126
- ]);
127
- });
128
- events.push([
129
- task.id,
130
- "test-finished",
131
- void 0
132
- ]);
133
- }
134
- }
135
- });
136
- events.push([
137
- suite.id,
138
- "suite-finished",
139
- void 0
140
- ]);
141
- }
142
- visit(file);
143
- return {
144
- packs,
145
- events
146
- };
147
- }
148
-
149
- // src/vlq.ts
150
- var comma = ",".charCodeAt(0);
151
- var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
152
- var intToChar = new Uint8Array(64);
153
- var charToInt = new Uint8Array(128);
154
- for (let i = 0; i < chars.length; i++) {
155
- const c = chars.charCodeAt(i);
156
- intToChar[i] = c;
157
- charToInt[c] = i;
158
- }
159
- function decodeInteger(reader, relative) {
160
- let value = 0;
161
- let shift = 0;
162
- let integer = 0;
163
- do {
164
- const c = reader.next();
165
- integer = charToInt[c];
166
- value |= (integer & 31) << shift;
167
- shift += 5;
168
- } while (integer & 32);
169
- const shouldNegate = value & 1;
170
- value >>>= 1;
171
- if (shouldNegate) {
172
- value = -2147483648 | -value;
173
- }
174
- return relative + value;
175
- }
176
- function hasMoreVlq(reader, max) {
177
- if (reader.pos >= max) return false;
178
- return reader.peek() !== comma;
179
- }
180
- var StringReader = class {
181
- constructor(buffer) {
182
- this.pos = 0;
183
- this.buffer = buffer;
184
- }
185
- next() {
186
- return this.buffer.charCodeAt(this.pos++);
187
- }
188
- peek() {
189
- return this.buffer.charCodeAt(this.pos);
190
- }
191
- indexOf(char) {
192
- const { buffer, pos } = this;
193
- const idx = buffer.indexOf(char, pos);
194
- return idx === -1 ? buffer.length : idx;
195
- }
196
- };
197
-
198
- // src/sourcemap-codec.ts
199
- function decode(mappings) {
200
- const { length } = mappings;
201
- const reader = new StringReader(mappings);
202
- const decoded = [];
203
- let genColumn = 0;
204
- let sourcesIndex = 0;
205
- let sourceLine = 0;
206
- let sourceColumn = 0;
207
- let namesIndex = 0;
208
- do {
209
- const semi = reader.indexOf(";");
210
- const line = [];
211
- let sorted = true;
212
- let lastCol = 0;
213
- genColumn = 0;
214
- while (reader.pos < semi) {
215
- let seg;
216
- genColumn = decodeInteger(reader, genColumn);
217
- if (genColumn < lastCol) sorted = false;
218
- lastCol = genColumn;
219
- if (hasMoreVlq(reader, semi)) {
220
- sourcesIndex = decodeInteger(reader, sourcesIndex);
221
- sourceLine = decodeInteger(reader, sourceLine);
222
- sourceColumn = decodeInteger(reader, sourceColumn);
223
- if (hasMoreVlq(reader, semi)) {
224
- namesIndex = decodeInteger(reader, namesIndex);
225
- seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex];
226
- } else {
227
- seg = [genColumn, sourcesIndex, sourceLine, sourceColumn];
228
- }
229
- } else {
230
- seg = [genColumn];
231
- }
232
- line.push(seg);
233
- reader.pos++;
234
- }
235
- if (!sorted) sort(line);
236
- decoded.push(line);
237
- reader.pos = semi + 1;
238
- } while (reader.pos <= length);
239
- return decoded;
240
- }
241
- function sort(line) {
242
- line.sort(sortComparator$1);
243
- }
244
- function sortComparator$1(a, b) {
245
- return a[0] - b[0];
246
- }
247
-
248
- // Matches the scheme of a URL, eg "http://"
249
- const schemeRegex = /^[\w+.-]+:\/\//;
250
- /**
251
- * Matches the parts of a URL:
252
- * 1. Scheme, including ":", guaranteed.
253
- * 2. User/password, including "@", optional.
254
- * 3. Host, guaranteed.
255
- * 4. Port, including ":", optional.
256
- * 5. Path, including "/", optional.
257
- * 6. Query, including "?", optional.
258
- * 7. Hash, including "#", optional.
259
- */
260
- const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/;
261
- /**
262
- * File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start
263
- * with a leading `/`, they can have a domain (but only if they don't start with a Windows drive).
264
- *
265
- * 1. Host, optional.
266
- * 2. Path, which may include "/", guaranteed.
267
- * 3. Query, including "?", optional.
268
- * 4. Hash, including "#", optional.
269
- */
270
- const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;
271
- function isAbsoluteUrl(input) {
272
- return schemeRegex.test(input);
273
- }
274
- function isSchemeRelativeUrl(input) {
275
- return input.startsWith('//');
276
- }
277
- function isAbsolutePath(input) {
278
- return input.startsWith('/');
279
- }
280
- function isFileUrl(input) {
281
- return input.startsWith('file:');
282
- }
283
- function isRelative(input) {
284
- return /^[.?#]/.test(input);
285
- }
286
- function parseAbsoluteUrl(input) {
287
- const match = urlRegex.exec(input);
288
- return makeUrl(match[1], match[2] || '', match[3], match[4] || '', match[5] || '/', match[6] || '', match[7] || '');
289
- }
290
- function parseFileUrl(input) {
291
- const match = fileRegex.exec(input);
292
- const path = match[2];
293
- return makeUrl('file:', '', match[1] || '', '', isAbsolutePath(path) ? path : '/' + path, match[3] || '', match[4] || '');
294
- }
295
- function makeUrl(scheme, user, host, port, path, query, hash) {
296
- return {
297
- scheme,
298
- user,
299
- host,
300
- port,
301
- path,
302
- query,
303
- hash,
304
- type: 7 /* Absolute */,
305
- };
306
- }
307
- function parseUrl(input) {
308
- if (isSchemeRelativeUrl(input)) {
309
- const url = parseAbsoluteUrl('http:' + input);
310
- url.scheme = '';
311
- url.type = 6 /* SchemeRelative */;
312
- return url;
313
- }
314
- if (isAbsolutePath(input)) {
315
- const url = parseAbsoluteUrl('http://foo.com' + input);
316
- url.scheme = '';
317
- url.host = '';
318
- url.type = 5 /* AbsolutePath */;
319
- return url;
320
- }
321
- if (isFileUrl(input))
322
- return parseFileUrl(input);
323
- if (isAbsoluteUrl(input))
324
- return parseAbsoluteUrl(input);
325
- const url = parseAbsoluteUrl('http://foo.com/' + input);
326
- url.scheme = '';
327
- url.host = '';
328
- url.type = input
329
- ? input.startsWith('?')
330
- ? 3 /* Query */
331
- : input.startsWith('#')
332
- ? 2 /* Hash */
333
- : 4 /* RelativePath */
334
- : 1 /* Empty */;
335
- return url;
336
- }
337
- function stripPathFilename(path) {
338
- // If a path ends with a parent directory "..", then it's a relative path with excess parent
339
- // paths. It's not a file, so we can't strip it.
340
- if (path.endsWith('/..'))
341
- return path;
342
- const index = path.lastIndexOf('/');
343
- return path.slice(0, index + 1);
344
- }
345
- function mergePaths(url, base) {
346
- normalizePath(base, base.type);
347
- // If the path is just a "/", then it was an empty path to begin with (remember, we're a relative
348
- // path).
349
- if (url.path === '/') {
350
- url.path = base.path;
351
- }
352
- else {
353
- // Resolution happens relative to the base path's directory, not the file.
354
- url.path = stripPathFilename(base.path) + url.path;
355
- }
356
- }
357
- /**
358
- * The path can have empty directories "//", unneeded parents "foo/..", or current directory
359
- * "foo/.". We need to normalize to a standard representation.
360
- */
361
- function normalizePath(url, type) {
362
- const rel = type <= 4 /* RelativePath */;
363
- const pieces = url.path.split('/');
364
- // We need to preserve the first piece always, so that we output a leading slash. The item at
365
- // pieces[0] is an empty string.
366
- let pointer = 1;
367
- // Positive is the number of real directories we've output, used for popping a parent directory.
368
- // Eg, "foo/bar/.." will have a positive 2, and we can decrement to be left with just "foo".
369
- let positive = 0;
370
- // We need to keep a trailing slash if we encounter an empty directory (eg, splitting "foo/" will
371
- // generate `["foo", ""]` pieces). And, if we pop a parent directory. But once we encounter a
372
- // real directory, we won't need to append, unless the other conditions happen again.
373
- let addTrailingSlash = false;
374
- for (let i = 1; i < pieces.length; i++) {
375
- const piece = pieces[i];
376
- // An empty directory, could be a trailing slash, or just a double "//" in the path.
377
- if (!piece) {
378
- addTrailingSlash = true;
379
- continue;
380
- }
381
- // If we encounter a real directory, then we don't need to append anymore.
382
- addTrailingSlash = false;
383
- // A current directory, which we can always drop.
384
- if (piece === '.')
385
- continue;
386
- // A parent directory, we need to see if there are any real directories we can pop. Else, we
387
- // have an excess of parents, and we'll need to keep the "..".
388
- if (piece === '..') {
389
- if (positive) {
390
- addTrailingSlash = true;
391
- positive--;
392
- pointer--;
393
- }
394
- else if (rel) {
395
- // If we're in a relativePath, then we need to keep the excess parents. Else, in an absolute
396
- // URL, protocol relative URL, or an absolute path, we don't need to keep excess.
397
- pieces[pointer++] = piece;
398
- }
399
- continue;
400
- }
401
- // We've encountered a real directory. Move it to the next insertion pointer, which accounts for
402
- // any popped or dropped directories.
403
- pieces[pointer++] = piece;
404
- positive++;
405
- }
406
- let path = '';
407
- for (let i = 1; i < pointer; i++) {
408
- path += '/' + pieces[i];
409
- }
410
- if (!path || (addTrailingSlash && !path.endsWith('/..'))) {
411
- path += '/';
412
- }
413
- url.path = path;
414
- }
415
- /**
416
- * Attempts to resolve `input` URL/path relative to `base`.
417
- */
418
- function resolve(input, base) {
419
- if (!input && !base)
420
- return '';
421
- const url = parseUrl(input);
422
- let inputType = url.type;
423
- if (base && inputType !== 7 /* Absolute */) {
424
- const baseUrl = parseUrl(base);
425
- const baseType = baseUrl.type;
426
- switch (inputType) {
427
- case 1 /* Empty */:
428
- url.hash = baseUrl.hash;
429
- // fall through
430
- case 2 /* Hash */:
431
- url.query = baseUrl.query;
432
- // fall through
433
- case 3 /* Query */:
434
- case 4 /* RelativePath */:
435
- mergePaths(url, baseUrl);
436
- // fall through
437
- case 5 /* AbsolutePath */:
438
- // The host, user, and port are joined, you can't copy one without the others.
439
- url.user = baseUrl.user;
440
- url.host = baseUrl.host;
441
- url.port = baseUrl.port;
442
- // fall through
443
- case 6 /* SchemeRelative */:
444
- // The input doesn't have a schema at least, so we need to copy at least that over.
445
- url.scheme = baseUrl.scheme;
446
- }
447
- if (baseType > inputType)
448
- inputType = baseType;
449
- }
450
- normalizePath(url, inputType);
451
- const queryHash = url.query + url.hash;
452
- switch (inputType) {
453
- // This is impossible, because of the empty checks at the start of the function.
454
- // case UrlType.Empty:
455
- case 2 /* Hash */:
456
- case 3 /* Query */:
457
- return queryHash;
458
- case 4 /* RelativePath */: {
459
- // The first char is always a "/", and we need it to be relative.
460
- const path = url.path.slice(1);
461
- if (!path)
462
- return queryHash || '.';
463
- if (isRelative(base || input) && !isRelative(path)) {
464
- // If base started with a leading ".", or there is no base and input started with a ".",
465
- // then we need to ensure that the relative path starts with a ".". We don't know if
466
- // relative starts with a "..", though, so check before prepending.
467
- return './' + path + queryHash;
468
- }
469
- return path + queryHash;
470
- }
471
- case 5 /* AbsolutePath */:
472
- return url.path + queryHash;
473
- default:
474
- return url.scheme + '//' + url.user + url.host + url.port + url.path + queryHash;
475
- }
476
- }
477
-
478
- // src/trace-mapping.ts
479
-
480
- // src/strip-filename.ts
481
- function stripFilename(path) {
482
- if (!path) return "";
483
- const index = path.lastIndexOf("/");
484
- return path.slice(0, index + 1);
485
- }
486
-
487
- // src/resolve.ts
488
- function resolver$1(mapUrl, sourceRoot) {
489
- const from = stripFilename(mapUrl);
490
- const prefix = sourceRoot ? sourceRoot + "/" : "";
491
- return (source) => resolve(prefix + (source || ""), from);
492
- }
493
-
494
- // src/sourcemap-segment.ts
495
- var COLUMN = 0;
496
- var SOURCES_INDEX = 1;
497
- var SOURCE_LINE = 2;
498
- var SOURCE_COLUMN = 3;
499
- var NAMES_INDEX = 4;
500
- var REV_GENERATED_LINE = 1;
501
- var REV_GENERATED_COLUMN = 2;
502
-
503
- // src/sort.ts
504
- function maybeSort(mappings, owned) {
505
- const unsortedIndex = nextUnsortedSegmentLine(mappings, 0);
506
- if (unsortedIndex === mappings.length) return mappings;
507
- if (!owned) mappings = mappings.slice();
508
- for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) {
509
- mappings[i] = sortSegments(mappings[i], owned);
510
- }
511
- return mappings;
512
- }
513
- function nextUnsortedSegmentLine(mappings, start) {
514
- for (let i = start; i < mappings.length; i++) {
515
- if (!isSorted(mappings[i])) return i;
516
- }
517
- return mappings.length;
518
- }
519
- function isSorted(line) {
520
- for (let j = 1; j < line.length; j++) {
521
- if (line[j][COLUMN] < line[j - 1][COLUMN]) {
522
- return false;
523
- }
524
- }
525
- return true;
526
- }
527
- function sortSegments(line, owned) {
528
- if (!owned) line = line.slice();
529
- return line.sort(sortComparator);
530
- }
531
- function sortComparator(a, b) {
532
- return a[COLUMN] - b[COLUMN];
533
- }
534
-
535
- // src/by-source.ts
536
- function buildBySources(decoded, memos) {
537
- const sources = memos.map(() => []);
538
- for (let i = 0; i < decoded.length; i++) {
539
- const line = decoded[i];
540
- for (let j = 0; j < line.length; j++) {
541
- const seg = line[j];
542
- if (seg.length === 1) continue;
543
- const sourceIndex2 = seg[SOURCES_INDEX];
544
- const sourceLine = seg[SOURCE_LINE];
545
- const sourceColumn = seg[SOURCE_COLUMN];
546
- const source = sources[sourceIndex2];
547
- const segs = source[sourceLine] || (source[sourceLine] = []);
548
- segs.push([sourceColumn, i, seg[COLUMN]]);
549
- }
550
- }
551
- for (let i = 0; i < sources.length; i++) {
552
- const source = sources[i];
553
- for (let j = 0; j < source.length; j++) {
554
- const line = source[j];
555
- if (line) line.sort(sortComparator);
556
- }
557
- }
558
- return sources;
559
- }
560
-
561
- // src/binary-search.ts
562
- var found = false;
563
- function binarySearch(haystack, needle, low, high) {
564
- while (low <= high) {
565
- const mid = low + (high - low >> 1);
566
- const cmp = haystack[mid][COLUMN] - needle;
567
- if (cmp === 0) {
568
- found = true;
569
- return mid;
570
- }
571
- if (cmp < 0) {
572
- low = mid + 1;
573
- } else {
574
- high = mid - 1;
575
- }
576
- }
577
- found = false;
578
- return low - 1;
579
- }
580
- function upperBound(haystack, needle, index) {
581
- for (let i = index + 1; i < haystack.length; index = i++) {
582
- if (haystack[i][COLUMN] !== needle) break;
583
- }
584
- return index;
585
- }
586
- function lowerBound(haystack, needle, index) {
587
- for (let i = index - 1; i >= 0; index = i--) {
588
- if (haystack[i][COLUMN] !== needle) break;
589
- }
590
- return index;
591
- }
592
- function memoizedState() {
593
- return {
594
- lastKey: -1,
595
- lastNeedle: -1,
596
- lastIndex: -1
597
- };
598
- }
599
- function memoizedBinarySearch(haystack, needle, state, key) {
600
- const { lastKey, lastNeedle, lastIndex } = state;
601
- let low = 0;
602
- let high = haystack.length - 1;
603
- if (key === lastKey) {
604
- if (needle === lastNeedle) {
605
- found = lastIndex !== -1 && haystack[lastIndex][COLUMN] === needle;
606
- return lastIndex;
607
- }
608
- if (needle >= lastNeedle) {
609
- low = lastIndex === -1 ? 0 : lastIndex;
610
- } else {
611
- high = lastIndex;
612
- }
613
- }
614
- state.lastKey = key;
615
- state.lastNeedle = needle;
616
- return state.lastIndex = binarySearch(haystack, needle, low, high);
617
- }
618
-
619
- // src/types.ts
620
- function parse$1(map) {
621
- return typeof map === "string" ? JSON.parse(map) : map;
622
- }
623
-
624
- // src/trace-mapping.ts
625
- var LINE_GTR_ZERO = "`line` must be greater than 0 (lines start at line 1)";
626
- var COL_GTR_EQ_ZERO = "`column` must be greater than or equal to 0 (columns start at column 0)";
627
- var LEAST_UPPER_BOUND = -1;
628
- var GREATEST_LOWER_BOUND = 1;
629
- var TraceMap = class {
630
- constructor(map, mapUrl) {
631
- const isString = typeof map === "string";
632
- if (!isString && map._decodedMemo) return map;
633
- const parsed = parse$1(map);
634
- const { version, file, names, sourceRoot, sources, sourcesContent } = parsed;
635
- this.version = version;
636
- this.file = file;
637
- this.names = names || [];
638
- this.sourceRoot = sourceRoot;
639
- this.sources = sources;
640
- this.sourcesContent = sourcesContent;
641
- this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || void 0;
642
- const resolve = resolver$1(mapUrl, sourceRoot);
643
- this.resolvedSources = sources.map(resolve);
644
- const { mappings } = parsed;
645
- if (typeof mappings === "string") {
646
- this._encoded = mappings;
647
- this._decoded = void 0;
648
- } else if (Array.isArray(mappings)) {
649
- this._encoded = void 0;
650
- this._decoded = maybeSort(mappings, isString);
651
- } else if (parsed.sections) {
652
- throw new Error(`TraceMap passed sectioned source map, please use FlattenMap export instead`);
653
- } else {
654
- throw new Error(`invalid source map: ${JSON.stringify(parsed)}`);
655
- }
656
- this._decodedMemo = memoizedState();
657
- this._bySources = void 0;
658
- this._bySourceMemos = void 0;
659
- }
660
- };
661
- function cast(map) {
662
- return map;
663
- }
664
- function decodedMappings(map) {
665
- var _a;
666
- return (_a = cast(map))._decoded || (_a._decoded = decode(cast(map)._encoded));
667
- }
668
- function originalPositionFor(map, needle) {
669
- let { line, column, bias } = needle;
670
- line--;
671
- if (line < 0) throw new Error(LINE_GTR_ZERO);
672
- if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
673
- const decoded = decodedMappings(map);
674
- if (line >= decoded.length) return OMapping(null, null, null, null);
675
- const segments = decoded[line];
676
- const index = traceSegmentInternal(
677
- segments,
678
- cast(map)._decodedMemo,
679
- line,
680
- column,
681
- bias || GREATEST_LOWER_BOUND
682
- );
683
- if (index === -1) return OMapping(null, null, null, null);
684
- const segment = segments[index];
685
- if (segment.length === 1) return OMapping(null, null, null, null);
686
- const { names, resolvedSources } = map;
687
- return OMapping(
688
- resolvedSources[segment[SOURCES_INDEX]],
689
- segment[SOURCE_LINE] + 1,
690
- segment[SOURCE_COLUMN],
691
- segment.length === 5 ? names[segment[NAMES_INDEX]] : null
692
- );
693
- }
694
- function generatedPositionFor(map, needle) {
695
- const { source, line, column, bias } = needle;
696
- return generatedPosition(map, source, line, column, bias || GREATEST_LOWER_BOUND, false);
697
- }
698
- function eachMapping(map, cb) {
699
- const decoded = decodedMappings(map);
700
- const { names, resolvedSources } = map;
701
- for (let i = 0; i < decoded.length; i++) {
702
- const line = decoded[i];
703
- for (let j = 0; j < line.length; j++) {
704
- const seg = line[j];
705
- const generatedLine = i + 1;
706
- const generatedColumn = seg[0];
707
- let source = null;
708
- let originalLine = null;
709
- let originalColumn = null;
710
- let name = null;
711
- if (seg.length !== 1) {
712
- source = resolvedSources[seg[1]];
713
- originalLine = seg[2] + 1;
714
- originalColumn = seg[3];
715
- }
716
- if (seg.length === 5) name = names[seg[4]];
717
- cb({
718
- generatedLine,
719
- generatedColumn,
720
- source,
721
- originalLine,
722
- originalColumn,
723
- name
724
- });
725
- }
726
- }
727
- }
728
- function OMapping(source, line, column, name) {
729
- return { source, line, column, name };
730
- }
731
- function GMapping(line, column) {
732
- return { line, column };
733
- }
734
- function traceSegmentInternal(segments, memo, line, column, bias) {
735
- let index = memoizedBinarySearch(segments, column, memo, line);
736
- if (found) {
737
- index = (bias === LEAST_UPPER_BOUND ? upperBound : lowerBound)(segments, column, index);
738
- } else if (bias === LEAST_UPPER_BOUND) index++;
739
- if (index === -1 || index === segments.length) return -1;
740
- return index;
741
- }
742
- function generatedPosition(map, source, line, column, bias, all) {
743
- var _a, _b;
744
- line--;
745
- if (line < 0) throw new Error(LINE_GTR_ZERO);
746
- if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
747
- const { sources, resolvedSources } = map;
748
- let sourceIndex2 = sources.indexOf(source);
749
- if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
750
- if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
751
- const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState));
752
- const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos));
753
- const segments = generated[sourceIndex2][line];
754
- if (segments == null) return all ? [] : GMapping(null, null);
755
- const memo = bySourceMemos[sourceIndex2];
756
- const index = traceSegmentInternal(segments, memo, line, column, bias);
757
- if (index === -1) return GMapping(null, null);
758
- const segment = segments[index];
759
- return GMapping(segment[REV_GENERATED_LINE] + 1, segment[REV_GENERATED_COLUMN]);
760
- }
761
-
762
- // AST walker module for ESTree compatible trees
763
-
764
-
765
- // An ancestor walk keeps an array of ancestor nodes (including the
766
- // current node) and passes them to the callback as third parameter
767
- // (and also as state parameter when no other state is present).
768
- function ancestor(node, visitors, baseVisitor, state, override) {
769
- var ancestors = [];
770
- if (!baseVisitor) { baseVisitor = base
771
- ; }(function c(node, st, override) {
772
- var type = override || node.type;
773
- var isNew = node !== ancestors[ancestors.length - 1];
774
- if (isNew) { ancestors.push(node); }
775
- visitNode(baseVisitor, type, node, st, c);
776
- if (visitors[type]) { visitors[type](node, st || ancestors, ancestors); }
777
- if (isNew) { ancestors.pop(); }
778
- })(node, state, override);
779
- }
780
-
781
- function skipThrough(node, st, c) { c(node, st); }
782
- function ignore$1(_node, _st, _c) {}
783
-
784
- function visitNode(baseVisitor, type, node, st, c) {
785
- if (baseVisitor[type] == null) { throw new Error(("No walker function defined for node type " + type)) }
786
- baseVisitor[type](node, st, c);
787
- }
788
-
789
- // Node walkers.
790
-
791
- var base = {};
792
-
793
- base.Program = base.BlockStatement = base.StaticBlock = function (node, st, c) {
794
- for (var i = 0, list = node.body; i < list.length; i += 1)
795
- {
796
- var stmt = list[i];
797
-
798
- c(stmt, st, "Statement");
799
- }
800
- };
801
- base.Statement = skipThrough;
802
- base.EmptyStatement = ignore$1;
803
- base.ExpressionStatement = base.ParenthesizedExpression = base.ChainExpression =
804
- function (node, st, c) { return c(node.expression, st, "Expression"); };
805
- base.IfStatement = function (node, st, c) {
806
- c(node.test, st, "Expression");
807
- c(node.consequent, st, "Statement");
808
- if (node.alternate) { c(node.alternate, st, "Statement"); }
809
- };
810
- base.LabeledStatement = function (node, st, c) { return c(node.body, st, "Statement"); };
811
- base.BreakStatement = base.ContinueStatement = ignore$1;
812
- base.WithStatement = function (node, st, c) {
813
- c(node.object, st, "Expression");
814
- c(node.body, st, "Statement");
815
- };
816
- base.SwitchStatement = function (node, st, c) {
817
- c(node.discriminant, st, "Expression");
818
- for (var i = 0, list = node.cases; i < list.length; i += 1) {
819
- var cs = list[i];
820
-
821
- c(cs, st);
822
- }
823
- };
824
- base.SwitchCase = function (node, st, c) {
825
- if (node.test) { c(node.test, st, "Expression"); }
826
- for (var i = 0, list = node.consequent; i < list.length; i += 1)
827
- {
828
- var cons = list[i];
829
-
830
- c(cons, st, "Statement");
831
- }
832
- };
833
- base.ReturnStatement = base.YieldExpression = base.AwaitExpression = function (node, st, c) {
834
- if (node.argument) { c(node.argument, st, "Expression"); }
835
- };
836
- base.ThrowStatement = base.SpreadElement =
837
- function (node, st, c) { return c(node.argument, st, "Expression"); };
838
- base.TryStatement = function (node, st, c) {
839
- c(node.block, st, "Statement");
840
- if (node.handler) { c(node.handler, st); }
841
- if (node.finalizer) { c(node.finalizer, st, "Statement"); }
842
- };
843
- base.CatchClause = function (node, st, c) {
844
- if (node.param) { c(node.param, st, "Pattern"); }
845
- c(node.body, st, "Statement");
846
- };
847
- base.WhileStatement = base.DoWhileStatement = function (node, st, c) {
848
- c(node.test, st, "Expression");
849
- c(node.body, st, "Statement");
850
- };
851
- base.ForStatement = function (node, st, c) {
852
- if (node.init) { c(node.init, st, "ForInit"); }
853
- if (node.test) { c(node.test, st, "Expression"); }
854
- if (node.update) { c(node.update, st, "Expression"); }
855
- c(node.body, st, "Statement");
856
- };
857
- base.ForInStatement = base.ForOfStatement = function (node, st, c) {
858
- c(node.left, st, "ForInit");
859
- c(node.right, st, "Expression");
860
- c(node.body, st, "Statement");
861
- };
862
- base.ForInit = function (node, st, c) {
863
- if (node.type === "VariableDeclaration") { c(node, st); }
864
- else { c(node, st, "Expression"); }
865
- };
866
- base.DebuggerStatement = ignore$1;
867
-
868
- base.FunctionDeclaration = function (node, st, c) { return c(node, st, "Function"); };
869
- base.VariableDeclaration = function (node, st, c) {
870
- for (var i = 0, list = node.declarations; i < list.length; i += 1)
871
- {
872
- var decl = list[i];
873
-
874
- c(decl, st);
875
- }
876
- };
877
- base.VariableDeclarator = function (node, st, c) {
878
- c(node.id, st, "Pattern");
879
- if (node.init) { c(node.init, st, "Expression"); }
880
- };
881
-
882
- base.Function = function (node, st, c) {
883
- if (node.id) { c(node.id, st, "Pattern"); }
884
- for (var i = 0, list = node.params; i < list.length; i += 1)
885
- {
886
- var param = list[i];
887
-
888
- c(param, st, "Pattern");
889
- }
890
- c(node.body, st, node.expression ? "Expression" : "Statement");
891
- };
892
-
893
- base.Pattern = function (node, st, c) {
894
- if (node.type === "Identifier")
895
- { c(node, st, "VariablePattern"); }
896
- else if (node.type === "MemberExpression")
897
- { c(node, st, "MemberPattern"); }
898
- else
899
- { c(node, st); }
900
- };
901
- base.VariablePattern = ignore$1;
902
- base.MemberPattern = skipThrough;
903
- base.RestElement = function (node, st, c) { return c(node.argument, st, "Pattern"); };
904
- base.ArrayPattern = function (node, st, c) {
905
- for (var i = 0, list = node.elements; i < list.length; i += 1) {
906
- var elt = list[i];
907
-
908
- if (elt) { c(elt, st, "Pattern"); }
909
- }
910
- };
911
- base.ObjectPattern = function (node, st, c) {
912
- for (var i = 0, list = node.properties; i < list.length; i += 1) {
913
- var prop = list[i];
914
-
915
- if (prop.type === "Property") {
916
- if (prop.computed) { c(prop.key, st, "Expression"); }
917
- c(prop.value, st, "Pattern");
918
- } else if (prop.type === "RestElement") {
919
- c(prop.argument, st, "Pattern");
920
- }
921
- }
922
- };
923
-
924
- base.Expression = skipThrough;
925
- base.ThisExpression = base.Super = base.MetaProperty = ignore$1;
926
- base.ArrayExpression = function (node, st, c) {
927
- for (var i = 0, list = node.elements; i < list.length; i += 1) {
928
- var elt = list[i];
929
-
930
- if (elt) { c(elt, st, "Expression"); }
931
- }
932
- };
933
- base.ObjectExpression = function (node, st, c) {
934
- for (var i = 0, list = node.properties; i < list.length; i += 1)
935
- {
936
- var prop = list[i];
937
-
938
- c(prop, st);
939
- }
940
- };
941
- base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration;
942
- base.SequenceExpression = function (node, st, c) {
943
- for (var i = 0, list = node.expressions; i < list.length; i += 1)
944
- {
945
- var expr = list[i];
946
-
947
- c(expr, st, "Expression");
948
- }
949
- };
950
- base.TemplateLiteral = function (node, st, c) {
951
- for (var i = 0, list = node.quasis; i < list.length; i += 1)
952
- {
953
- var quasi = list[i];
954
-
955
- c(quasi, st);
956
- }
957
-
958
- for (var i$1 = 0, list$1 = node.expressions; i$1 < list$1.length; i$1 += 1)
959
- {
960
- var expr = list$1[i$1];
961
-
962
- c(expr, st, "Expression");
963
- }
964
- };
965
- base.TemplateElement = ignore$1;
966
- base.UnaryExpression = base.UpdateExpression = function (node, st, c) {
967
- c(node.argument, st, "Expression");
968
- };
969
- base.BinaryExpression = base.LogicalExpression = function (node, st, c) {
970
- c(node.left, st, "Expression");
971
- c(node.right, st, "Expression");
972
- };
973
- base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) {
974
- c(node.left, st, "Pattern");
975
- c(node.right, st, "Expression");
976
- };
977
- base.ConditionalExpression = function (node, st, c) {
978
- c(node.test, st, "Expression");
979
- c(node.consequent, st, "Expression");
980
- c(node.alternate, st, "Expression");
981
- };
982
- base.NewExpression = base.CallExpression = function (node, st, c) {
983
- c(node.callee, st, "Expression");
984
- if (node.arguments)
985
- { for (var i = 0, list = node.arguments; i < list.length; i += 1)
986
- {
987
- var arg = list[i];
988
-
989
- c(arg, st, "Expression");
990
- } }
991
- };
992
- base.MemberExpression = function (node, st, c) {
993
- c(node.object, st, "Expression");
994
- if (node.computed) { c(node.property, st, "Expression"); }
995
- };
996
- base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) {
997
- if (node.declaration)
998
- { c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression"); }
999
- if (node.source) { c(node.source, st, "Expression"); }
1000
- if (node.attributes)
1001
- { for (var i = 0, list = node.attributes; i < list.length; i += 1)
1002
- {
1003
- var attr = list[i];
1004
-
1005
- c(attr, st);
1006
- } }
1007
- };
1008
- base.ExportAllDeclaration = function (node, st, c) {
1009
- if (node.exported)
1010
- { c(node.exported, st); }
1011
- c(node.source, st, "Expression");
1012
- if (node.attributes)
1013
- { for (var i = 0, list = node.attributes; i < list.length; i += 1)
1014
- {
1015
- var attr = list[i];
1016
-
1017
- c(attr, st);
1018
- } }
1019
- };
1020
- base.ImportAttribute = function (node, st, c) {
1021
- c(node.value, st, "Expression");
1022
- };
1023
- base.ImportDeclaration = function (node, st, c) {
1024
- for (var i = 0, list = node.specifiers; i < list.length; i += 1)
1025
- {
1026
- var spec = list[i];
1027
-
1028
- c(spec, st);
1029
- }
1030
- c(node.source, st, "Expression");
1031
- if (node.attributes)
1032
- { for (var i$1 = 0, list$1 = node.attributes; i$1 < list$1.length; i$1 += 1)
1033
- {
1034
- var attr = list$1[i$1];
1035
-
1036
- c(attr, st);
1037
- } }
1038
- };
1039
- base.ImportExpression = function (node, st, c) {
1040
- c(node.source, st, "Expression");
1041
- if (node.options) { c(node.options, st, "Expression"); }
1042
- };
1043
- base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore$1;
1044
-
1045
- base.TaggedTemplateExpression = function (node, st, c) {
1046
- c(node.tag, st, "Expression");
1047
- c(node.quasi, st, "Expression");
1048
- };
1049
- base.ClassDeclaration = base.ClassExpression = function (node, st, c) { return c(node, st, "Class"); };
1050
- base.Class = function (node, st, c) {
1051
- if (node.id) { c(node.id, st, "Pattern"); }
1052
- if (node.superClass) { c(node.superClass, st, "Expression"); }
1053
- c(node.body, st);
1054
- };
1055
- base.ClassBody = function (node, st, c) {
1056
- for (var i = 0, list = node.body; i < list.length; i += 1)
1057
- {
1058
- var elt = list[i];
1059
-
1060
- c(elt, st);
1061
- }
1062
- };
1063
- base.MethodDefinition = base.PropertyDefinition = base.Property = function (node, st, c) {
1064
- if (node.computed) { c(node.key, st, "Expression"); }
1065
- if (node.value) { c(node.value, st, "Expression"); }
1066
- };
1067
-
1068
- /// <reference types="../types/index.d.ts" />
1069
-
1070
- // (c) 2020-present Andrea Giammarchi
1071
-
1072
- const {parse: $parse, stringify: $stringify} = JSON;
1073
- const {keys} = Object;
1074
-
1075
- const Primitive = String; // it could be Number
1076
- const primitive = 'string'; // it could be 'number'
1077
-
1078
- const ignore = {};
1079
- const object = 'object';
1080
-
1081
- const noop = (_, value) => value;
1082
-
1083
- const primitives = value => (
1084
- value instanceof Primitive ? Primitive(value) : value
1085
- );
1086
-
1087
- const Primitives = (_, value) => (
1088
- typeof value === primitive ? new Primitive(value) : value
1089
- );
1090
-
1091
- const resolver = (input, lazy, parsed, $) => output => {
1092
- for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
1093
- const k = ke[y];
1094
- const value = output[k];
1095
- if (value instanceof Primitive) {
1096
- const tmp = input[+value];
1097
- if (typeof tmp === object && !parsed.has(tmp)) {
1098
- parsed.add(tmp);
1099
- output[k] = ignore;
1100
- lazy.push({ o: output, k, r: tmp });
1101
- }
1102
- else
1103
- output[k] = $.call(output, k, tmp);
1104
- }
1105
- else if (output[k] !== ignore)
1106
- output[k] = $.call(output, k, value);
1107
- }
1108
- return output;
1109
- };
1110
-
1111
- const set = (known, input, value) => {
1112
- const index = Primitive(input.push(value) - 1);
1113
- known.set(value, index);
1114
- return index;
1115
- };
1116
-
1117
- /**
1118
- * Converts a specialized flatted string into a JS value.
1119
- * @param {string} text
1120
- * @param {(this: any, key: string, value: any) => any} [reviver]
1121
- * @returns {any}
1122
- */
1123
- const parse = (text, reviver) => {
1124
- const input = $parse(text, Primitives).map(primitives);
1125
- const $ = reviver || noop;
1126
-
1127
- let value = input[0];
1128
-
1129
- if (typeof value === object && value) {
1130
- const lazy = [];
1131
- const revive = resolver(input, lazy, new Set, $);
1132
- value = revive(value);
1133
-
1134
- let i = 0;
1135
- while (i < lazy.length) {
1136
- // it could be a lazy.shift() but that's costly
1137
- const {o, k, r} = lazy[i++];
1138
- o[k] = $.call(o, k, revive(r));
1139
- }
1140
- }
1141
-
1142
- return $.call({'': value}, '', value);
1143
- };
1144
-
1145
- /**
1146
- * Converts a JS value into a specialized flatted string.
1147
- * @param {any} value
1148
- * @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
1149
- * @param {string | number | undefined} [space]
1150
- * @returns {string}
1151
- */
1152
- const stringify = (value, replacer, space) => {
1153
- const $ = replacer && typeof replacer === object ?
1154
- (k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
1155
- (replacer || noop);
1156
- const known = new Map;
1157
- const input = [];
1158
- const output = [];
1159
- let i = +set(known, input, $.call({'': value}, '', value));
1160
- let firstRun = !i;
1161
- while (i < input.length) {
1162
- firstRun = true;
1163
- output[i] = $stringify(input[i++], replace, space);
1164
- }
1165
- return '[' + output.join(',') + ']';
1166
- function replace(key, value) {
1167
- if (firstRun) {
1168
- firstRun = !firstRun;
1169
- return value;
1170
- }
1171
- const after = $.call(this, key, value);
1172
- switch (typeof after) {
1173
- case object:
1174
- if (after === null) return after;
1175
- case primitive:
1176
- return known.get(after) || set(known, input, after);
1177
- }
1178
- return after;
1179
- }
1180
- };
1181
-
1182
- async function collectTests(ctx, filepath) {
1183
- const request = await ctx.vite.environments.ssr.transformRequest(filepath);
1184
- if (!request) return null;
1185
- const ast = await parseAstAsync(request.code);
1186
- const testFilepath = relative(ctx.config.root, filepath);
1187
- const projectName = ctx.name;
1188
- const file = {
1189
- filepath,
1190
- type: "suite",
1191
- id: generateHash(`${testFilepath}${projectName ? `${projectName}:__typecheck__` : "__typecheck__"}`),
1192
- name: testFilepath,
1193
- fullName: testFilepath,
1194
- mode: "run",
1195
- tasks: [],
1196
- start: ast.start,
1197
- end: ast.end,
1198
- projectName,
1199
- meta: { typecheck: true },
1200
- file: null
1201
- };
1202
- file.file = file;
1203
- const definitions = [];
1204
- const getName = (callee) => {
1205
- if (!callee) return null;
1206
- if (callee.type === "Identifier") return callee.name;
1207
- if (callee.type === "CallExpression") return getName(callee.callee);
1208
- if (callee.type === "TaggedTemplateExpression") return getName(callee.tag);
1209
- if (callee.type === "MemberExpression") {
1210
- if (callee.object?.type === "Identifier" && [
1211
- "it",
1212
- "test",
1213
- "describe",
1214
- "suite"
1215
- ].includes(callee.object.name)) return callee.object?.name;
1216
- // direct call as `__vite_ssr_exports_0__.test()`
1217
- if (callee.object?.name?.startsWith("__vite_ssr_")) return getName(callee.property);
1218
- // call as `__vite_ssr__.test.skip()`
1219
- return getName(callee.object?.property);
1220
- }
1221
- // unwrap (0, ...)
1222
- if (callee.type === "SequenceExpression" && callee.expressions.length === 2) {
1223
- const [e0, e1] = callee.expressions;
1224
- if (e0.type === "Literal" && e0.value === 0) return getName(e1);
1225
- }
1226
- return null;
1227
- };
1228
- ancestor(ast, { CallExpression(node) {
1229
- const { callee } = node;
1230
- const name = getName(callee);
1231
- if (!name) return;
1232
- if (![
1233
- "it",
1234
- "test",
1235
- "describe",
1236
- "suite"
1237
- ].includes(name)) return;
1238
- const property = callee?.property?.name;
1239
- let mode = !property || property === name ? "run" : property;
1240
- // they will be picked up in the next iteration
1241
- if ([
1242
- "each",
1243
- "for",
1244
- "skipIf",
1245
- "runIf"
1246
- ].includes(mode)) return;
1247
- let start;
1248
- const end = node.end;
1249
- // .each
1250
- if (callee.type === "CallExpression") start = callee.end;
1251
- else if (callee.type === "TaggedTemplateExpression") start = callee.end + 1;
1252
- else start = node.start;
1253
- const { arguments: [messageNode] } = node;
1254
- const message = messageNode?.type === "Literal" || messageNode?.type === "TemplateLiteral" ? request.code.slice(messageNode.start + 1, messageNode.end - 1) : request.code.slice(messageNode.start, messageNode.end);
1255
- // cannot statically analyze, so we always skip it
1256
- if (mode === "skipIf" || mode === "runIf") mode = "skip";
1257
- definitions.push({
1258
- start,
1259
- end,
1260
- name: message,
1261
- type: name === "it" || name === "test" ? "test" : "suite",
1262
- mode,
1263
- task: null
1264
- });
1265
- } });
1266
- let lastSuite = file;
1267
- const updateLatestSuite = (index) => {
1268
- while (lastSuite.suite && lastSuite.end < index) lastSuite = lastSuite.suite;
1269
- return lastSuite;
1270
- };
1271
- definitions.sort((a, b) => a.start - b.start).forEach((definition) => {
1272
- const latestSuite = updateLatestSuite(definition.start);
1273
- let mode = definition.mode;
1274
- if (latestSuite.mode !== "run")
1275
- // inherit suite mode, if it's set
1276
- mode = latestSuite.mode;
1277
- if (definition.type === "suite") {
1278
- const task = {
1279
- type: definition.type,
1280
- id: "",
1281
- suite: latestSuite,
1282
- file,
1283
- tasks: [],
1284
- mode,
1285
- name: definition.name,
1286
- fullName: createTaskName([lastSuite.fullName, definition.name]),
1287
- fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
1288
- end: definition.end,
1289
- start: definition.start,
1290
- meta: { typecheck: true }
1291
- };
1292
- definition.task = task;
1293
- latestSuite.tasks.push(task);
1294
- lastSuite = task;
1295
- return;
1296
- }
1297
- const task = {
1298
- type: definition.type,
1299
- id: "",
1300
- suite: latestSuite,
1301
- file,
1302
- mode,
1303
- timeout: 0,
1304
- context: {},
1305
- name: definition.name,
1306
- fullName: createTaskName([lastSuite.fullName, definition.name]),
1307
- fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
1308
- end: definition.end,
1309
- start: definition.start,
1310
- annotations: [],
1311
- artifacts: [],
1312
- meta: { typecheck: true }
1313
- };
1314
- definition.task = task;
1315
- latestSuite.tasks.push(task);
1316
- });
1317
- calculateSuiteHash(file);
1318
- const hasOnly = someTasksAreOnly(file);
1319
- interpretTaskModes(file, ctx.config.testNamePattern, void 0, void 0, void 0, hasOnly, false, ctx.config.allowOnly);
1320
- return {
1321
- file,
1322
- parsed: request.code,
1323
- filepath,
1324
- map: request.map,
1325
- definitions
1326
- };
1327
- }
1328
-
1329
- const newLineRegExp = /\r?\n/;
1330
- const errCodeRegExp = /error TS(?<errCode>\d+)/;
1331
- async function makeTscErrorInfo(errInfo) {
1332
- const [errFilePathPos = "", ...errMsgRawArr] = errInfo.split(":");
1333
- if (!errFilePathPos || errMsgRawArr.length === 0 || errMsgRawArr.join("").length === 0) return ["unknown filepath", null];
1334
- const errMsgRaw = errMsgRawArr.join("").trim();
1335
- // get filePath, line, col
1336
- const [errFilePath, errPos] = errFilePathPos.slice(0, -1).split("(");
1337
- if (!errFilePath || !errPos) return ["unknown filepath", null];
1338
- const [errLine, errCol] = errPos.split(",");
1339
- if (!errLine || !errCol) return [errFilePath, null];
1340
- // get errCode, errMsg
1341
- const execArr = errCodeRegExp.exec(errMsgRaw);
1342
- if (!execArr) return [errFilePath, null];
1343
- const errCodeStr = execArr.groups?.errCode ?? "";
1344
- if (!errCodeStr) return [errFilePath, null];
1345
- const line = Number(errLine);
1346
- const col = Number(errCol);
1347
- const errCode = Number(errCodeStr);
1348
- return [errFilePath, {
1349
- filePath: errFilePath,
1350
- errCode,
1351
- line,
1352
- column: col,
1353
- errMsg: errMsgRaw.slice(`error TS${errCode} `.length)
1354
- }];
1355
- }
1356
- async function getRawErrsMapFromTsCompile(tscErrorStdout) {
1357
- const rawErrsMap = /* @__PURE__ */ new Map();
1358
- (await Promise.all(tscErrorStdout.split(newLineRegExp).reduce((prev, next) => {
1359
- if (!next) return prev;
1360
- else if (next[0] !== " ") prev.push(next);
1361
- else prev[prev.length - 1] += `\n${next}`;
1362
- return prev;
1363
- }, []).map((errInfoLine) => makeTscErrorInfo(errInfoLine)))).forEach(([errFilePath, errInfo]) => {
1364
- if (!errInfo) return;
1365
- if (!rawErrsMap.has(errFilePath)) rawErrsMap.set(errFilePath, [errInfo]);
1366
- else rawErrsMap.get(errFilePath)?.push(errInfo);
1367
- });
1368
- return rawErrsMap;
1369
- }
1370
-
1371
- class TypeCheckError extends Error {
1372
- name = "TypeCheckError";
1373
- constructor(message, stacks) {
1374
- super(message);
1375
- this.message = message;
1376
- this.stacks = stacks;
1377
- }
1378
- }
1379
- class Typechecker {
1380
- _onParseStart;
1381
- _onParseEnd;
1382
- _onWatcherRerun;
1383
- _result = {
1384
- files: [],
1385
- sourceErrors: [],
1386
- time: 0
1387
- };
1388
- _startTime = 0;
1389
- _output = "";
1390
- _tests = {};
1391
- process;
1392
- files = [];
1393
- constructor(project) {
1394
- this.project = project;
1395
- }
1396
- setFiles(files) {
1397
- this.files = files;
1398
- }
1399
- onParseStart(fn) {
1400
- this._onParseStart = fn;
1401
- }
1402
- onParseEnd(fn) {
1403
- this._onParseEnd = fn;
1404
- }
1405
- onWatcherRerun(fn) {
1406
- this._onWatcherRerun = fn;
1407
- }
1408
- async collectFileTests(filepath) {
1409
- return collectTests(this.project, filepath);
1410
- }
1411
- getFiles() {
1412
- return this.files;
1413
- }
1414
- async collectTests() {
1415
- const tests = (await Promise.all(this.getFiles().map((filepath) => this.collectFileTests(filepath)))).reduce((acc, data) => {
1416
- if (!data) return acc;
1417
- acc[data.filepath] = data;
1418
- return acc;
1419
- }, {});
1420
- this._tests = tests;
1421
- return tests;
1422
- }
1423
- markPassed(file) {
1424
- if (!file.result?.state) file.result = { state: "pass" };
1425
- const markTasks = (tasks) => {
1426
- for (const task of tasks) {
1427
- if ("tasks" in task) markTasks(task.tasks);
1428
- if (!task.result?.state && (task.mode === "run" || task.mode === "queued")) task.result = { state: "pass" };
1429
- }
1430
- };
1431
- markTasks(file.tasks);
1432
- }
1433
- async prepareResults(output) {
1434
- // Detect if tsc output is help text instead of error output
1435
- // This happens when tsconfig.json is missing and tsc can't find any config
1436
- if (output.includes("The TypeScript Compiler - Version") || output.includes("COMMON COMMANDS")) {
1437
- const { typecheck } = this.project.config;
1438
- const msg = `TypeScript compiler returned help text instead of type checking results.
1439
- This usually means the tsconfig file was not found.
1440
-
1441
- Possible solutions:
1442
- 1. Ensure '${typecheck.tsconfig || "tsconfig.json"}' exists in your project root\n 2. If using a custom tsconfig, verify the path in your Vitest config:\n test: { typecheck: { tsconfig: 'path/to/tsconfig.json' } }\n 3. Check that the tsconfig file is valid JSON`;
1443
- throw new Error(msg);
1444
- }
1445
- const typeErrors = await this.parseTscLikeOutput(output);
1446
- const testFiles = new Set(this.getFiles());
1447
- if (!this._tests) this._tests = await this.collectTests();
1448
- const sourceErrors = [];
1449
- const files = [];
1450
- testFiles.forEach((path) => {
1451
- const { file, definitions, map, parsed } = this._tests[path];
1452
- const errors = typeErrors.get(path);
1453
- files.push(file);
1454
- if (!errors) {
1455
- this.markPassed(file);
1456
- return;
1457
- }
1458
- const sortedDefinitions = [...definitions.sort((a, b) => b.start - a.start)];
1459
- // has no map for ".js" files that use // @ts-check
1460
- const traceMap = map && new TraceMap(map);
1461
- const indexMap = createLocationsIndexMap(parsed);
1462
- const markState = (task, state) => {
1463
- task.result = { state: task.mode === "run" || task.mode === "only" ? state : task.mode };
1464
- if (task.suite) markState(task.suite, state);
1465
- else if (task.file && task !== task.file) markState(task.file, state);
1466
- };
1467
- errors.forEach(({ error, originalError }) => {
1468
- const processedPos = traceMap ? findGeneratedPosition(traceMap, {
1469
- line: originalError.line,
1470
- column: originalError.column,
1471
- source: basename(path)
1472
- }) : originalError;
1473
- const line = processedPos.line ?? originalError.line;
1474
- const column = processedPos.column ?? originalError.column;
1475
- const index = indexMap.get(`${line}:${column}`);
1476
- const definition = index != null && sortedDefinitions.find((def) => def.start <= index && def.end >= index);
1477
- const suite = definition ? definition.task : file;
1478
- const state = suite.mode === "run" || suite.mode === "only" ? "fail" : suite.mode;
1479
- const errors = suite.result?.errors || [];
1480
- suite.result = {
1481
- state,
1482
- errors
1483
- };
1484
- errors.push(error);
1485
- if (state === "fail") {
1486
- if (suite.suite) markState(suite.suite, "fail");
1487
- else if (suite.file && suite !== suite.file) markState(suite.file, "fail");
1488
- }
1489
- });
1490
- this.markPassed(file);
1491
- });
1492
- typeErrors.forEach((errors, path) => {
1493
- if (!testFiles.has(path)) sourceErrors.push(...errors.map(({ error }) => error));
1494
- });
1495
- return {
1496
- files,
1497
- sourceErrors,
1498
- time: performance$1.now() - this._startTime
1499
- };
1500
- }
1501
- async parseTscLikeOutput(output) {
1502
- const errorsMap = await getRawErrsMapFromTsCompile(output);
1503
- const typesErrors = /* @__PURE__ */ new Map();
1504
- errorsMap.forEach((errors, path) => {
1505
- const filepath = resolve$1(this.project.config.root, path);
1506
- const suiteErrors = errors.map((info) => {
1507
- const limit = Error.stackTraceLimit;
1508
- Error.stackTraceLimit = 0;
1509
- // Some expect-type errors have the most useful information on the second line e.g. `This expression is not callable.\n Type 'ExpectString<number>' has no call signatures.`
1510
- const errMsg = info.errMsg.replace(/\r?\n\s*(Type .* has no call signatures)/g, " $1");
1511
- const error = new TypeCheckError(errMsg, [{
1512
- file: filepath,
1513
- line: info.line,
1514
- column: info.column,
1515
- method: ""
1516
- }]);
1517
- Error.stackTraceLimit = limit;
1518
- return {
1519
- originalError: info,
1520
- error: {
1521
- name: error.name,
1522
- message: errMsg,
1523
- stacks: error.stacks,
1524
- stack: ""
1525
- }
1526
- };
1527
- });
1528
- typesErrors.set(filepath, suiteErrors);
1529
- });
1530
- return typesErrors;
1531
- }
1532
- async stop() {
1533
- this.process?.kill();
1534
- this.process = void 0;
1535
- }
1536
- async ensurePackageInstalled(ctx, checker) {
1537
- if (checker !== "tsc" && checker !== "vue-tsc") return;
1538
- const packageName = checker === "tsc" ? "typescript" : "vue-tsc";
1539
- await ctx.packageInstaller.ensureInstalled(packageName, ctx.config.root);
1540
- }
1541
- getExitCode() {
1542
- return this.process?.exitCode != null && this.process.exitCode;
1543
- }
1544
- getOutput() {
1545
- return this._output;
1546
- }
1547
- async spawn() {
1548
- const { root, watch, typecheck } = this.project.config;
1549
- const args = [
1550
- "--noEmit",
1551
- "--pretty",
1552
- "false",
1553
- "--incremental",
1554
- "--tsBuildInfoFile",
1555
- join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo")
1556
- ];
1557
- // use builtin watcher because it's faster
1558
- if (watch) args.push("--watch");
1559
- if (typecheck.allowJs) args.push("--allowJs", "--checkJs");
1560
- if (typecheck.tsconfig) args.push("-p", resolve$1(root, typecheck.tsconfig));
1561
- this._output = "";
1562
- this._startTime = performance$1.now();
1563
- const child = x(typecheck.checker, args, {
1564
- nodeOptions: {
1565
- cwd: root,
1566
- stdio: "pipe"
1567
- },
1568
- throwOnError: false
1569
- });
1570
- this.process = child.process;
1571
- let rerunTriggered = false;
1572
- let dataReceived = false;
1573
- return new Promise((resolve, reject) => {
1574
- if (!child.process || !child.process.stdout) {
1575
- reject(/* @__PURE__ */ new Error(`Failed to initialize ${typecheck.checker}. This is a bug in Vitest - please, open an issue with reproduction.`));
1576
- return;
1577
- }
1578
- let resolved = false;
1579
- child.process.stdout.on("data", (chunk) => {
1580
- dataReceived = true;
1581
- this._output += chunk;
1582
- if (!watch) return;
1583
- if (this._output.includes("File change detected") && !rerunTriggered) {
1584
- this._onWatcherRerun?.();
1585
- this._startTime = performance$1.now();
1586
- this._result.sourceErrors = [];
1587
- this._result.files = [];
1588
- this._tests = null;
1589
- rerunTriggered = true;
1590
- }
1591
- if (/Found \w+ errors*. Watching for/.test(this._output)) {
1592
- rerunTriggered = false;
1593
- this.prepareResults(this._output).then((result) => {
1594
- this._result = result;
1595
- this._onParseEnd?.(result);
1596
- });
1597
- this._output = "";
1598
- }
1599
- });
1600
- // Also capture stderr for configuration errors like missing tsconfig
1601
- child.process.stderr?.on("data", (chunk) => {
1602
- this._output += chunk;
1603
- });
1604
- const timeout = setTimeout(() => reject(/* @__PURE__ */ new Error(`${typecheck.checker} spawn timed out`)), this.project.config.typecheck.spawnTimeout);
1605
- let winTimeout;
1606
- function onError(cause) {
1607
- if (resolved) return;
1608
- clearTimeout(timeout);
1609
- clearTimeout(winTimeout);
1610
- resolved = true;
1611
- reject(new Error("Spawning typechecker failed - is typescript installed?", { cause }));
1612
- }
1613
- child.process.once("spawn", () => {
1614
- this._onParseStart?.();
1615
- child.process?.off("error", onError);
1616
- clearTimeout(timeout);
1617
- if (process.platform === "win32")
1618
- // on Windows, the process might be spawned but fail to start
1619
- // we wait for a potential error here. if "close" event didn't trigger,
1620
- // we resolve the promise
1621
- winTimeout = setTimeout(() => {
1622
- resolved = true;
1623
- resolve({ result: child });
1624
- }, 200);
1625
- else {
1626
- resolved = true;
1627
- resolve({ result: child });
1628
- }
1629
- });
1630
- if (process.platform === "win32") child.process.once("close", (code) => {
1631
- if (code != null && code !== 0 && !dataReceived) onError(/* @__PURE__ */ new Error(`The ${typecheck.checker} command exited with code ${code}.`));
1632
- });
1633
- child.process.once("error", onError);
1634
- });
1635
- }
1636
- async start() {
1637
- if (this.process) return;
1638
- const { watch } = this.project.config;
1639
- const { result: child } = await this.spawn();
1640
- if (!watch) {
1641
- await child;
1642
- this._result = await this.prepareResults(this._output);
1643
- await this._onParseEnd?.(this._result);
1644
- }
1645
- }
1646
- getResult() {
1647
- return this._result;
1648
- }
1649
- getTestFiles() {
1650
- return Object.values(this._tests || {}).map((i) => i.file);
1651
- }
1652
- getTestPacksAndEvents() {
1653
- const packs = [];
1654
- const events = [];
1655
- for (const { file } of Object.values(this._tests || {})) {
1656
- const result = convertTasksToEvents(file);
1657
- packs.push(...result.packs);
1658
- events.push(...result.events);
1659
- }
1660
- return {
1661
- packs,
1662
- events
1663
- };
1664
- }
1665
- }
1666
- function findGeneratedPosition(traceMap, { line, column, source }) {
1667
- const found = generatedPositionFor(traceMap, {
1668
- line,
1669
- column,
1670
- source
1671
- });
1672
- if (found.line !== null) return found;
1673
- // find the next source token position when the exact error position doesn't exist in source map.
1674
- // this can happen, for example, when the type error is in the comment "// @ts-expect-error"
1675
- // and comments are stripped away in the generated code.
1676
- const mappings = [];
1677
- eachMapping(traceMap, (m) => {
1678
- if (m.source === source && m.originalLine !== null && m.originalColumn !== null && (line === m.originalLine ? column < m.originalColumn : line < m.originalLine)) mappings.push(m);
1679
- });
1680
- const next = mappings.sort((a, b) => a.originalLine === b.originalLine ? a.originalColumn - b.originalColumn : a.originalLine - b.originalLine).at(0);
1681
- if (next) return {
1682
- line: next.generatedLine,
1683
- column: next.generatedColumn
1684
- };
1685
- return {
1686
- line: null,
1687
- column: null
1688
- };
1689
- }
1690
-
1691
- // use Logger with custom Console to capture entire error printing
1692
- function capturePrintError(error, ctx, options) {
1693
- let output = "";
1694
- const console = new Console(new Writable({ write(chunk, _encoding, callback) {
1695
- output += String(chunk);
1696
- callback();
1697
- } }));
1698
- return {
1699
- nearest: printError(error, ctx, {
1700
- error: console.error.bind(console),
1701
- highlight: ctx.logger.highlight.bind(ctx.logger)
1702
- }, {
1703
- showCodeFrame: false,
1704
- ...options
1705
- })?.nearest,
1706
- output
1707
- };
1708
- }
1709
- function printError(error, ctx, logger, options) {
1710
- const project = options.project ?? ctx.coreWorkspaceProject ?? ctx.projects[0];
1711
- return printErrorInner(error, project, {
1712
- logger,
1713
- type: options.type,
1714
- showCodeFrame: options.showCodeFrame,
1715
- screenshotPaths: options.screenshotPaths,
1716
- printProperties: options.verbose,
1717
- parseErrorStacktrace(error) {
1718
- if (error.stacks) if (options.fullStack) return error.stacks;
1719
- else return error.stacks.filter((stack) => {
1720
- return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
1721
- });
1722
- // browser stack trace needs to be processed differently,
1723
- // so there is a separate method for that
1724
- if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, {
1725
- frameFilter: project.config.onStackTrace,
1726
- ignoreStackEntries: options.fullStack ? [] : void 0
1727
- });
1728
- // node.js stack trace already has correct source map locations
1729
- return parseErrorStacktrace(error, {
1730
- frameFilter: project.config.onStackTrace,
1731
- ignoreStackEntries: options.fullStack ? [] : void 0
1732
- });
1733
- }
1734
- });
1735
- }
1736
- function printErrorInner(error, project, options) {
1737
- const { showCodeFrame = true, type, printProperties = true } = options;
1738
- const logger = options.logger;
1739
- let e = error;
1740
- if (isPrimitive(e)) e = {
1741
- message: String(error).split(/\n/g)[0],
1742
- stack: String(error)
1743
- };
1744
- if (!e) {
1745
- const error = /* @__PURE__ */ new Error("unknown error");
1746
- e = {
1747
- message: e ?? error.message,
1748
- stack: error.stack
1749
- };
1750
- }
1751
- // Error may have occurred even before the configuration was resolved
1752
- if (!project) {
1753
- printErrorMessage(e, logger);
1754
- return;
1755
- }
1756
- const stacks = options.parseErrorStacktrace(e);
1757
- const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
1758
- // we are checking that this module was processed by us at one point
1759
- try {
1760
- return [...Object.values(project._vite?.environments || {}), ...Object.values(project.browser?.vite.environments || {})].some((environment) => {
1761
- return [...environment.moduleGraph.getModulesByFile(stack.file)?.values() || []].some((module) => !!module.transformResult);
1762
- }) && existsSync(stack.file);
1763
- } catch {
1764
- return false;
1765
- }
1766
- });
1767
- if (type) printErrorType(type, project.vitest);
1768
- printErrorMessage(e, logger);
1769
- if (options.screenshotPaths?.length) {
1770
- const uniqueScreenshots = Array.from(new Set(options.screenshotPaths));
1771
- const length = uniqueScreenshots.length;
1772
- logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
1773
- logger.error(uniqueScreenshots.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
1774
- if (!e.diff) logger.error();
1775
- }
1776
- if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
1777
- if ("__vitest_rollup_error__" in e) {
1778
- // https://github.com/vitejs/vite/blob/95020ab49e12d143262859e095025cf02423c1d9/packages/vite/src/node/server/middlewares/error.ts#L25-L36
1779
- const err = e.__vitest_rollup_error__;
1780
- logger.error([
1781
- err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
1782
- err.id && ` File: ${c.cyan(err.id)}${err.loc ? `:${err.loc.line}:${err.loc.column}` : ""}`,
1783
- err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
1784
- ].filter(Boolean).join("\n"));
1785
- }
1786
- // E.g. AssertionError from assert does not set showDiff but has both actual and expected properties
1787
- if (e.diff) logger.error(`\n${e.diff}\n`);
1788
- // if the error provide the frame
1789
- if (e.frame) logger.error(c.yellow(e.frame));
1790
- else printStack(logger, project, stacks, nearest, printProperties ? getErrorProperties(e) : {}, (s) => {
1791
- if (showCodeFrame && s === nearest && nearest) {
1792
- const sourceCode = readFileSync(nearest.file, "utf-8");
1793
- logger.error(generateCodeFrame(sourceCode.length > 1e5 ? sourceCode : logger.highlight(nearest.file, sourceCode), 4, s));
1794
- }
1795
- });
1796
- const testPath = e.VITEST_TEST_PATH;
1797
- const testName = e.VITEST_TEST_NAME;
1798
- // testName has testPath inside
1799
- 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.`));
1800
- 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:
1801
- - The error was thrown, while Vitest was running this test.
1802
- - If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
1803
- if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
1804
- e.cause.name = `Caused by: ${e.cause.name}`;
1805
- printErrorInner(e.cause, project, {
1806
- showCodeFrame: false,
1807
- logger: options.logger,
1808
- parseErrorStacktrace: options.parseErrorStacktrace
1809
- });
1810
- }
1811
- handleImportOutsideModuleError(e.stack || "", logger);
1812
- return { nearest };
1813
- }
1814
- function printErrorType(type, ctx) {
1815
- ctx.logger.error(`\n${errorBanner(type)}`);
1816
- }
1817
- const skipErrorProperties = new Set([
1818
- "cause",
1819
- "stacks",
1820
- "type",
1821
- "showDiff",
1822
- "ok",
1823
- "operator",
1824
- "diff",
1825
- "codeFrame",
1826
- "actual",
1827
- "expected",
1828
- "diffOptions",
1829
- "runnerError",
1830
- "sourceURL",
1831
- "column",
1832
- "line",
1833
- "fileName",
1834
- "lineNumber",
1835
- "columnNumber",
1836
- "VITEST_TEST_NAME",
1837
- "VITEST_TEST_PATH",
1838
- "__vitest_rollup_error__",
1839
- "__vitest_error_context__",
1840
- ...Object.getOwnPropertyNames(Error.prototype),
1841
- ...Object.getOwnPropertyNames(Object.prototype)
1842
- ]);
1843
- function getErrorProperties(e) {
1844
- const errorObject = Object.create(null);
1845
- if (e.name === "AssertionError") return errorObject;
1846
- for (const key of Object.getOwnPropertyNames(e))
1847
- // print the original stack if it was ever changed manually by the user
1848
- if (key === "stack" && e[key] != null && typeof e[key] !== "string") errorObject[key] = e[key];
1849
- else if (key !== "stack" && !skipErrorProperties.has(key)) errorObject[key] = e[key];
1850
- return errorObject;
1851
- }
1852
- const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
1853
- function handleImportOutsideModuleError(stack, logger) {
1854
- if (!esmErrors.some((e) => stack.includes(e))) return;
1855
- const path = normalize(stack.split("\n")[0].trim());
1856
- let name = path.split("/node_modules/").pop() || "";
1857
- if (name[0] === "@") name = name.split("/").slice(0, 2).join("/");
1858
- else name = name.split("/")[0];
1859
- if (name) printModuleWarningForPackage(logger, path, name);
1860
- else printModuleWarningForSourceCode(logger, path);
1861
- }
1862
- function printModuleWarningForPackage(logger, path, name) {
1863
- logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package ${c.bold(`"${name}"`)} asking them to ship the file in .mjs extension or add "type": "module" in their package.json.
1864
-
1865
- As a temporary workaround you can try to inline the package by updating your config:
1866
-
1867
- ` + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
1868
- test: {
1869
- server: {
1870
- deps: {
1871
- inline: [
1872
- ${c.yellow(c.bold(`"${name}"`))}
1873
- ]
1874
- }
1875
- }
1876
- }
1877
- }\n`)));
1878
- }
1879
- function printModuleWarningForSourceCode(logger, path) {
1880
- logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. To fix this issue, change the file extension to .mjs or add "type": "module" in your package.json.`));
1881
- }
1882
- function printErrorMessage(error, logger) {
1883
- const errorName = error.name || "Unknown Error";
1884
- if (!error.message) {
1885
- logger.error(error);
1886
- return;
1887
- }
1888
- if (error.message.length > 5e3)
1889
- // Protect against infinite stack trace in tinyrainbow
1890
- logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
1891
- else logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
1892
- }
1893
- function printStack(logger, project, stack, highlight, errorProperties, onStack) {
1894
- for (const frame of stack) {
1895
- const color = frame === highlight ? c.cyan : c.gray;
1896
- const path = relative(project.config.root, frame.file);
1897
- logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
1898
- onStack?.(frame);
1899
- }
1900
- if (stack.length) logger.error();
1901
- if (hasProperties(errorProperties)) {
1902
- logger.error(c.red(c.dim(divider())));
1903
- const propertiesString = inspect(errorProperties);
1904
- logger.error(c.red(c.bold("Serialized Error:")), c.gray(propertiesString));
1905
- }
1906
- }
1907
- function hasProperties(obj) {
1908
- // eslint-disable-next-line no-unreachable-loop
1909
- for (const _key in obj) return true;
1910
- return false;
1911
- }
1912
- function generateCodeFrame(source, indent = 0, loc, range = 2) {
1913
- const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
1914
- const end = start;
1915
- const lines = source.split(lineSplitRE);
1916
- const nl = /\r\n/.test(source) ? 2 : 1;
1917
- let count = 0;
1918
- let res = [];
1919
- const columns = process.stdout?.columns || 80;
1920
- for (let i = 0; i < lines.length; i++) {
1921
- count += lines[i].length + nl;
1922
- if (count >= start) {
1923
- for (let j = i - range; j <= i + range || end > count; j++) {
1924
- if (j < 0 || j >= lines.length) continue;
1925
- const lineLength = lines[j].length;
1926
- const strippedContent = stripVTControlCharacters(lines[j]);
1927
- if (strippedContent.startsWith("//# sourceMappingURL")) continue;
1928
- // too long, maybe it's a minified file, skip for codeframe
1929
- if (strippedContent.length > 200) return "";
1930
- const truncatedLine = truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent).trimEnd();
1931
- res.push(lineNo(j + 1) + (truncatedLine ? " " + truncatedLine : truncatedLine));
1932
- if (j === i) {
1933
- // push underline
1934
- const pad = start - (count - lineLength) + (nl - 1);
1935
- const length = Math.max(1, end > count ? lineLength - pad : end - start);
1936
- res.push(lineNo() + " ".repeat(pad + 1) + c.red("^".repeat(length)));
1937
- } else if (j > i) {
1938
- if (end > count) {
1939
- const length = Math.max(1, Math.min(end - count, lineLength));
1940
- res.push(lineNo() + " " + c.red("^".repeat(length)));
1941
- }
1942
- count += lineLength + 1;
1943
- }
1944
- }
1945
- break;
1946
- }
1947
- }
1948
- if (indent) res = res.map((line) => " ".repeat(indent) + line);
1949
- return res.join("\n");
1950
- }
1951
- function lineNo(no = "") {
1952
- return c.gray(`${String(no).padStart(3, " ")}|`);
1953
- }
1954
-
1955
- function getOutputFile(config, reporter) {
1956
- if (!config?.outputFile) return;
1957
- if (typeof config.outputFile === "string") return config.outputFile;
1958
- return config.outputFile[reporter];
1959
- }
1960
- function createDefinesScript(define) {
1961
- if (!define) return "";
1962
- if (serializeDefine(define) === "{}") return "";
1963
- return `
1964
- const defines = ${serializeDefine(define)}
1965
- Object.keys(defines).forEach((key) => {
1966
- const segments = key.split('.')
1967
- let target = globalThis
1968
- for (let i = 0; i < segments.length; i++) {
1969
- const segment = segments[i]
1970
- if (i === segments.length - 1) {
1971
- target[segment] = defines[key]
1972
- } else {
1973
- target = target[segment] || (target[segment] = {})
1974
- }
1975
- }
1976
- })
1977
- `;
1978
- }
1979
- /**
1980
- * Like `JSON.stringify` but keeps raw string values as a literal
1981
- * in the generated code. For example: `"window"` would refer to
1982
- * the global `window` object directly.
1983
- */
1984
- function serializeDefine(define) {
1985
- const userDefine = {};
1986
- for (const key in define) {
1987
- // vitest sets this to avoid vite:client-inject plugin
1988
- if (key === "process.env.NODE_ENV" && define[key] === "process.env.NODE_ENV") continue;
1989
- // import.meta.env.* is handled in `importAnalysis` plugin
1990
- if (!key.startsWith("import.meta.env.")) userDefine[key] = define[key];
1991
- }
1992
- let res = `{`;
1993
- const keys = Object.keys(userDefine).sort();
1994
- for (let i = 0; i < keys.length; i++) {
1995
- const key = keys[i];
1996
- const val = userDefine[key];
1997
- res += `${JSON.stringify(key)}: ${handleDefineValue(val)}`;
1998
- if (i !== keys.length - 1) res += `, `;
1999
- }
2000
- return `${res}}`;
2001
- }
2002
- function handleDefineValue(value) {
2003
- if (typeof value === "undefined") return "undefined";
2004
- if (typeof value === "string") return value;
2005
- return JSON.stringify(value);
2006
- }
2007
-
2008
- class BlobReporter {
2009
- start = 0;
2010
- ctx;
2011
- options;
2012
- coverage;
2013
- constructor(options) {
2014
- this.options = options;
2015
- }
2016
- onInit(ctx) {
2017
- if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
2018
- this.ctx = ctx;
2019
- this.start = performance.now();
2020
- this.coverage = void 0;
2021
- }
2022
- onCoverage(coverage) {
2023
- this.coverage = coverage;
2024
- }
2025
- async onTestRunEnd(testModules, unhandledErrors) {
2026
- const executionTime = performance.now() - this.start;
2027
- const files = testModules.map((testModule) => testModule.task);
2028
- const errors = [...unhandledErrors];
2029
- const coverage = this.coverage;
2030
- let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
2031
- if (!outputFile) {
2032
- const shard = this.ctx.config.shard;
2033
- outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
2034
- }
2035
- const environmentModules = {};
2036
- this.ctx.projects.forEach((project) => {
2037
- const serializedProject = {
2038
- environments: {},
2039
- external: []
2040
- };
2041
- Object.entries(project.vite.environments).forEach(([environmentName, environment]) => {
2042
- serializedProject.environments[environmentName] = serializeEnvironmentModuleGraph(environment);
2043
- });
2044
- if (project.browser?.vite.environments.client) serializedProject.browser = serializeEnvironmentModuleGraph(project.browser.vite.environments.client);
2045
- for (const [id, value] of project._resolver.externalizeCache.entries()) if (typeof value === "string") serializedProject.external.push([id, value]);
2046
- environmentModules[project.name] = serializedProject;
2047
- });
2048
- const report = [
2049
- this.ctx.version,
2050
- files,
2051
- errors,
2052
- coverage,
2053
- executionTime,
2054
- environmentModules
2055
- ];
2056
- const reportFile = resolve$1(this.ctx.config.root, outputFile);
2057
- await writeBlob(report, reportFile);
2058
- this.ctx.logger.log("blob report written to", reportFile);
2059
- }
2060
- }
2061
- async function writeBlob(content, filename) {
2062
- const report = stringify(content);
2063
- const dir = dirname(filename);
2064
- if (!existsSync(dir)) await mkdir(dir, { recursive: true });
2065
- await writeFile(filename, report, "utf-8");
2066
- }
2067
- async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
2068
- // using process.cwd() because --merge-reports can only be used in CLI
2069
- const resolvedDir = resolve$1(process.cwd(), blobsDirectory);
2070
- const promises = (await readdir(resolvedDir)).map(async (filename) => {
2071
- const fullPath = resolve$1(resolvedDir, filename);
2072
- if (!(await stat(fullPath)).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`);
2073
- const [version, files, errors, coverage, executionTime, environmentModules] = parse(await readFile(fullPath, "utf-8"));
2074
- 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`);
2075
- return {
2076
- version,
2077
- files,
2078
- errors,
2079
- coverage,
2080
- file: filename,
2081
- executionTime,
2082
- environmentModules
2083
- };
2084
- });
2085
- const blobs = await Promise.all(promises);
2086
- if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
2087
- const versions = new Set(blobs.map((blob) => blob.version));
2088
- 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")}`);
2089
- 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}`);
2090
- // Restore module graph
2091
- const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
2092
- for (const project of projectsArray) if (project.isBrowserEnabled()) await project._initBrowserServer();
2093
- blobs.forEach((blob) => {
2094
- Object.entries(blob.environmentModules).forEach(([projectName, modulesByProject]) => {
2095
- const project = projects[projectName];
2096
- if (!project) return;
2097
- modulesByProject.external.forEach(([id, externalized]) => {
2098
- project._resolver.externalizeCache.set(id, externalized);
2099
- });
2100
- Object.entries(modulesByProject.environments).forEach(([environmentName, moduleGraph]) => {
2101
- const environment = project.vite.environments[environmentName];
2102
- deserializeEnvironmentModuleGraph(environment, moduleGraph);
2103
- });
2104
- const browserModuleGraph = modulesByProject.browser;
2105
- if (browserModuleGraph) {
2106
- const browserEnvironment = project.browser.vite.environments.client;
2107
- deserializeEnvironmentModuleGraph(browserEnvironment, browserModuleGraph);
2108
- }
2109
- });
2110
- });
2111
- return {
2112
- files: blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
2113
- return (f1.result?.startTime || 0) - (f2.result?.startTime || 0);
2114
- }),
2115
- errors: blobs.flatMap((blob) => blob.errors),
2116
- coverages: blobs.map((blob) => blob.coverage),
2117
- executionTimes: blobs.map((blob) => blob.executionTime)
2118
- };
2119
- }
2120
- function serializeEnvironmentModuleGraph(environment) {
2121
- const idTable = [];
2122
- const idMap = /* @__PURE__ */ new Map();
2123
- const getIdIndex = (id) => {
2124
- const existing = idMap.get(id);
2125
- if (existing != null) return existing;
2126
- const next = idTable.length;
2127
- idMap.set(id, next);
2128
- idTable.push(id);
2129
- return next;
2130
- };
2131
- const modules = [];
2132
- for (const [id, mod] of environment.moduleGraph.idToModuleMap.entries()) {
2133
- if (!mod.file) continue;
2134
- const importedIds = [];
2135
- for (const importedNode of mod.importedModules) if (importedNode.id) importedIds.push(getIdIndex(importedNode.id));
2136
- modules.push([
2137
- getIdIndex(id),
2138
- getIdIndex(mod.file),
2139
- getIdIndex(mod.url),
2140
- importedIds
2141
- ]);
2142
- }
2143
- return {
2144
- idTable,
2145
- modules
2146
- };
2147
- }
2148
- function deserializeEnvironmentModuleGraph(environment, serialized) {
2149
- const nodesById = /* @__PURE__ */ new Map();
2150
- serialized.modules.forEach(([id, file, url]) => {
2151
- const moduleId = serialized.idTable[id];
2152
- const filePath = serialized.idTable[file];
2153
- const urlPath = serialized.idTable[url];
2154
- const moduleNode = environment.moduleGraph.createFileOnlyEntry(filePath);
2155
- moduleNode.url = urlPath;
2156
- moduleNode.id = moduleId;
2157
- moduleNode.transformResult = {
2158
- code: " ",
2159
- map: null
2160
- };
2161
- environment.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
2162
- nodesById.set(moduleId, moduleNode);
2163
- });
2164
- serialized.modules.forEach(([id, _file, _url, importedIds]) => {
2165
- const moduleId = serialized.idTable[id];
2166
- const moduleNode = nodesById.get(moduleId);
2167
- importedIds.forEach((importedIdIndex) => {
2168
- const importedId = serialized.idTable[importedIdIndex];
2169
- const importedNode = nodesById.get(importedId);
2170
- moduleNode.importedModules.add(importedNode);
2171
- importedNode.importers.add(moduleNode);
2172
- });
2173
- });
2174
- }
2175
-
2176
- class HangingProcessReporter {
2177
- whyRunning;
2178
- onInit() {
2179
- this.whyRunning = createRequire(import.meta.url)("why-is-node-running");
2180
- }
2181
- onProcessTimeout() {
2182
- this.whyRunning?.();
2183
- }
2184
- }
2185
-
2186
- const BADGE_PADDING = " ";
2187
- class BaseReporter {
2188
- start = 0;
2189
- end = 0;
2190
- watchFilters;
2191
- failedUnwatchedFiles = [];
2192
- isTTY;
2193
- ctx = void 0;
2194
- renderSucceed = false;
2195
- verbose = false;
2196
- silent;
2197
- _filesInWatchMode = /* @__PURE__ */ new Map();
2198
- _timeStart = formatTimeString(/* @__PURE__ */ new Date());
2199
- constructor(options = {}) {
2200
- this.isTTY = options.isTTY ?? isTTY;
2201
- this.silent = options.silent;
2202
- }
2203
- onInit(ctx) {
2204
- this.ctx = ctx;
2205
- this.silent ??= this.ctx.config.silent;
2206
- this.ctx.logger.printBanner();
2207
- }
2208
- log(...messages) {
2209
- this.ctx.logger.log(...messages);
2210
- }
2211
- error(...messages) {
2212
- this.ctx.logger.error(...messages);
2213
- }
2214
- relative(path) {
2215
- return relative(this.ctx.config.root, path);
2216
- }
2217
- onTestRunStart(_specifications) {
2218
- this.start = performance$1.now();
2219
- this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
2220
- }
2221
- onTestRunEnd(testModules, unhandledErrors, _reason) {
2222
- const files = testModules.map((testModule) => testModule.task);
2223
- const errors = [...unhandledErrors];
2224
- this.end = performance$1.now();
2225
- if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
2226
- else this.reportSummary(files, errors);
2227
- }
2228
- onTestCaseResult(testCase) {
2229
- if (testCase.result().state === "failed") this.logFailedTask(testCase.task);
2230
- }
2231
- onTestSuiteResult(testSuite) {
2232
- if (testSuite.state() === "failed") this.logFailedTask(testSuite.task);
2233
- }
2234
- onTestModuleEnd(testModule) {
2235
- if (testModule.state() === "failed") this.logFailedTask(testModule.task);
2236
- this.printTestModule(testModule);
2237
- }
2238
- logFailedTask(task) {
2239
- if (this.silent === "passed-only") for (const log of task.logs || []) this.onUserConsoleLog(log, "failed");
2240
- }
2241
- printTestModule(testModule) {
2242
- const moduleState = testModule.state();
2243
- if (moduleState === "queued" || moduleState === "pending") return;
2244
- let testsCount = 0;
2245
- let failedCount = 0;
2246
- let skippedCount = 0;
2247
- let todoCount = 0;
2248
- // delaying logs to calculate the test stats first
2249
- // which minimizes the amount of for loops
2250
- const logs = [];
2251
- const originalLog = this.log.bind(this);
2252
- this.log = (msg) => logs.push(msg);
2253
- const visit = (suiteState, children) => {
2254
- for (const child of children) if (child.type === "suite") {
2255
- const suiteState = child.state();
2256
- // Skipped suites are hidden when --hideSkippedTests, print otherwise
2257
- if (!this.ctx.config.hideSkippedTests || suiteState !== "skipped" || child.task.mode === "todo") this.printTestSuite(child);
2258
- visit(suiteState, child.children);
2259
- } else {
2260
- const testResult = child.result();
2261
- testsCount++;
2262
- if (testResult.state === "failed") failedCount++;
2263
- else if (testResult.state === "skipped") if (child.options.mode === "todo") todoCount++;
2264
- else skippedCount++;
2265
- if (this.ctx.config.hideSkippedTests && suiteState === "skipped" && child.options.mode !== "todo")
2266
- // Skipped suites are hidden when --hideSkippedTests
2267
- continue;
2268
- this.printTestCase(moduleState, child);
2269
- }
2270
- };
2271
- try {
2272
- visit(moduleState, testModule.children);
2273
- } finally {
2274
- this.log = originalLog;
2275
- }
2276
- this.log(this.getModuleLog(testModule, {
2277
- tests: testsCount,
2278
- failed: failedCount,
2279
- skipped: skippedCount,
2280
- todo: todoCount
2281
- }));
2282
- logs.forEach((log) => this.log(log));
2283
- }
2284
- printTestCase(moduleState, test) {
2285
- const testResult = test.result();
2286
- const { duration = 0 } = test.diagnostic() || {};
2287
- const padding = this.getTestIndentation(test.task);
2288
- const suffix = this.getTestCaseSuffix(test);
2289
- if (testResult.state === "failed") this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix);
2290
- else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)} ${suffix}`);
2291
- else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") ; else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`);
2292
- }
2293
- getModuleLog(testModule, counts) {
2294
- let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
2295
- if (counts.failed) state += c.dim(" | ") + c.red(`${counts.failed} failed`);
2296
- if (counts.skipped) state += c.dim(" | ") + c.yellow(`${counts.skipped} skipped`);
2297
- if (counts.todo) state += c.dim(" | ") + c.gray(`${counts.todo} todo`);
2298
- let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(testModule.task);
2299
- const diagnostic = testModule.diagnostic();
2300
- if (diagnostic.heap != null) suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
2301
- return ` ${this.getEntityPrefix(testModule)} ${testModule.task.name} ${suffix}`;
2302
- }
2303
- printTestSuite(testSuite) {
2304
- if (!this.renderSucceed) return;
2305
- const indentation = " ".repeat(getIndentation(testSuite.task));
2306
- const tests = Array.from(testSuite.children.allTests());
2307
- const state = this.getStateSymbol(testSuite);
2308
- this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
2309
- }
2310
- getTestName(test, _separator) {
2311
- return test.name;
2312
- }
2313
- getFullName(test, separator) {
2314
- if (test === test.file) return test.name;
2315
- let name = test.file.name;
2316
- if (test.location) name += c.dim(`:${test.location.line}:${test.location.column}`);
2317
- name += separator;
2318
- name += getTestName(test, separator);
2319
- return name;
2320
- }
2321
- getTestIndentation(test) {
2322
- return " ".repeat(getIndentation(test));
2323
- }
2324
- printAnnotations(test, console, padding = 0) {
2325
- const annotations = test.annotations();
2326
- if (!annotations.length) return;
2327
- const PADDING = " ".repeat(padding);
2328
- const groupedAnnotations = {};
2329
- annotations.forEach((annotation) => {
2330
- const { location, type } = annotation;
2331
- let group;
2332
- if (location) {
2333
- const file = relative(test.project.config.root, location.file);
2334
- group = `${c.gray(`${file}:${location.line}:${location.column}`)} ${c.bold(type)}`;
2335
- } else group = c.bold(type);
2336
- groupedAnnotations[group] ??= [];
2337
- groupedAnnotations[group].push(annotation);
2338
- });
2339
- for (const group in groupedAnnotations) {
2340
- this[console](`${PADDING}${c.blue(F_POINTER)} ${group}`);
2341
- groupedAnnotations[group].forEach(({ message }) => {
2342
- this[console](`${PADDING} ${c.blue(F_DOWN_RIGHT)} ${message}`);
2343
- });
2344
- }
2345
- }
2346
- getEntityPrefix(entity) {
2347
- let title = this.getStateSymbol(entity);
2348
- if (entity.project.name) title += ` ${formatProjectName(entity.project, "")}`;
2349
- if (entity.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
2350
- return title;
2351
- }
2352
- getTestCaseSuffix(testCase) {
2353
- const { heap, retryCount, repeatCount } = testCase.diagnostic() || {};
2354
- const testResult = testCase.result();
2355
- let suffix = this.getDurationPrefix(testCase.task);
2356
- if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
2357
- if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
2358
- if (heap != null) suffix += c.magenta(` ${Math.floor(heap / 1024 / 1024)} MB heap used`);
2359
- if (testResult.state === "skipped" && testResult.note) suffix += c.dim(c.gray(` [${testResult.note}]`));
2360
- return suffix;
2361
- }
2362
- getStateSymbol(test) {
2363
- return getStateSymbol(test.task);
2364
- }
2365
- getDurationPrefix(task) {
2366
- const duration = task.result?.duration && Math.round(task.result?.duration);
2367
- if (duration == null) return "";
2368
- return (duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green)(` ${duration}${c.dim("ms")}`);
2369
- }
2370
- onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
2371
- if (errors.length > 0 || hasFailed(files)) this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
2372
- else if (this.ctx.isCancelling) this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
2373
- else this.log(withLabel("green", "PASS", "Waiting for file changes..."));
2374
- const hints = [c.dim("press ") + c.bold("h") + c.dim(" to show help")];
2375
- if (hasFailedSnapshot(files)) hints.unshift(c.dim("press ") + c.bold(c.yellow("u")) + c.dim(" to update snapshot"));
2376
- else hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
2377
- this.log(BADGE_PADDING + hints.join(c.dim(", ")));
2378
- }
2379
- onWatcherRerun(files, trigger) {
2380
- this.watchFilters = files;
2381
- this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
2382
- // Update re-run count for each file
2383
- files.forEach((filepath) => {
2384
- let reruns = this._filesInWatchMode.get(filepath) ?? 0;
2385
- this._filesInWatchMode.set(filepath, ++reruns);
2386
- });
2387
- let banner = trigger ? c.dim(`${this.relative(trigger)} `) : "";
2388
- if (files.length === 1) {
2389
- const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
2390
- banner += c.blue(`x${rerun} `);
2391
- }
2392
- this.ctx.logger.clearFullScreen();
2393
- this.log(withLabel("blue", "RERUN", banner));
2394
- if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
2395
- if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
2396
- if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
2397
- this.log("");
2398
- for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
2399
- }
2400
- onUserConsoleLog(log, taskState) {
2401
- if (!this.shouldLog(log, taskState)) return;
2402
- const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
2403
- const write = (msg) => output.write(msg);
2404
- let headerText = "unknown test";
2405
- const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
2406
- if (task) headerText = this.getFullName(task, separator);
2407
- else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
2408
- write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
2409
- if (log.origin) {
2410
- // browser logs don't have an extra end of line at the end like Node.js does
2411
- if (log.browser) write("\n");
2412
- const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
2413
- const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
2414
- const highlight = task && stack.find((i) => i.file === task.file.filepath);
2415
- for (const frame of stack) {
2416
- const color = frame === highlight ? c.cyan : c.gray;
2417
- const path = relative(project.config.root, frame.file);
2418
- const positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
2419
- write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
2420
- }
2421
- }
2422
- write("\n");
2423
- }
2424
- onTestRemoved(trigger) {
2425
- this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
2426
- }
2427
- shouldLog(log, taskState) {
2428
- if (this.silent === true) return false;
2429
- if (this.silent === "passed-only" && taskState !== "failed") return false;
2430
- if (this.ctx.config.onConsoleLog) {
2431
- const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
2432
- const entity = task && this.ctx.state.getReportedEntity(task);
2433
- if (this.ctx.config.onConsoleLog(log.content, log.type, entity) === false) return false;
2434
- }
2435
- return true;
2436
- }
2437
- onServerRestart(reason) {
2438
- this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
2439
- }
2440
- reportSummary(files, errors) {
2441
- this.printErrorsSummary(files, errors);
2442
- const leakCount = this.printLeaksSummary();
2443
- if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
2444
- else this.reportTestSummary(files, errors, leakCount);
2445
- }
2446
- reportTestSummary(files, errors, leakCount) {
2447
- this.log();
2448
- const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
2449
- const tests = getTests(affectedFiles);
2450
- const snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
2451
- for (const [index, snapshot] of snapshotOutput.entries()) {
2452
- const title = index === 0 ? "Snapshots" : "";
2453
- this.log(`${padSummaryTitle(title)} ${snapshot}`);
2454
- }
2455
- if (snapshotOutput.length > 1) this.log();
2456
- this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
2457
- this.log(padSummaryTitle("Tests"), getStateString$1(tests));
2458
- if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
2459
- const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
2460
- this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
2461
- }
2462
- if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
2463
- if (leakCount) this.log(padSummaryTitle("Leaks"), c.bold(c.red(`${leakCount} leak${leakCount > 1 ? "s" : ""}`)));
2464
- this.log(padSummaryTitle("Start at"), this._timeStart);
2465
- const collectTime = sum(files, (file) => file.collectDuration);
2466
- const testsTime = sum(files, (file) => file.result?.duration);
2467
- const setupTime = sum(files, (file) => file.setupDuration);
2468
- if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
2469
- else {
2470
- const blobs = this.ctx.state.blobs;
2471
- // Execution time is either sum of all runs of `--merge-reports` or the current run's time
2472
- const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
2473
- const environmentTime = sum(files, (file) => file.environmentLoad);
2474
- const transformTime = this.ctx.state.transformTime;
2475
- const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
2476
- const timers = [
2477
- `transform ${formatTime(transformTime)}`,
2478
- `setup ${formatTime(setupTime)}`,
2479
- `import ${formatTime(collectTime)}`,
2480
- `tests ${formatTime(testsTime)}`,
2481
- `environment ${formatTime(environmentTime)}`,
2482
- typecheck && `typecheck ${formatTime(typecheck)}`
2483
- ].filter(Boolean).join(", ");
2484
- this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
2485
- if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
2486
- }
2487
- this.reportImportDurations();
2488
- this.log();
2489
- }
2490
- reportImportDurations() {
2491
- const { print, failOnDanger, thresholds } = this.ctx.config.experimental.importDurations;
2492
- if (!print && !failOnDanger) return;
2493
- const testModules = this.ctx.state.getTestModules();
2494
- const allImports = [];
2495
- for (const testModule of testModules) {
2496
- const importDurations = testModule.diagnostic().importDurations;
2497
- for (const filePath in importDurations) {
2498
- const duration = importDurations[filePath];
2499
- allImports.push({
2500
- importedModuleId: filePath,
2501
- testModule,
2502
- selfTime: duration.selfTime,
2503
- totalTime: duration.totalTime,
2504
- external: duration.external
2505
- });
2506
- }
2507
- }
2508
- if (allImports.length === 0) return;
2509
- const dangerImports = allImports.filter((imp) => imp.totalTime >= thresholds.danger);
2510
- const warnImports = allImports.filter((imp) => imp.totalTime >= thresholds.warn);
2511
- const hasDangerImports = dangerImports.length > 0;
2512
- const hasWarnImports = warnImports.length > 0;
2513
- // Determine if we should print
2514
- const shouldFail = failOnDanger && hasDangerImports;
2515
- if (!(print === true || print === "on-warn" && hasWarnImports || shouldFail)) return;
2516
- const sortedImports = allImports.sort((a, b) => b.totalTime - a.totalTime);
2517
- const maxTotalTime = sortedImports[0].totalTime;
2518
- const limit = this.ctx.config.experimental.importDurations.limit;
2519
- const topImports = sortedImports.slice(0, limit);
2520
- const totalSelfTime = allImports.reduce((sum, imp) => sum + imp.selfTime, 0);
2521
- const totalTotalTime = allImports.reduce((sum, imp) => sum + imp.totalTime, 0);
2522
- const slowestImport = sortedImports[0];
2523
- this.log();
2524
- this.log(c.bold("Import Duration Breakdown") + c.dim(` (Top ${limit})`));
2525
- this.log();
2526
- this.log(c.dim(`${"Module".padEnd(50)} ${"Self".padStart(6)} ${"Total".padStart(6)}`));
2527
- // if there are multiple files, it's highly possible that some of them will import the same large file
2528
- // we group them to show the distinction between those files more easily
2529
- // Import Duration Breakdown (Top 10)
2530
- //
2531
- // Module Self Total
2532
- // .../fields/FieldFile/__tests__/FieldFile.spec.ts 7ms 1.01s ████████████████████
2533
- // ↳ tests/support/components/index.ts 0ms 861ms █████████████████░░░
2534
- // ↳ tests/support/components/renderComponent.ts 59ms 861ms █████████████████░░░
2535
- // ...s__/apps/desktop/form-updater.desktop.spec.ts 8ms 991ms ████████████████████
2536
- // ...sts__/apps/mobile/form-updater.mobile.spec.ts 11ms 990ms ████████████████████
2537
- // shared/components/Form/__tests__/Form.spec.ts 5ms 988ms ████████████████████
2538
- // ↳ tests/support/components/index.ts 0ms 935ms ███████████████████░
2539
- // ↳ tests/support/components/renderComponent.ts 61ms 935ms ███████████████████░
2540
- // ...ditor/features/link/__test__/LinkForm.spec.ts 7ms 972ms ███████████████████░
2541
- // ↳ tests/support/components/renderComponent.ts 56ms 936ms ███████████████████░
2542
- const groupedImports = Object.entries(
2543
- groupBy(topImports, (i) => i.testModule.id)
2544
- // the first one is always the highest because the modules are already sorted
2545
- ).sort(([, imps1], [, imps2]) => imps2[0].totalTime - imps1[0].totalTime);
2546
- for (const [_, group] of groupedImports) group.forEach((imp, index) => {
2547
- const barWidth = 20;
2548
- const filledWidth = Math.round(imp.totalTime / maxTotalTime * barWidth);
2549
- const bar = c.cyan("█".repeat(filledWidth)) + c.dim("░".repeat(barWidth - filledWidth));
2550
- // only show the arrow if there is more than 1 group
2551
- const pathDisplay = this.ellipsisPath(imp.importedModuleId, imp.external, groupedImports.length > 1 && index > 0);
2552
- this.log(`${pathDisplay} ${this.importDurationTime(imp.selfTime)} ${this.importDurationTime(imp.totalTime)} ${bar}`);
2553
- });
2554
- this.log();
2555
- this.log(c.dim("Total imports: ") + allImports.length);
2556
- this.log(c.dim("Slowest import (total-time): ") + formatTime(slowestImport.totalTime));
2557
- this.log(c.dim("Total import time (self/total): ") + formatTime(totalSelfTime) + c.dim(" / ") + formatTime(totalTotalTime));
2558
- // Fail if danger threshold exceeded
2559
- if (shouldFail) {
2560
- this.log();
2561
- this.ctx.logger.error(`ERROR: ${dangerImports.length} import(s) exceeded the danger threshold of ${thresholds.danger}ms`);
2562
- process.exitCode = 1;
2563
- }
2564
- }
2565
- importDurationTime(duration) {
2566
- const { thresholds } = this.ctx.config.experimental.importDurations;
2567
- return (duration >= thresholds.danger ? c.red : duration >= thresholds.warn ? c.yellow : (c) => c)(formatTime(duration).padStart(6));
2568
- }
2569
- ellipsisPath(path, external, nested) {
2570
- const pathDisplay = this.relative(path);
2571
- const color = external ? c.magenta : (c) => c;
2572
- const slicedPath = pathDisplay.slice(-44);
2573
- let title = "";
2574
- if (pathDisplay.length > slicedPath.length) title += "...";
2575
- if (nested) title = ` ${F_DOWN_RIGHT} ${title}`;
2576
- title += slicedPath;
2577
- return color(title.padEnd(50));
2578
- }
2579
- printErrorsSummary(files, errors) {
2580
- const suites = getSuites(files);
2581
- const tests = getTests(files);
2582
- const failedSuites = suites.filter((i) => i.result?.errors);
2583
- const failedTests = tests.filter((i) => i.result?.state === "fail");
2584
- const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
2585
- // TODO: error divider should take into account merged errors for counting
2586
- let current = 1;
2587
- const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
2588
- if (failedSuites.length) {
2589
- this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`);
2590
- this.printTaskErrors(failedSuites, errorDivider);
2591
- }
2592
- if (failedTests.length) {
2593
- this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
2594
- this.printTaskErrors(failedTests, errorDivider);
2595
- }
2596
- if (errors.length) {
2597
- this.ctx.logger.printUnhandledErrors(errors);
2598
- this.error();
2599
- }
2600
- }
2601
- printLeaksSummary() {
2602
- const leaks = this.ctx.state.leakSet;
2603
- if (leaks.size === 0) return 0;
2604
- const leakWithStacks = /* @__PURE__ */ new Map();
2605
- // Leaks can be duplicate, where type and position are identical
2606
- for (const leak of leaks) {
2607
- const stacks = parseStacktrace(leak.stack);
2608
- if (stacks.length === 0) continue;
2609
- const key = `${this.relative(leak.filename)}:${stacks[0].line}:${stacks[0].column}:${leak.type}`;
2610
- if (leakWithStacks.has(key)) continue;
2611
- leakWithStacks.set(key, {
2612
- leak,
2613
- stacks
2614
- });
2615
- }
2616
- this.error(`\n${errorBanner(`Async Leaks ${leakWithStacks.size}`)}\n`);
2617
- for (const { leak, stacks } of leakWithStacks.values()) {
2618
- const filename = this.relative(leak.filename);
2619
- this.ctx.logger.error(c.red(`${leak.type} leaking in ${filename}`));
2620
- try {
2621
- const sourceCode = readFileSync(stacks[0].file, "utf-8");
2622
- this.ctx.logger.error(generateCodeFrame(sourceCode.length > 1e5 ? sourceCode : this.ctx.logger.highlight(stacks[0].file, sourceCode), void 0, stacks[0]));
2623
- } catch {}
2624
- printStack(this.ctx.logger, this.ctx.getProjectByName(leak.projectName), stacks, stacks[0], {});
2625
- }
2626
- return leakWithStacks.size;
2627
- }
2628
- reportBenchmarkSummary(files) {
2629
- const topBenches = getTests(files).filter((i) => i.result?.benchmark?.rank === 1);
2630
- this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
2631
- for (const bench of topBenches) {
2632
- const group = bench.suite || bench.file;
2633
- if (!group) continue;
2634
- const groupName = this.getFullName(group, separator);
2635
- const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
2636
- this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
2637
- 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);
2638
- for (const sibling of siblings) {
2639
- const number = (sibling.result.benchmark.mean / bench.result.benchmark.mean).toFixed(2);
2640
- this.log(c.green(` ${number}x `) + c.gray("faster than ") + sibling.name);
2641
- }
2642
- this.log("");
2643
- }
2644
- }
2645
- printTaskErrors(tasks, errorDivider) {
2646
- const errorsQueue = [];
2647
- for (const task of tasks)
2648
- // Merge identical errors
2649
- task.result?.errors?.forEach((error) => {
2650
- let previous;
2651
- if (error?.stack) previous = errorsQueue.find((i) => {
2652
- if (i[0]?.stack !== error.stack || i[0]?.diff !== error.diff) return false;
2653
- const currentProjectName = task?.projectName || task.file?.projectName || "";
2654
- const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
2655
- const currentAnnotations = task.type === "test" && task.annotations;
2656
- const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
2657
- return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
2658
- });
2659
- if (previous) previous[1].push(task);
2660
- else errorsQueue.push([error, [task]]);
2661
- });
2662
- for (const [error, tasks] of errorsQueue) {
2663
- for (const task of tasks) {
2664
- const filepath = task?.filepath || "";
2665
- const projectName = task?.projectName || task.file?.projectName || "";
2666
- const project = this.ctx.projects.find((p) => p.name === projectName);
2667
- let name = this.getFullName(task, separator);
2668
- if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
2669
- this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
2670
- }
2671
- const screenshotPaths = tasks.reduce((paths, t) => {
2672
- if (t.type === "test") {
2673
- for (const artifact of t.artifacts) if (artifact.type === "internal:failureScreenshot") {
2674
- if (artifact.attachments.length) paths.push(artifact.attachments[0].originalPath);
2675
- }
2676
- }
2677
- return paths;
2678
- }, []);
2679
- this.ctx.logger.printError(error, {
2680
- project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
2681
- verbose: this.verbose,
2682
- screenshotPaths,
2683
- task: tasks[0]
2684
- });
2685
- if (tasks[0].type === "test" && tasks[0].annotations.length) {
2686
- const test = this.ctx.state.getReportedEntity(tasks[0]);
2687
- this.printAnnotations(test, "error", 1);
2688
- this.error();
2689
- }
2690
- errorDivider();
2691
- }
2692
- }
2693
- }
2694
- function deepEqual(a, b) {
2695
- if (a === b) return true;
2696
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
2697
- const keysA = Object.keys(a);
2698
- const keysB = Object.keys(b);
2699
- if (keysA.length !== keysB.length) return false;
2700
- for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
2701
- return true;
2702
- }
2703
- function sum(items, cb) {
2704
- return items.reduce((total, next) => {
2705
- return total + Math.max(cb(next) || 0, 0);
2706
- }, 0);
2707
- }
2708
- function getIndentation(suite, level = 1) {
2709
- if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
2710
- return level;
2711
- }
2712
-
2713
- const DEFAULT_RENDER_INTERVAL_MS = 1e3;
2714
- const ESC = "\x1B[";
2715
- const CLEAR_LINE = `${ESC}K`;
2716
- const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
2717
- const SYNC_START = `${ESC}?2026h`;
2718
- const SYNC_END = `${ESC}?2026l`;
2719
- /**
2720
- * Renders content of `getWindow` at the bottom of the terminal and
2721
- * forwards all other intercepted `stdout` and `stderr` logs above it.
2722
- */
2723
- class WindowRenderer {
2724
- options;
2725
- streams;
2726
- buffer = [];
2727
- renderInterval = void 0;
2728
- renderScheduled = false;
2729
- windowHeight = 0;
2730
- started = false;
2731
- finished = false;
2732
- cleanups = [];
2733
- constructor(options) {
2734
- this.options = {
2735
- interval: DEFAULT_RENDER_INTERVAL_MS,
2736
- ...options
2737
- };
2738
- this.streams = {
2739
- output: options.logger.outputStream.write.bind(options.logger.outputStream),
2740
- error: options.logger.errorStream.write.bind(options.logger.errorStream)
2741
- };
2742
- this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
2743
- // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
2744
- this.options.logger.onTerminalCleanup(() => {
2745
- this.flushBuffer();
2746
- this.stop();
2747
- });
2748
- }
2749
- start() {
2750
- this.started = true;
2751
- this.finished = false;
2752
- this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
2753
- }
2754
- stop() {
2755
- this.cleanups.splice(0).map((fn) => fn());
2756
- clearInterval(this.renderInterval);
2757
- }
2758
- /**
2759
- * Write all buffered output and stop buffering.
2760
- * All intercepted writes are forwarded to actual write after this.
2761
- */
2762
- finish() {
2763
- this.finished = true;
2764
- this.flushBuffer();
2765
- clearInterval(this.renderInterval);
2766
- }
2767
- /**
2768
- * Queue new render update
2769
- */
2770
- schedule() {
2771
- if (!this.renderScheduled) {
2772
- this.renderScheduled = true;
2773
- this.flushBuffer();
2774
- setTimeout(() => {
2775
- this.renderScheduled = false;
2776
- }, 100).unref();
2777
- }
2778
- }
2779
- flushBuffer() {
2780
- if (this.buffer.length === 0) return this.render();
2781
- let current;
2782
- // Concatenate same types into a single render
2783
- for (const next of this.buffer.splice(0)) {
2784
- if (!current) {
2785
- current = next;
2786
- continue;
2787
- }
2788
- if (current.type !== next.type) {
2789
- this.render(current.message, current.type);
2790
- current = next;
2791
- continue;
2792
- }
2793
- current.message += next.message;
2794
- }
2795
- if (current) this.render(current?.message, current?.type);
2796
- }
2797
- render(message, type = "output") {
2798
- if (this.finished) {
2799
- this.clearWindow();
2800
- return this.write(message || "", type);
2801
- }
2802
- const windowContent = this.options.getWindow();
2803
- const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
2804
- let padding = this.windowHeight - rowCount;
2805
- if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
2806
- this.write(SYNC_START);
2807
- this.clearWindow();
2808
- if (message) this.write(message, type);
2809
- if (padding > 0) this.write("\n".repeat(padding));
2810
- this.write(windowContent.join("\n"));
2811
- this.write(SYNC_END);
2812
- this.windowHeight = rowCount + Math.max(0, padding);
2813
- }
2814
- clearWindow() {
2815
- if (this.windowHeight === 0) return;
2816
- this.write(CLEAR_LINE);
2817
- for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
2818
- this.windowHeight = 0;
2819
- }
2820
- interceptStream(stream, type) {
2821
- const original = stream.write;
2822
- // @ts-expect-error -- not sure how 2 overloads should be typed
2823
- stream.write = (chunk, _, callback) => {
2824
- if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
2825
- else this.buffer.push({
2826
- type,
2827
- message: chunk.toString()
2828
- });
2829
- callback?.();
2830
- };
2831
- return function restore() {
2832
- stream.write = original;
2833
- };
2834
- }
2835
- write(message, type = "output") {
2836
- this.streams[type](message);
2837
- }
2838
- }
2839
- /** Calculate the actual row count needed to render `rows` into `stream` */
2840
- function getRenderedRowCount(rows, columns) {
2841
- let count = 0;
2842
- for (const row of rows) {
2843
- const text = stripVTControlCharacters(row);
2844
- count += Math.max(1, Math.ceil(text.length / columns));
2845
- }
2846
- return count;
2847
- }
2848
-
2849
- const DURATION_UPDATE_INTERVAL_MS = 100;
2850
- const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
2851
- /**
2852
- * Reporter extension that renders summary and forwards all other logs above itself.
2853
- * Intended to be used by other reporters, not as a standalone reporter.
2854
- */
2855
- class SummaryReporter {
2856
- ctx;
2857
- options;
2858
- renderer;
2859
- modules = emptyCounters();
2860
- tests = emptyCounters();
2861
- maxParallelTests = 0;
2862
- /** Currently running test modules, may include finished test modules too */
2863
- runningModules = /* @__PURE__ */ new Map();
2864
- /** ID of finished `this.runningModules` that are currently being shown */
2865
- finishedModules = /* @__PURE__ */ new Map();
2866
- startTime = "";
2867
- currentTime = 0;
2868
- duration = 0;
2869
- durationInterval = void 0;
2870
- onInit(ctx, options = {}) {
2871
- this.ctx = ctx;
2872
- this.options = {
2873
- verbose: false,
2874
- ...options
2875
- };
2876
- this.renderer = new WindowRenderer({
2877
- logger: ctx.logger,
2878
- getWindow: () => this.createSummary()
2879
- });
2880
- this.ctx.onClose(() => {
2881
- clearInterval(this.durationInterval);
2882
- this.renderer.stop();
2883
- });
2884
- }
2885
- onTestRunStart(specifications) {
2886
- this.runningModules.clear();
2887
- this.finishedModules.clear();
2888
- this.modules = emptyCounters();
2889
- this.tests = emptyCounters();
2890
- this.startTimers();
2891
- this.renderer.start();
2892
- this.modules.total = specifications.length;
2893
- }
2894
- onTestRunEnd() {
2895
- this.runningModules.clear();
2896
- this.finishedModules.clear();
2897
- this.renderer.finish();
2898
- clearInterval(this.durationInterval);
2899
- }
2900
- onTestModuleQueued(module) {
2901
- // When new test module starts, take the place of previously finished test module, if any
2902
- if (this.finishedModules.size) {
2903
- const finished = this.finishedModules.keys().next().value;
2904
- this.removeTestModule(finished);
2905
- }
2906
- this.runningModules.set(module.id, initializeStats(module));
2907
- this.renderer.schedule();
2908
- }
2909
- onTestModuleCollected(module) {
2910
- let stats = this.runningModules.get(module.id);
2911
- if (!stats) {
2912
- stats = initializeStats(module);
2913
- this.runningModules.set(module.id, stats);
2914
- }
2915
- const total = Array.from(module.children.allTests()).length;
2916
- this.tests.total += total;
2917
- stats.total = total;
2918
- this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
2919
- this.renderer.schedule();
2920
- }
2921
- onHookStart(options) {
2922
- const stats = this.getHookStats(options);
2923
- if (!stats) return;
2924
- const hook = {
2925
- name: options.name,
2926
- visible: false,
2927
- startTime: performance.now(),
2928
- onFinish: () => {}
2929
- };
2930
- stats.hook?.onFinish?.();
2931
- stats.hook = hook;
2932
- const timeout = setTimeout(() => {
2933
- hook.visible = true;
2934
- }, this.ctx.config.slowTestThreshold).unref();
2935
- hook.onFinish = () => clearTimeout(timeout);
2936
- }
2937
- onHookEnd(options) {
2938
- const stats = this.getHookStats(options);
2939
- if (stats?.hook?.name !== options.name) return;
2940
- stats.hook.onFinish();
2941
- stats.hook.visible = false;
2942
- }
2943
- onTestCaseReady(test) {
2944
- // Track slow running tests only on verbose mode
2945
- if (!this.options.verbose) return;
2946
- const stats = this.runningModules.get(test.module.id);
2947
- if (!stats || stats.tests.has(test.id)) return;
2948
- const slowTest = {
2949
- name: test.name,
2950
- visible: false,
2951
- startTime: performance.now(),
2952
- onFinish: () => {}
2953
- };
2954
- const timeout = setTimeout(() => {
2955
- slowTest.visible = true;
2956
- }, this.ctx.config.slowTestThreshold).unref();
2957
- slowTest.onFinish = () => {
2958
- slowTest.hook?.onFinish();
2959
- clearTimeout(timeout);
2960
- };
2961
- stats.tests.set(test.id, slowTest);
2962
- }
2963
- onTestCaseResult(test) {
2964
- const stats = this.runningModules.get(test.module.id);
2965
- if (!stats) return;
2966
- stats.tests.get(test.id)?.onFinish();
2967
- stats.tests.delete(test.id);
2968
- stats.completed++;
2969
- const result = test.result();
2970
- if (result?.state === "passed")
2971
- // Check if this is an expected failure (test.fails && passed)
2972
- if (test.options.fails) this.tests.expectedFail++;
2973
- else this.tests.passed++;
2974
- else if (result?.state === "failed") this.tests.failed++;
2975
- else if (!result?.state || result?.state === "skipped") if (test.options.mode === "todo") this.tests.todo++;
2976
- else this.tests.skipped++;
2977
- this.renderer.schedule();
2978
- }
2979
- onTestModuleEnd(module) {
2980
- const state = module.state();
2981
- this.modules.completed++;
2982
- if (state === "passed") this.modules.passed++;
2983
- else if (state === "failed") this.modules.failed++;
2984
- else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
2985
- else if (state === "skipped") this.modules.skipped++;
2986
- // Keep finished tests visible in summary for a while if there are more tests left.
2987
- // When a new test starts in onTestModuleQueued it will take this ones place.
2988
- // This reduces flickering by making summary more stable.
2989
- if (this.modules.total - this.modules.completed > this.maxParallelTests) this.finishedModules.set(module.id, setTimeout(() => {
2990
- this.removeTestModule(module.id);
2991
- }, FINISHED_TEST_CLEANUP_TIME_MS).unref());
2992
- else
2993
- // Run is about to end as there are less tests left than whole run had parallel at max.
2994
- // Remove finished test immediately.
2995
- this.removeTestModule(module.id);
2996
- this.renderer.schedule();
2997
- }
2998
- getHookStats({ entity }) {
2999
- // Track slow running hooks only on verbose mode
3000
- if (!this.options.verbose) return;
3001
- const module = entity.type === "module" ? entity : entity.module;
3002
- const stats = this.runningModules.get(module.id);
3003
- if (!stats) return;
3004
- return entity.type === "test" ? stats.tests.get(entity.id) : stats;
3005
- }
3006
- createSummary() {
3007
- const summary = [""];
3008
- for (const testFile of Array.from(this.runningModules.values()).sort(sortRunningModules)) {
3009
- const typecheck = testFile.typecheck ? `${c.bgBlue(c.bold(" TS "))} ` : "";
3010
- summary.push(c.bold(c.yellow(` ${F_POINTER} `)) + formatProjectName({
3011
- name: testFile.projectName,
3012
- color: testFile.projectColor
3013
- }) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
3014
- const slowTasks = [testFile.hook, ...testFile.tests.values()].filter((t) => t != null && t.visible);
3015
- for (const [index, task] of slowTasks.entries()) {
3016
- const elapsed = this.currentTime - task.startTime;
3017
- const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
3018
- summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
3019
- if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
3020
- }
3021
- }
3022
- if (this.runningModules.size > 0) summary.push("");
3023
- summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
3024
- summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
3025
- summary.push(padSummaryTitle("Start at") + this.startTime);
3026
- summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
3027
- summary.push("");
3028
- return summary;
3029
- }
3030
- startTimers() {
3031
- const start = performance.now();
3032
- this.startTime = formatTimeString(/* @__PURE__ */ new Date());
3033
- this.durationInterval = setInterval(() => {
3034
- this.currentTime = performance.now();
3035
- this.duration = this.currentTime - start;
3036
- }, DURATION_UPDATE_INTERVAL_MS).unref();
3037
- }
3038
- removeTestModule(id) {
3039
- if (!id) return;
3040
- const testFile = this.runningModules.get(id);
3041
- testFile?.hook?.onFinish();
3042
- testFile?.tests?.forEach((test) => test.onFinish());
3043
- this.runningModules.delete(id);
3044
- clearTimeout(this.finishedModules.get(id));
3045
- this.finishedModules.delete(id);
3046
- }
3047
- }
3048
- function emptyCounters() {
3049
- return {
3050
- completed: 0,
3051
- passed: 0,
3052
- failed: 0,
3053
- skipped: 0,
3054
- todo: 0,
3055
- expectedFail: 0,
3056
- total: 0
3057
- };
3058
- }
3059
- function getStateString(entry) {
3060
- return [
3061
- entry.failed ? c.bold(c.red(`${entry.failed} failed`)) : null,
3062
- c.bold(c.green(`${entry.passed} passed`)),
3063
- entry.expectedFail ? c.cyan(`${entry.expectedFail} expected fail`) : null,
3064
- entry.skipped ? c.yellow(`${entry.skipped} skipped`) : null,
3065
- entry.todo ? c.gray(`${entry.todo} todo`) : null
3066
- ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
3067
- }
3068
- function sortRunningModules(a, b) {
3069
- if ((a.projectName || "") > (b.projectName || "")) return 1;
3070
- if ((a.projectName || "") < (b.projectName || "")) return -1;
3071
- return a.filename.localeCompare(b.filename);
3072
- }
3073
- function initializeStats(module) {
3074
- return {
3075
- total: 0,
3076
- completed: 0,
3077
- filename: module.task.name,
3078
- projectName: module.project.name,
3079
- projectColor: module.project.color,
3080
- tests: /* @__PURE__ */ new Map(),
3081
- typecheck: !!module.task.meta.typecheck
3082
- };
3083
- }
3084
-
3085
- class DefaultReporter extends BaseReporter {
3086
- options;
3087
- summary;
3088
- constructor(options = {}) {
3089
- super(options);
3090
- this.options = {
3091
- summary: true,
3092
- ...options
3093
- };
3094
- if (!this.isTTY) this.options.summary = false;
3095
- if (this.options.summary) this.summary = new SummaryReporter();
3096
- }
3097
- onTestRunStart(specifications) {
3098
- if (this.isTTY) {
3099
- if (this.renderSucceed === void 0) this.renderSucceed = !!this.renderSucceed;
3100
- if (this.renderSucceed !== true) this.renderSucceed = specifications.length <= 1;
3101
- }
3102
- super.onTestRunStart(specifications);
3103
- this.summary?.onTestRunStart(specifications);
3104
- }
3105
- onTestRunEnd(testModules, unhandledErrors, reason) {
3106
- super.onTestRunEnd(testModules, unhandledErrors, reason);
3107
- this.summary?.onTestRunEnd();
3108
- }
3109
- onTestModuleQueued(file) {
3110
- this.summary?.onTestModuleQueued(file);
3111
- }
3112
- onTestModuleCollected(module) {
3113
- this.summary?.onTestModuleCollected(module);
3114
- }
3115
- onTestModuleEnd(module) {
3116
- super.onTestModuleEnd(module);
3117
- this.summary?.onTestModuleEnd(module);
3118
- }
3119
- onTestCaseReady(test) {
3120
- this.summary?.onTestCaseReady(test);
3121
- }
3122
- onTestCaseResult(test) {
3123
- super.onTestCaseResult(test);
3124
- this.summary?.onTestCaseResult(test);
3125
- }
3126
- onHookStart(hook) {
3127
- this.summary?.onHookStart(hook);
3128
- }
3129
- onHookEnd(hook) {
3130
- this.summary?.onHookEnd(hook);
3131
- }
3132
- onInit(ctx) {
3133
- super.onInit(ctx);
3134
- this.summary?.onInit(ctx, { verbose: this.verbose });
3135
- }
3136
- }
3137
-
3138
- class DotReporter extends BaseReporter {
3139
- renderer;
3140
- tests = /* @__PURE__ */ new Map();
3141
- finishedTests = /* @__PURE__ */ new Set();
3142
- onInit(ctx) {
3143
- super.onInit(ctx);
3144
- if (this.isTTY) {
3145
- this.renderer = new WindowRenderer({
3146
- logger: ctx.logger,
3147
- getWindow: () => this.createSummary()
3148
- });
3149
- this.ctx.onClose(() => this.renderer?.stop());
3150
- }
3151
- }
3152
- // Ignore default logging of base reporter
3153
- printTestModule() {}
3154
- onTestRunStart(_specifications) {
3155
- super.onTestRunStart(_specifications);
3156
- this.renderer?.start();
3157
- }
3158
- onWatcherRerun(files, trigger) {
3159
- this.tests.clear();
3160
- this.renderer?.start();
3161
- super.onWatcherRerun(files, trigger);
3162
- }
3163
- onTestRunEnd(testModules, unhandledErrors, reason) {
3164
- if (this.isTTY) {
3165
- const finalLog = formatTests(Array.from(this.tests.values()));
3166
- this.ctx.logger.log(finalLog);
3167
- } else this.ctx.logger.log();
3168
- this.tests.clear();
3169
- this.renderer?.finish();
3170
- super.onTestRunEnd(testModules, unhandledErrors, reason);
3171
- }
3172
- onTestModuleCollected(module) {
3173
- for (const test of module.children.allTests())
3174
- // Dot reporter marks pending tests as running
3175
- this.onTestCaseReady(test);
3176
- }
3177
- onTestCaseReady(test) {
3178
- if (this.finishedTests.has(test.id)) return;
3179
- this.tests.set(test.id, test.result().state || "run");
3180
- this.renderer?.schedule();
3181
- }
3182
- onTestCaseResult(test) {
3183
- const result = test.result().state;
3184
- // On non-TTY the finished tests are printed immediately
3185
- if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
3186
- super.onTestCaseResult(test);
3187
- this.finishedTests.add(test.id);
3188
- this.tests.set(test.id, result || "skipped");
3189
- this.renderer?.schedule();
3190
- }
3191
- onTestModuleEnd(testModule) {
3192
- super.onTestModuleEnd(testModule);
3193
- if (!this.isTTY) return;
3194
- const columns = this.ctx.logger.getColumns();
3195
- if (this.tests.size < columns) return;
3196
- const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
3197
- if (finishedTests.length < columns) return;
3198
- // Remove finished tests from state and render them in static output
3199
- const states = [];
3200
- let count = 0;
3201
- for (const [id, state] of finishedTests) {
3202
- if (count++ >= columns) break;
3203
- this.tests.delete(id);
3204
- states.push(state);
3205
- }
3206
- this.ctx.logger.log(formatTests(states));
3207
- this.renderer?.schedule();
3208
- }
3209
- createSummary() {
3210
- return [formatTests(Array.from(this.tests.values())), ""];
3211
- }
3212
- }
3213
- // These are compared with reference equality in formatTests
3214
- const pass = {
3215
- char: "·",
3216
- color: c.green
3217
- };
3218
- const fail = {
3219
- char: "x",
3220
- color: c.red
3221
- };
3222
- const pending = {
3223
- char: "*",
3224
- color: c.yellow
3225
- };
3226
- const skip = {
3227
- char: "-",
3228
- color: (char) => c.dim(c.gray(char))
3229
- };
3230
- function getIcon(state) {
3231
- switch (state) {
3232
- case "passed": return pass;
3233
- case "failed": return fail;
3234
- case "skipped": return skip;
3235
- default: return pending;
3236
- }
3237
- }
3238
- /**
3239
- * Format test states into string while keeping ANSI escapes at minimal.
3240
- * Sibling icons with same color are merged into a single c.color() call.
3241
- */
3242
- function formatTests(states) {
3243
- let currentIcon = pending;
3244
- let count = 0;
3245
- let output = "";
3246
- for (const state of states) {
3247
- const icon = getIcon(state);
3248
- if (currentIcon === icon) {
3249
- count++;
3250
- continue;
3251
- }
3252
- output += currentIcon.color(currentIcon.char.repeat(count));
3253
- // Start tracking new group
3254
- count = 1;
3255
- currentIcon = icon;
3256
- }
3257
- output += currentIcon.color(currentIcon.char.repeat(count));
3258
- return output;
3259
- }
3260
-
3261
- const defaultOptions = {
3262
- onWritePath: defaultOnWritePath,
3263
- displayAnnotations: true,
3264
- jobSummary: {
3265
- enabled: true,
3266
- outputPath: process.env.GITHUB_STEP_SUMMARY,
3267
- fileLinks: {
3268
- repository: process.env.GITHUB_REPOSITORY,
3269
- commitHash: process.env.GITHUB_SHA,
3270
- workspacePath: process.env.GITHUB_WORKSPACE
3271
- }
3272
- }
3273
- };
3274
- class GithubActionsReporter {
3275
- ctx = void 0;
3276
- options;
3277
- constructor(options = {}) {
3278
- this.options = deepMerge(Object.create(null), defaultOptions, options);
3279
- }
3280
- onInit(ctx) {
3281
- this.ctx = ctx;
3282
- }
3283
- onTestCaseAnnotate(testCase, annotation) {
3284
- if (!annotation.location || this.options.displayAnnotations === false) return;
3285
- const type = getTitle(annotation.type);
3286
- const formatted = formatMessage({
3287
- command: getType(annotation.type),
3288
- properties: {
3289
- file: annotation.location.file,
3290
- line: String(annotation.location.line),
3291
- column: String(annotation.location.column),
3292
- ...type && { title: type }
3293
- },
3294
- message: stripVTControlCharacters(annotation.message)
3295
- });
3296
- this.ctx.logger.log(`\n${formatted}`);
3297
- }
3298
- onTestRunEnd(testModules, unhandledErrors) {
3299
- const files = testModules.map((testModule) => testModule.task);
3300
- const errors = [...unhandledErrors];
3301
- // collect all errors and associate them with projects
3302
- const projectErrors = new Array();
3303
- for (const error of errors) projectErrors.push({
3304
- project: this.ctx.getRootProject(),
3305
- title: "Unhandled error",
3306
- error
3307
- });
3308
- for (const file of files) {
3309
- const tasks = getTasks(file);
3310
- const project = this.ctx.getProjectByName(file.projectName || "");
3311
- for (const task of tasks) {
3312
- if (task.result?.state !== "fail") continue;
3313
- const title = getFullName(task, " > ");
3314
- for (const error of task.result?.errors ?? []) projectErrors.push({
3315
- project,
3316
- title: project.name ? `[${project.name}] ${title}` : title,
3317
- error,
3318
- file
3319
- });
3320
- }
3321
- }
3322
- // format errors via `printError`
3323
- for (const { project, title, error, file } of projectErrors) {
3324
- const result = capturePrintError(error, this.ctx, {
3325
- project,
3326
- task: file
3327
- });
3328
- const stack = result?.nearest;
3329
- if (!stack) continue;
3330
- const formatted = formatMessage({
3331
- command: "error",
3332
- properties: {
3333
- file: this.options.onWritePath(stack.file),
3334
- title,
3335
- line: String(stack.line),
3336
- column: String(stack.column)
3337
- },
3338
- message: stripVTControlCharacters(result.output)
3339
- });
3340
- this.ctx.logger.log(`\n${formatted}`);
3341
- }
3342
- if (this.options.jobSummary.enabled === true && this.options.jobSummary.outputPath) {
3343
- const summary = renderSummary(collectSummaryData(testModules), this.options.jobSummary.fileLinks);
3344
- try {
3345
- writeFileSync(this.options.jobSummary.outputPath, summary, { flag: "a" });
3346
- } catch (error) {
3347
- this.ctx.logger.warn("Could not write summary to `options.summary.outputPath`", error);
3348
- }
3349
- }
3350
- }
3351
- }
3352
- const BUILT_IN_TYPES = [
3353
- "notice",
3354
- "error",
3355
- "warning"
3356
- ];
3357
- function getTitle(type) {
3358
- if (BUILT_IN_TYPES.includes(type)) return;
3359
- return type;
3360
- }
3361
- function getType(type) {
3362
- if (BUILT_IN_TYPES.includes(type)) return type;
3363
- return "notice";
3364
- }
3365
- function defaultOnWritePath(path) {
3366
- return path;
3367
- }
3368
- // workflow command formatting based on
3369
- // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
3370
- // https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
3371
- function formatMessage({ command, properties, message }) {
3372
- let result = `::${command}`;
3373
- Object.entries(properties).forEach(([k, v], i) => {
3374
- result += i === 0 ? " " : ",";
3375
- result += `${k}=${escapeProperty(v)}`;
3376
- });
3377
- result += `::${escapeData(message)}`;
3378
- return result;
3379
- }
3380
- function escapeData(s) {
3381
- return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
3382
- }
3383
- function escapeProperty(s) {
3384
- return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
3385
- }
3386
- function collectSummaryData(testModules) {
3387
- const summaryData = {
3388
- fileStats: {
3389
- failed: 0,
3390
- passed: 0
3391
- },
3392
- testsStats: {
3393
- failed: 0,
3394
- passed: 0,
3395
- expectedFail: 0,
3396
- skipped: 0,
3397
- todo: 0
3398
- },
3399
- flakyTests: []
3400
- };
3401
- for (const module of testModules) {
3402
- const flakyTests = {
3403
- path: {
3404
- relative: module.relativeModuleId,
3405
- absolute: module.moduleId
3406
- },
3407
- tests: []
3408
- };
3409
- switch (module.task.result?.state) {
3410
- case "fail":
3411
- summaryData.fileStats.failed += 1;
3412
- break;
3413
- case "pass":
3414
- summaryData.fileStats.passed += 1;
3415
- break;
3416
- }
3417
- for (const test of module.children.allTests()) {
3418
- switch (test.task.mode) {
3419
- case "skip":
3420
- summaryData.testsStats.skipped += 1;
3421
- break;
3422
- case "todo":
3423
- summaryData.testsStats.todo += 1;
3424
- break;
3425
- default: switch (test.task.result?.state) {
3426
- case "fail":
3427
- summaryData.testsStats.failed += 1;
3428
- break;
3429
- case "pass":
3430
- if (test.task.fails) summaryData.testsStats.expectedFail += 1;
3431
- else summaryData.testsStats.passed += 1;
3432
- break;
3433
- }
3434
- }
3435
- const diagnostic = test.diagnostic();
3436
- if (diagnostic?.flaky) {
3437
- const retriesAllowed = typeof test.options.retry === "number" ? test.options.retry : test.options.retry?.count ?? diagnostic.retryCount;
3438
- const retriesRatio = diagnostic.retryCount / retriesAllowed;
3439
- flakyTests.tests.push({
3440
- retries: {
3441
- allowed: retriesAllowed,
3442
- count: diagnostic.retryCount,
3443
- ratio: retriesRatio
3444
- },
3445
- line: test.task.location?.line,
3446
- testName: test.task.fullTestName
3447
- });
3448
- }
3449
- }
3450
- if (flakyTests.tests.length > 0) {
3451
- flakyTests.tests.sort((a, b) => b.retries.ratio - a.retries.ratio);
3452
- summaryData.flakyTests.push(flakyTests);
3453
- }
3454
- }
3455
- return summaryData;
3456
- }
3457
- function createGitHubFileLinkCreator(fileLinks) {
3458
- const repository = fileLinks?.repository;
3459
- const commitHash = fileLinks?.commitHash;
3460
- const workspacePath = fileLinks?.workspacePath;
3461
- if (repository !== void 0 && commitHash !== void 0 && workspacePath !== void 0) return (path, line) => {
3462
- const lineFragment = line !== void 0 ? `#L${line}` : "";
3463
- return `https://github.com/${repository}/blob/${commitHash}/${relative(workspacePath, path)}${lineFragment}`;
3464
- };
3465
- return () => null;
3466
- }
3467
- function mdLink(text, url) {
3468
- return url === null ? text : `[${text}](${url})`;
3469
- }
3470
- function renderStats({ fileStats, testsStats }) {
3471
- const SEPARATOR_SYMBOL = " · ";
3472
- const fileInfoTotal = fileStats.failed + fileStats.passed;
3473
- const primaryInfoTotal = testsStats.failed + testsStats.passed + testsStats.expectedFail;
3474
- const secondaryInfoTotal = testsStats.skipped + testsStats.todo;
3475
- const fileInfo = [];
3476
- const primaryInfo = [];
3477
- const secondaryInfo = [];
3478
- if (fileStats.failed > 0) fileInfo.push(`❌ **${fileStats.failed} ${noun(fileStats.failed, "failure", "failures")}**`);
3479
- if (fileStats.passed > 0) fileInfo.push(`✅ **${fileStats.passed} ${noun(fileStats.passed, "pass", "passes")}**`);
3480
- fileInfo.push(`${fileInfoTotal} total`);
3481
- if (testsStats.failed > 0) primaryInfo.push(`❌ **${testsStats.failed} ${noun(testsStats.failed, "failure", "failures")}**`);
3482
- if (testsStats.passed > 0) primaryInfo.push(`✅ **${testsStats.passed} ${noun(testsStats.passed, "pass", "passes")}**`);
3483
- if (testsStats.expectedFail > 0) primaryInfo.push(`🔵 **${testsStats.expectedFail} expected ${noun(testsStats.expectedFail, "failure", "failures")}**`);
3484
- primaryInfo.push(`${primaryInfoTotal} total`);
3485
- if (testsStats.skipped > 0) secondaryInfo.push(`${testsStats.skipped} ${noun(testsStats.skipped, "skip", "skips")}`);
3486
- if (testsStats.todo > 0) secondaryInfo.push(`${testsStats.todo} ${noun(testsStats.todo, "todo", "todos")}`);
3487
- let output = `\n### Summary\n\n- **Test Files**: ${fileInfo.join(SEPARATOR_SYMBOL)}\n- **Test Results**: ${primaryInfo.join(SEPARATOR_SYMBOL)}\n`;
3488
- if (secondaryInfo.length > 0) {
3489
- secondaryInfo.push(`${secondaryInfoTotal} total`);
3490
- output += `- **Other**: ${secondaryInfo.join(SEPARATOR_SYMBOL)}\n`;
3491
- }
3492
- return output;
3493
- }
3494
- const SUMMARY_HEADER = "## Vitest Test Report\n";
3495
- function renderSummary(summaryData, fileLinks) {
3496
- const fileLinkCreator = createGitHubFileLinkCreator(fileLinks);
3497
- let summary = `${SUMMARY_HEADER}${renderStats(summaryData)}`;
3498
- if (summaryData.flakyTests.length > 0) {
3499
- summary += "\n### Flaky Tests\n\nThese tests passed only after one or more retries, indicating potential instability.\n";
3500
- for (const flakyTests of summaryData.flakyTests) {
3501
- summary += `\n##### \`${flakyTests.path.relative}\` (${flakyTests.tests.length} flaky tests)\n`;
3502
- for (const flakyTest of flakyTests.tests) {
3503
- const retriesText = `passed on retry ${flakyTest.retries.count} out of ${flakyTest.retries.allowed}`;
3504
- summary += `\n- ${mdLink(`\`${flakyTest.testName}\``, fileLinkCreator(flakyTests.path.absolute, flakyTest.line))} (${flakyTest.retries.ratio >= .8 ? `**${retriesText}**` : retriesText})`;
3505
- }
3506
- summary += "\n";
3507
- }
3508
- }
3509
- if (!summary.endsWith("\n")) summary += "\n";
3510
- return summary;
3511
- }
3512
-
3513
- const StatusMap = {
3514
- fail: "failed",
3515
- only: "pending",
3516
- pass: "passed",
3517
- run: "pending",
3518
- skip: "skipped",
3519
- todo: "todo",
3520
- queued: "pending"
3521
- };
3522
- class JsonReporter {
3523
- start = 0;
3524
- ctx;
3525
- options;
3526
- coverageMap;
3527
- constructor(options) {
3528
- this.options = options;
3529
- }
3530
- onInit(ctx) {
3531
- this.ctx = ctx;
3532
- this.start = Date.now();
3533
- this.coverageMap = void 0;
3534
- }
3535
- onCoverage(coverageMap) {
3536
- this.coverageMap = coverageMap;
3537
- }
3538
- async onTestRunEnd(testModules) {
3539
- const files = testModules.map((testModule) => testModule.task);
3540
- const suites = getSuites(files);
3541
- const numTotalTestSuites = suites.length;
3542
- const tests = getTests(files);
3543
- const numTotalTests = tests.length;
3544
- const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
3545
- const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
3546
- const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
3547
- const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
3548
- const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
3549
- const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
3550
- const numTodoTests = tests.filter((t) => t.mode === "todo").length;
3551
- const testResults = [];
3552
- const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
3553
- const { filterMeta } = this.options;
3554
- for (const file of files) {
3555
- const tests = getTests([file]);
3556
- let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
3557
- if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
3558
- const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime);
3559
- const assertionResults = tests.map((t) => {
3560
- const ancestorTitles = [];
3561
- let iter = t.suite;
3562
- while (iter) {
3563
- ancestorTitles.push(iter.name);
3564
- iter = iter.suite;
3565
- }
3566
- ancestorTitles.reverse();
3567
- return {
3568
- ancestorTitles,
3569
- fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
3570
- status: StatusMap[t.result?.state || t.mode] || "skipped",
3571
- title: t.name,
3572
- duration: t.result?.duration,
3573
- failureMessages: t.result?.errors?.map((e) => e.stack || e.message) || [],
3574
- location: t.location,
3575
- meta: filterMeta ? (() => {
3576
- const filtered = {};
3577
- for (const key in t.meta) {
3578
- const value = t.meta[key];
3579
- if (filterMeta(key, value)) filtered[key] = value;
3580
- }
3581
- return filtered;
3582
- })() : t.meta,
3583
- tags: t.tags || []
3584
- };
3585
- });
3586
- if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued")) this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report.This is likely an internal bug in Vitest.Please report it to https://github.com/vitest-dev/vitest/issues");
3587
- const hasFailedTests = tests.some((t) => t.result?.state === "fail");
3588
- testResults.push({
3589
- assertionResults,
3590
- startTime,
3591
- endTime,
3592
- status: file.result?.state === "fail" || hasFailedTests ? "failed" : "passed",
3593
- message: file.result?.errors?.[0]?.message ?? "",
3594
- name: file.filepath
3595
- });
3596
- }
3597
- const result = {
3598
- numTotalTestSuites,
3599
- numPassedTestSuites,
3600
- numFailedTestSuites,
3601
- numPendingTestSuites,
3602
- numTotalTests,
3603
- numPassedTests,
3604
- numFailedTests,
3605
- numPendingTests,
3606
- numTodoTests,
3607
- snapshot: this.ctx.snapshot.summary,
3608
- startTime: this.start,
3609
- success,
3610
- testResults,
3611
- coverageMap: this.coverageMap
3612
- };
3613
- await this.writeReport(JSON.stringify(result));
3614
- }
3615
- /**
3616
- * Writes the report to an output file if specified in the config,
3617
- * or logs it to the console otherwise.
3618
- * @param report
3619
- */
3620
- async writeReport(report) {
3621
- const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
3622
- if (outputFile) {
3623
- const reportFile = resolve$1(this.ctx.config.root, outputFile);
3624
- const outputDirectory = dirname(reportFile);
3625
- if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
3626
- await promises.writeFile(reportFile, report, "utf-8");
3627
- this.ctx.logger.log(`JSON report written to ${reportFile}`);
3628
- } else this.ctx.logger.log(report);
3629
- }
3630
- }
3631
-
3632
- class IndentedLogger {
3633
- currentIndent = "";
3634
- constructor(baseLog) {
3635
- this.baseLog = baseLog;
3636
- }
3637
- indent() {
3638
- this.currentIndent += " ";
3639
- }
3640
- unindent() {
3641
- this.currentIndent = this.currentIndent.substring(0, this.currentIndent.length - 4);
3642
- }
3643
- log(text) {
3644
- return this.baseLog(this.currentIndent + text);
3645
- }
3646
- }
3647
-
3648
- function flattenTasks$1(task, baseName = "") {
3649
- const base = baseName ? `${baseName} > ` : "";
3650
- if (task.type === "suite") return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
3651
- else return [{
3652
- ...task,
3653
- name: `${base}${task.name}`
3654
- }];
3655
- }
3656
- // https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
3657
- function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
3658
- let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
3659
- value = String(value || "").replace(regex, "");
3660
- {
3661
- // remove everything discouraged by XML 1.0 specifications
3662
- regex = new RegExp(
3663
- /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
3664
- "([\\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]))",
3665
- "g"
3666
- /* eslint-enable */
3667
- );
3668
- value = value.replace(regex, "");
3669
- }
3670
- return value;
3671
- }
3672
- function escapeXML(value) {
3673
- return removeInvalidXMLCharacters(String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
3674
- }
3675
- function executionTime(durationMS) {
3676
- return (durationMS / 1e3).toLocaleString("en-US", {
3677
- useGrouping: false,
3678
- maximumFractionDigits: 10
3679
- });
3680
- }
3681
- function getDuration(task) {
3682
- return executionTime(task.result?.duration ?? 0);
3683
- }
3684
- class JUnitReporter {
3685
- ctx;
3686
- reportFile;
3687
- baseLog;
3688
- logger;
3689
- _timeStart = /* @__PURE__ */ new Date();
3690
- fileFd;
3691
- options;
3692
- constructor(options) {
3693
- this.options = { ...options };
3694
- this.options.includeConsoleOutput ??= true;
3695
- }
3696
- async onInit(ctx) {
3697
- this.ctx = ctx;
3698
- const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "junit");
3699
- if (outputFile) {
3700
- this.reportFile = resolve$1(this.ctx.config.root, outputFile);
3701
- const outputDirectory = dirname(this.reportFile);
3702
- if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
3703
- this.fileFd = await promises.open(this.reportFile, "w+");
3704
- this.baseLog = async (text) => {
3705
- if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
3706
- await promises.writeFile(this.fileFd, `${text}\n`);
3707
- };
3708
- } else this.baseLog = async (text) => this.ctx.logger.log(text);
3709
- this._timeStart = /* @__PURE__ */ new Date();
3710
- this.logger = new IndentedLogger(this.baseLog);
3711
- }
3712
- async writeElement(name, attrs, children) {
3713
- const pairs = [];
3714
- for (const key in attrs) {
3715
- const attr = attrs[key];
3716
- if (attr === void 0) continue;
3717
- pairs.push(`${key}="${escapeXML(attr)}"`);
3718
- }
3719
- await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
3720
- this.logger.indent();
3721
- await children.call(this);
3722
- this.logger.unindent();
3723
- await this.logger.log(`</${name}>`);
3724
- }
3725
- async writeLogs(task, type) {
3726
- if (task.logs == null || task.logs.length === 0) return;
3727
- const logType = type === "err" ? "stderr" : "stdout";
3728
- const logs = task.logs.filter((log) => log.type === logType);
3729
- if (logs.length === 0) return;
3730
- await this.writeElement(`system-${type}`, {}, async () => {
3731
- for (const log of logs) await this.baseLog(escapeXML(log.content));
3732
- });
3733
- }
3734
- async writeTasks(tasks, filename) {
3735
- for (const task of tasks) {
3736
- let classname = filename;
3737
- const templateVars = {
3738
- filename: task.file.name,
3739
- filepath: task.file.filepath
3740
- };
3741
- if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
3742
- else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
3743
- await this.writeElement("testcase", {
3744
- classname,
3745
- file: this.options.addFileAttribute ? filename : void 0,
3746
- name: task.name,
3747
- time: getDuration(task)
3748
- }, async () => {
3749
- if (this.options.includeConsoleOutput) {
3750
- await this.writeLogs(task, "out");
3751
- await this.writeLogs(task, "err");
3752
- }
3753
- if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
3754
- if (task.type === "test" && task.annotations.length) {
3755
- await this.logger.log("<properties>");
3756
- this.logger.indent();
3757
- for (const annotation of task.annotations) {
3758
- await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
3759
- await this.logger.log("</property>");
3760
- }
3761
- this.logger.unindent();
3762
- await this.logger.log("</properties>");
3763
- }
3764
- if (task.result?.state === "fail") {
3765
- const errors = task.result.errors || [];
3766
- for (const error of errors) await this.writeElement("failure", {
3767
- message: error?.message,
3768
- type: error?.name
3769
- }, async () => {
3770
- if (!error) return;
3771
- const result = capturePrintError(error, this.ctx, {
3772
- project: this.ctx.getProjectByName(task.file.projectName || ""),
3773
- task
3774
- });
3775
- await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
3776
- });
3777
- }
3778
- });
3779
- }
3780
- }
3781
- async onTestRunEnd(testModules) {
3782
- const files = testModules.map((testModule) => testModule.task);
3783
- await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
3784
- const transformed = files.map((file) => {
3785
- const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
3786
- const stats = tasks.reduce((stats, task) => {
3787
- return {
3788
- passed: stats.passed + Number(task.result?.state === "pass"),
3789
- failures: stats.failures + Number(task.result?.state === "fail"),
3790
- skipped: stats.skipped + Number(task.mode === "skip" || task.mode === "todo")
3791
- };
3792
- }, {
3793
- passed: 0,
3794
- failures: 0,
3795
- skipped: 0
3796
- });
3797
- // inject failed suites to surface errors during beforeAll/afterAll
3798
- const suites = getSuites(file);
3799
- for (const suite of suites) if (suite.result?.errors) {
3800
- tasks.push(suite);
3801
- stats.failures += 1;
3802
- }
3803
- // If there are no tests, but the file failed to load, we still want to report it as a failure
3804
- if (tasks.length === 0 && file.result?.state === "fail") {
3805
- stats.failures = 1;
3806
- tasks.push({
3807
- id: file.id,
3808
- type: "test",
3809
- name: file.name,
3810
- fullName: file.name,
3811
- fullTestName: file.name,
3812
- mode: "run",
3813
- result: file.result,
3814
- meta: {},
3815
- timeout: 0,
3816
- context: null,
3817
- suite: null,
3818
- file: null,
3819
- annotations: [],
3820
- artifacts: []
3821
- });
3822
- }
3823
- return {
3824
- ...file,
3825
- tasks,
3826
- stats
3827
- };
3828
- });
3829
- const stats = transformed.reduce((stats, file) => {
3830
- stats.tests += file.tasks.length;
3831
- stats.failures += file.stats.failures;
3832
- stats.time += file.result?.duration || 0;
3833
- return stats;
3834
- }, {
3835
- name: this.options.suiteName || "vitest tests",
3836
- tests: 0,
3837
- failures: 0,
3838
- errors: 0,
3839
- time: 0
3840
- });
3841
- await this.writeElement("testsuites", {
3842
- ...stats,
3843
- time: executionTime(stats.time)
3844
- }, async () => {
3845
- for (const file of transformed) {
3846
- const filename = relative(this.ctx.config.root, file.filepath);
3847
- await this.writeElement("testsuite", {
3848
- name: filename,
3849
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3850
- hostname: this.options.hostname || hostname(),
3851
- tests: file.tasks.length,
3852
- failures: file.stats.failures,
3853
- errors: 0,
3854
- skipped: file.stats.skipped,
3855
- time: getDuration(file)
3856
- }, async () => {
3857
- await this.writeTasks(file.tasks, filename);
3858
- });
3859
- }
3860
- });
3861
- if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
3862
- await this.fileFd?.close();
3863
- this.fileFd = void 0;
3864
- }
3865
- }
3866
-
3867
- class MinimalReporter extends DefaultReporter {
3868
- renderSucceed = false;
3869
- constructor(options = {}) {
3870
- super({
3871
- silent: "passed-only",
3872
- ...options,
3873
- summary: false
3874
- });
3875
- }
3876
- onTestRunStart(specifications) {
3877
- super.onTestRunStart(specifications);
3878
- this.renderSucceed = false;
3879
- }
3880
- printTestModule(testModule) {
3881
- if (testModule.state() !== "failed") return;
3882
- super.printTestModule(testModule);
3883
- }
3884
- printTestCase(moduleState, test) {
3885
- if (test.result().state === "failed") super.printTestCase(moduleState, test);
3886
- }
3887
- }
3888
-
3889
- function yamlString(str) {
3890
- if (!str) return "";
3891
- return `"${str.replace(/"/g, "\\\"")}"`;
3892
- }
3893
- function tapString(str) {
3894
- return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
3895
- }
3896
- class TapReporter {
3897
- ctx;
3898
- logger;
3899
- onInit(ctx) {
3900
- this.ctx = ctx;
3901
- this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
3902
- }
3903
- static getComment(task) {
3904
- if (task.mode === "skip") return " # SKIP";
3905
- else if (task.mode === "todo") return " # TODO";
3906
- else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
3907
- else return "";
3908
- }
3909
- logErrorDetails(error, stack) {
3910
- const errorName = error.name || "Unknown Error";
3911
- this.logger.log(`name: ${yamlString(String(errorName))}`);
3912
- this.logger.log(`message: ${yamlString(String(error.message))}`);
3913
- if (stack)
3914
- // For compatibility with tap-mocha-reporter
3915
- this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
3916
- }
3917
- logTasks(tasks) {
3918
- this.logger.log(`1..${tasks.length}`);
3919
- for (const [i, task] of tasks.entries()) {
3920
- const id = i + 1;
3921
- const ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok";
3922
- const comment = TapReporter.getComment(task);
3923
- if (task.type === "suite" && task.tasks.length > 0) {
3924
- this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
3925
- this.logger.indent();
3926
- this.logTasks(task.tasks);
3927
- this.logger.unindent();
3928
- this.logger.log("}");
3929
- } else {
3930
- this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
3931
- const project = this.ctx.getProjectByName(task.file.projectName || "");
3932
- if (task.type === "test" && task.annotations) {
3933
- this.logger.indent();
3934
- task.annotations.forEach(({ type, message }) => {
3935
- this.logger.log(`# ${type}: ${message}`);
3936
- });
3937
- this.logger.unindent();
3938
- }
3939
- if (task.result?.state === "fail" && task.result.errors) {
3940
- this.logger.indent();
3941
- task.result.errors.forEach((error) => {
3942
- const stack = (task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }))[0];
3943
- this.logger.log("---");
3944
- this.logger.log("error:");
3945
- this.logger.indent();
3946
- this.logErrorDetails(error);
3947
- this.logger.unindent();
3948
- if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
3949
- if (error.showDiff) {
3950
- this.logger.log(`actual: ${yamlString(error.actual)}`);
3951
- this.logger.log(`expected: ${yamlString(error.expected)}`);
3952
- }
3953
- });
3954
- this.logger.log("...");
3955
- this.logger.unindent();
3956
- }
3957
- }
3958
- }
3959
- }
3960
- onTestRunEnd(testModules) {
3961
- const files = testModules.map((testModule) => testModule.task);
3962
- this.logger.log("TAP version 13");
3963
- this.logTasks(files);
3964
- }
3965
- }
3966
-
3967
- function flattenTasks(task, baseName = "") {
3968
- const base = baseName ? `${baseName} > ` : "";
3969
- if (task.type === "suite" && task.tasks.length > 0) return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
3970
- else return [{
3971
- ...task,
3972
- name: `${base}${task.name}`
3973
- }];
3974
- }
3975
- class TapFlatReporter extends TapReporter {
3976
- onInit(ctx) {
3977
- super.onInit(ctx);
3978
- }
3979
- onTestRunEnd(testModules) {
3980
- this.ctx.logger.log("TAP version 13");
3981
- const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
3982
- this.logTasks(flatTasks);
3983
- }
3984
- }
3985
-
3986
- class TreeReporter extends DefaultReporter {
3987
- verbose = true;
3988
- renderSucceed = true;
3989
- }
3990
-
3991
- class VerboseReporter extends DefaultReporter {
3992
- verbose = true;
3993
- renderSucceed = true;
3994
- printTestModule(_module) {
3995
- // don't print test module, only print tests
3996
- }
3997
- onTestCaseResult(test) {
3998
- super.onTestCaseResult(test);
3999
- const testResult = test.result();
4000
- if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") return;
4001
- let title = ` ${this.getEntityPrefix(test)} `;
4002
- title += test.module.task.name;
4003
- if (test.location) title += c.dim(`:${test.location.line}:${test.location.column}`);
4004
- title += separator;
4005
- title += getTestName(test.task, separator);
4006
- title += this.getTestCaseSuffix(test);
4007
- this.log(title);
4008
- if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error.message}`)));
4009
- if (test.annotations().length) {
4010
- this.log();
4011
- this.printAnnotations(test, "log", 3);
4012
- this.log();
4013
- }
4014
- }
4015
- }
4016
-
4017
- function createBenchmarkJsonReport(files) {
4018
- const report = { files: [] };
4019
- for (const file of files) {
4020
- const groups = [];
4021
- for (const task of getTasks(file)) if (task?.type === "suite") {
4022
- const benchmarks = [];
4023
- for (const t of task.tasks) {
4024
- const benchmark = t.meta.benchmark && t.result?.benchmark;
4025
- if (benchmark) benchmarks.push({
4026
- id: t.id,
4027
- ...benchmark,
4028
- samples: []
4029
- });
4030
- }
4031
- if (benchmarks.length) groups.push({
4032
- fullName: getFullName(task, " > "),
4033
- benchmarks
4034
- });
4035
- }
4036
- report.files.push({
4037
- filepath: file.filepath,
4038
- groups
4039
- });
4040
- }
4041
- return report;
4042
- }
4043
- function flattenFormattedBenchmarkReport(report) {
4044
- const flat = {};
4045
- for (const file of report.files) for (const group of file.groups) for (const t of group.benchmarks) flat[t.id] = t;
4046
- return flat;
4047
- }
4048
-
4049
- const outputMap = /* @__PURE__ */ new WeakMap();
4050
- function formatNumber(number) {
4051
- const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
4052
- return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
4053
- }
4054
- const tableHead = [
4055
- "name",
4056
- "hz",
4057
- "min",
4058
- "max",
4059
- "mean",
4060
- "p75",
4061
- "p99",
4062
- "p995",
4063
- "p999",
4064
- "rme",
4065
- "samples"
4066
- ];
4067
- function renderBenchmarkItems(result) {
4068
- return [
4069
- result.name,
4070
- formatNumber(result.hz || 0),
4071
- formatNumber(result.min || 0),
4072
- formatNumber(result.max || 0),
4073
- formatNumber(result.mean || 0),
4074
- formatNumber(result.p75 || 0),
4075
- formatNumber(result.p99 || 0),
4076
- formatNumber(result.p995 || 0),
4077
- formatNumber(result.p999 || 0),
4078
- `±${(result.rme || 0).toFixed(2)}%`,
4079
- (result.sampleCount || 0).toString()
4080
- ];
4081
- }
4082
- function computeColumnWidths(results) {
4083
- const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
4084
- return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
4085
- }
4086
- function padRow(row, widths) {
4087
- return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
4088
- }
4089
- function renderTableHead(widths) {
4090
- return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
4091
- }
4092
- function renderBenchmark(result, widths) {
4093
- const padded = padRow(renderBenchmarkItems(result), widths);
4094
- return [
4095
- padded[0],
4096
- c.blue(padded[1]),
4097
- c.cyan(padded[2]),
4098
- c.cyan(padded[3]),
4099
- c.cyan(padded[4]),
4100
- c.cyan(padded[5]),
4101
- c.cyan(padded[6]),
4102
- c.cyan(padded[7]),
4103
- c.cyan(padded[8]),
4104
- c.dim(padded[9]),
4105
- c.dim(padded[10])
4106
- ].join(" ");
4107
- }
4108
- function renderTable(options) {
4109
- const output = [];
4110
- const benchMap = {};
4111
- for (const task of options.tasks) if (task.meta.benchmark && task.result?.benchmark) benchMap[task.id] = {
4112
- current: task.result.benchmark,
4113
- baseline: options.compare?.[task.id]
4114
- };
4115
- const benchCount = Object.entries(benchMap).length;
4116
- const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
4117
- let idx = 0;
4118
- const padding = " ".repeat(1 );
4119
- for (const task of options.tasks) {
4120
- const duration = task.result?.duration;
4121
- const bench = benchMap[task.id];
4122
- let prefix = "";
4123
- if (idx === 0 && task.meta?.benchmark) prefix += `${renderTableHead(columnWidths)}\n${padding}`;
4124
- prefix += ` ${getStateSymbol(task)} `;
4125
- let suffix = "";
4126
- if (task.type === "suite") suffix += c.dim(` (${getTests(task).length})`);
4127
- if (task.mode === "skip" || task.mode === "todo") suffix += c.dim(c.gray(" [skipped]"));
4128
- if (duration != null) {
4129
- const color = duration > options.slowTestThreshold ? c.yellow : c.green;
4130
- suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
4131
- }
4132
- if (options.showHeap && task.result?.heap != null) suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
4133
- if (bench) {
4134
- let body = renderBenchmark(bench.current, columnWidths);
4135
- if (options.compare && bench.baseline) {
4136
- if (bench.current.hz) {
4137
- const diff = bench.current.hz / bench.baseline.hz;
4138
- const diffFixed = diff.toFixed(2);
4139
- if (diffFixed === "1.0.0") body += c.gray(` [${diffFixed}x]`);
4140
- if (diff > 1) body += c.blue(` [${diffFixed}x] ⇑`);
4141
- else body += c.red(` [${diffFixed}x] ⇓`);
4142
- }
4143
- output.push(padding + prefix + body + suffix);
4144
- const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
4145
- output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
4146
- } else {
4147
- if (bench.current.rank === 1 && benchCount > 1) body += c.bold(c.green(" fastest"));
4148
- if (bench.current.rank === benchCount && benchCount > 2) body += c.bold(c.gray(" slowest"));
4149
- output.push(padding + prefix + body + suffix);
4150
- }
4151
- } else output.push(padding + prefix + task.name + suffix);
4152
- if (task.result?.state !== "pass" && outputMap.get(task) != null) {
4153
- let data = outputMap.get(task);
4154
- if (typeof data === "string") {
4155
- data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
4156
- if (data === "") data = void 0;
4157
- }
4158
- if (data != null) {
4159
- const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
4160
- output.push(c.gray(truncateString(out, options.columns)));
4161
- }
4162
- }
4163
- idx++;
4164
- }
4165
- return output.filter(Boolean).join("\n");
4166
- }
4167
-
4168
- class BenchmarkReporter extends DefaultReporter {
4169
- compare;
4170
- async onInit(ctx) {
4171
- super.onInit(ctx);
4172
- if (this.ctx.config.benchmark?.compare) {
4173
- const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
4174
- try {
4175
- this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs.promises.readFile(compareFile, "utf-8")));
4176
- } catch (e) {
4177
- this.error(`Failed to read '${compareFile}'`, e);
4178
- }
4179
- }
4180
- }
4181
- onTaskUpdate(packs) {
4182
- for (const pack of packs) {
4183
- const task = this.ctx.state.idMap.get(pack[0]);
4184
- if (task?.type === "suite" && task.result?.state !== "run") task.tasks.filter((task) => task.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
4185
- bench.result.benchmark.rank = Number(idx) + 1;
4186
- });
4187
- }
4188
- }
4189
- onTestSuiteResult(testSuite) {
4190
- super.onTestSuiteResult(testSuite);
4191
- this.printSuiteTable(testSuite);
4192
- }
4193
- printTestModule(testModule) {
4194
- this.printSuiteTable(testModule);
4195
- }
4196
- printSuiteTable(testTask) {
4197
- const state = testTask.state();
4198
- if (state === "pending" || state === "queued") return;
4199
- const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
4200
- const duration = testTask.task.result?.duration || 0;
4201
- if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
4202
- let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, separator)}`;
4203
- if (duration != null && duration > this.ctx.config.slowTestThreshold) title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
4204
- this.log(title);
4205
- this.log(renderTable({
4206
- tasks: benches,
4207
- level: 1,
4208
- columns: this.ctx.logger.getColumns(),
4209
- compare: this.compare,
4210
- showHeap: this.ctx.config.logHeapUsage,
4211
- slowTestThreshold: this.ctx.config.slowTestThreshold
4212
- }));
4213
- }
4214
- }
4215
- async onTestRunEnd(testModules, unhandledErrors, reason) {
4216
- super.onTestRunEnd(testModules, unhandledErrors, reason);
4217
- // write output for future comparison
4218
- let outputFile = this.ctx.config.benchmark?.outputJson;
4219
- if (outputFile) {
4220
- outputFile = pathe.resolve(this.ctx.config.root, outputFile);
4221
- const outputDirectory = pathe.dirname(outputFile);
4222
- if (!fs.existsSync(outputDirectory)) await fs.promises.mkdir(outputDirectory, { recursive: true });
4223
- const output = createBenchmarkJsonReport(testModules.map((t) => t.task.file));
4224
- await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
4225
- this.log(`Benchmark report written to ${outputFile}`);
4226
- }
4227
- }
4228
- }
4229
-
4230
- class VerboseBenchmarkReporter extends BenchmarkReporter {
4231
- verbose = true;
4232
- }
4233
-
4234
- const BenchmarkReportsMap = {
4235
- default: BenchmarkReporter,
4236
- verbose: VerboseBenchmarkReporter
4237
- };
4238
-
4239
- const ReportersMap = {
4240
- "default": DefaultReporter,
4241
- "agent": MinimalReporter,
4242
- "minimal": MinimalReporter,
4243
- "blob": BlobReporter,
4244
- "verbose": VerboseReporter,
4245
- "dot": DotReporter,
4246
- "json": JsonReporter,
4247
- "tap": TapReporter,
4248
- "tap-flat": TapFlatReporter,
4249
- "junit": JUnitReporter,
4250
- "tree": TreeReporter,
4251
- "hanging-process": HangingProcessReporter,
4252
- "github-actions": GithubActionsReporter
4253
- };
4254
-
4255
- export { BenchmarkReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JUnitReporter as J, MinimalReporter as M, ReportersMap as R, TapFlatReporter as T, VerboseBenchmarkReporter as V, BenchmarkReportsMap as a, DotReporter as b, JsonReporter as c, TapReporter as d, VerboseReporter as e, createIndexLocationsMap as f, TraceMap as g, ancestor as h, printError as i, Typechecker as j, generateCodeFrame as k, escapeRegExp as l, createDefinesScript as m, groupBy as n, originalPositionFor as o, parse as p, BlobReporter as q, readBlobs as r, stringify as s, convertTasksToEvents as t, stdout as u, wildcardPatternToRegExp as w };