vitest 2.2.0-beta.1 → 3.0.0-beta.1

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 (51) hide show
  1. package/dist/browser.d.ts +8 -8
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{RandomSequencer.CMRlh2v4.js → RandomSequencer.gisBJ77r.js} +11 -3
  4. package/dist/chunks/{base.BS0HhLXd.js → base.CkcgFVQd.js} +8 -3
  5. package/dist/chunks/{benchmark.geERunq4.d.ts → benchmark.CFFwLv-O.d.ts} +2 -2
  6. package/dist/chunks/{cac.Z91LBqmg.js → cac.CWCZimpS.js} +7 -7
  7. package/dist/chunks/{cli-api.DVJJMJHj.js → cli-api.BKUOv0Nc.js} +928 -661
  8. package/dist/chunks/{config.CPguQ7J1.d.ts → config.BTPBhmK5.d.ts} +1 -1
  9. package/dist/chunks/{creator.IIqd8RWT.js → creator.DcAcUhMD.js} +1 -4
  10. package/dist/chunks/{globals.BCGEw6ON.js → globals.DJTzb7B3.js} +2 -2
  11. package/dist/chunks/{index.DD5eTY2y.js → index.BqHViJW9.js} +1 -1
  12. package/dist/chunks/{index.BjjsHdBb.js → index.CkOJwybT.js} +1 -1
  13. package/dist/chunks/{index.DLRzErGF.js → index.DKe7vK-G.js} +708 -548
  14. package/dist/chunks/{index.CqYx2Nsr.js → index.DQboAxJm.js} +23 -14
  15. package/dist/chunks/{inspector.70d6emsh.js → inspector.DKLceBVD.js} +1 -1
  16. package/dist/chunks/{reporters.B_9uUTGW.d.ts → reporters.BZbwTvrM.d.ts} +1252 -1234
  17. package/dist/chunks/{resolveConfig.CQIc6fe7.js → resolveConfig.3rGGWga5.js} +88 -56
  18. package/dist/chunks/{runBaseTests.B7hcVT-s.js → runBaseTests.C6huCAng.js} +6 -6
  19. package/dist/chunks/{setup-common.BfGt8K-K.js → setup-common.B5ClyS48.js} +1 -1
  20. package/dist/chunks/{suite.B2jumIFP.d.ts → suite.BJU7kdY9.d.ts} +4 -4
  21. package/dist/chunks/{utils.DNoFbBUZ.js → utils.CMUTX-p8.js} +20 -23
  22. package/dist/chunks/{vi.BlPttogV.js → vi.CZKezqeD.js} +18 -13
  23. package/dist/chunks/{vite.Bvms8Xir.d.ts → vite.DIfmneq0.d.ts} +1 -1
  24. package/dist/chunks/{vm.Zr4qWzDJ.js → vm.DGhTouO3.js} +10 -1
  25. package/dist/chunks/{worker.9VY11NZs.d.ts → worker.CmzGeuVD.d.ts} +3 -3
  26. package/dist/chunks/{worker.Qz1UB4Fv.d.ts → worker.umPNbBNk.d.ts} +1 -1
  27. package/dist/cli.js +1 -1
  28. package/dist/config.cjs +1 -10
  29. package/dist/config.d.ts +11 -11
  30. package/dist/config.js +1 -10
  31. package/dist/coverage.d.ts +7 -7
  32. package/dist/coverage.js +4 -4
  33. package/dist/execute.d.ts +3 -3
  34. package/dist/index.d.ts +26 -17
  35. package/dist/index.js +2 -2
  36. package/dist/node.d.ts +16 -20
  37. package/dist/node.js +7 -7
  38. package/dist/reporters.d.ts +7 -7
  39. package/dist/reporters.js +3 -3
  40. package/dist/runners.d.ts +3 -4
  41. package/dist/runners.js +9 -14
  42. package/dist/suite.d.ts +2 -2
  43. package/dist/worker.js +1 -1
  44. package/dist/workers/forks.js +1 -1
  45. package/dist/workers/runVmTests.js +6 -6
  46. package/dist/workers/threads.js +1 -1
  47. package/dist/workers/vmForks.js +1 -1
  48. package/dist/workers/vmThreads.js +1 -1
  49. package/dist/workers.d.ts +3 -3
  50. package/dist/workers.js +3 -3
  51. package/package.json +22 -22
@@ -3,12 +3,12 @@ import { getTests, getTestName, hasFailed, getFullName, getSuites, getTasks } fr
3
3
  import * as pathe from 'pathe';
4
4
  import { extname, relative, normalize, resolve, dirname } from 'pathe';
5
5
  import c from 'tinyrainbow';
6
- import { d as divider, F as F_POINTER, w as withLabel, f as formatProjectName, a as formatTimeString, g as getStateSymbol, t as taskFail, b as F_RIGHT, c as F_CHECK, r as renderSnapshotSummary, e as getStateString, h as countTestErrors, i as getCols, j as getHookStateSymbol } from './utils.DNoFbBUZ.js';
6
+ import { d as divider, F as F_POINTER, w as withLabel, f as formatProjectName, a as formatTimeString, g as getStateSymbol, t as taskFail, b as F_RIGHT, c as F_CHECK, r as renderSnapshotSummary, p as padSummaryTitle, e as getStateString$1, h as formatTime, i as countTestErrors, j as F_TREE_NODE_END, k as F_TREE_NODE_MIDDLE, l as getCols } from './utils.CMUTX-p8.js';
7
7
  import { stripVTControlCharacters } from 'node:util';
8
8
  import { highlight, isPrimitive, inspect, positionToOffset, lineSplitRE, toArray, notNullish } from '@vitest/utils';
9
- import { performance } from 'node:perf_hooks';
9
+ import { performance as performance$1 } from 'node:perf_hooks';
10
10
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
11
- import { a as TypeCheckError, R as RandomSequencer, g as getOutputFile, b as isNode, c as isDeno } from './RandomSequencer.CMRlh2v4.js';
11
+ import { a as TypeCheckError, R as RandomSequencer, g as getOutputFile, b as isNode, c as isDeno } from './RandomSequencer.gisBJ77r.js';
12
12
  import { isCI } from 'std-env';
13
13
  import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
14
14
  import { Writable } from 'node:stream';
@@ -20,68 +20,6 @@ import require$$0$1 from 'events';
20
20
  import { createRequire } from 'node:module';
21
21
  import { hostname } from 'node:os';
22
22
 
23
- class TestProject {
24
- /**
25
- * The global vitest instance.
26
- * @experimental The public Vitest API is experimental and does not follow semver.
27
- */
28
- vitest;
29
- /**
30
- * The workspace project this test project is associated with.
31
- * @experimental The public Vitest API is experimental and does not follow semver.
32
- */
33
- workspaceProject;
34
- /**
35
- * Vite's dev server instance. Every workspace project has its own server.
36
- */
37
- vite;
38
- /**
39
- * Resolved project configuration.
40
- */
41
- config;
42
- /**
43
- * Resolved global configuration. If there are no workspace projects, this will be the same as `config`.
44
- */
45
- globalConfig;
46
- /**
47
- * The name of the project or an empty string if not set.
48
- */
49
- name;
50
- constructor(workspaceProject) {
51
- this.workspaceProject = workspaceProject;
52
- this.vitest = workspaceProject.ctx;
53
- this.vite = workspaceProject.server;
54
- this.globalConfig = workspaceProject.ctx.config;
55
- this.config = workspaceProject.config;
56
- this.name = workspaceProject.getName();
57
- }
58
- /**
59
- * Serialized project configuration. This is the config that tests receive.
60
- */
61
- get serializedConfig() {
62
- return this.workspaceProject.getSerializableConfig();
63
- }
64
- /**
65
- * Custom context provided to the project.
66
- */
67
- context() {
68
- return this.workspaceProject.getProvidedContext();
69
- }
70
- /**
71
- * Provide a custom serializable context to the project. This context will be available for tests once they run.
72
- */
73
- provide(key, value) {
74
- this.workspaceProject.provide(key, value);
75
- }
76
- toJSON() {
77
- return {
78
- name: this.name,
79
- serializedConfig: this.serializedConfig,
80
- context: this.context()
81
- };
82
- }
83
- }
84
-
85
23
  class ReportedTaskImplementation {
86
24
  /**
87
25
  * Task instance.
@@ -104,7 +42,7 @@ class ReportedTaskImplementation {
104
42
  location;
105
43
  constructor(task, project) {
106
44
  this.task = task;
107
- this.project = project.testProject || (project.testProject = new TestProject(project));
45
+ this.project = project;
108
46
  this.id = task.id;
109
47
  this.location = task.location;
110
48
  }
@@ -415,10 +353,10 @@ function getTestState(test) {
415
353
  return result ? result.state : "running";
416
354
  }
417
355
  function storeTask(project, runnerTask, reportedTask) {
418
- project.ctx.state.reportedTasksMap.set(runnerTask, reportedTask);
356
+ project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
419
357
  }
420
358
  function getReportedTask(project, runnerTask) {
421
- const reportedTask = project.ctx.state.getReportedEntity(runnerTask);
359
+ const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
422
360
  if (!reportedTask) {
423
361
  throw new Error(
424
362
  `Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
@@ -534,7 +472,7 @@ const stringify = (value, replacer, space) => {
534
472
  }
535
473
  };
536
474
 
537
- const ESC$1 = '\u001B[';
475
+ const ESC$2 = '\u001B[';
538
476
  const OSC = '\u001B]';
539
477
  const BEL = '\u0007';
540
478
  const SEP = ';';
@@ -548,10 +486,10 @@ ansiEscapes.cursorTo = (x, y) => {
548
486
  }
549
487
 
550
488
  if (typeof y !== 'number') {
551
- return ESC$1 + (x + 1) + 'G';
489
+ return ESC$2 + (x + 1) + 'G';
552
490
  }
553
491
 
554
- return ESC$1 + (y + 1) + ';' + (x + 1) + 'H';
492
+ return ESC$2 + (y + 1) + ';' + (x + 1) + 'H';
555
493
  };
556
494
 
557
495
  ansiEscapes.cursorMove = (x, y) => {
@@ -562,33 +500,33 @@ ansiEscapes.cursorMove = (x, y) => {
562
500
  let returnValue = '';
563
501
 
564
502
  if (x < 0) {
565
- returnValue += ESC$1 + (-x) + 'D';
503
+ returnValue += ESC$2 + (-x) + 'D';
566
504
  } else if (x > 0) {
567
- returnValue += ESC$1 + x + 'C';
505
+ returnValue += ESC$2 + x + 'C';
568
506
  }
569
507
 
570
508
  if (y < 0) {
571
- returnValue += ESC$1 + (-y) + 'A';
509
+ returnValue += ESC$2 + (-y) + 'A';
572
510
  } else if (y > 0) {
573
- returnValue += ESC$1 + y + 'B';
511
+ returnValue += ESC$2 + y + 'B';
574
512
  }
575
513
 
576
514
  return returnValue;
577
515
  };
578
516
 
579
- ansiEscapes.cursorUp = (count = 1) => ESC$1 + count + 'A';
580
- ansiEscapes.cursorDown = (count = 1) => ESC$1 + count + 'B';
581
- ansiEscapes.cursorForward = (count = 1) => ESC$1 + count + 'C';
582
- ansiEscapes.cursorBackward = (count = 1) => ESC$1 + count + 'D';
517
+ ansiEscapes.cursorUp = (count = 1) => ESC$2 + count + 'A';
518
+ ansiEscapes.cursorDown = (count = 1) => ESC$2 + count + 'B';
519
+ ansiEscapes.cursorForward = (count = 1) => ESC$2 + count + 'C';
520
+ ansiEscapes.cursorBackward = (count = 1) => ESC$2 + count + 'D';
583
521
 
584
- ansiEscapes.cursorLeft = ESC$1 + 'G';
585
- ansiEscapes.cursorSavePosition = isTerminalApp ? '\u001B7' : ESC$1 + 's';
586
- ansiEscapes.cursorRestorePosition = isTerminalApp ? '\u001B8' : ESC$1 + 'u';
587
- ansiEscapes.cursorGetPosition = ESC$1 + '6n';
588
- ansiEscapes.cursorNextLine = ESC$1 + 'E';
589
- ansiEscapes.cursorPrevLine = ESC$1 + 'F';
590
- ansiEscapes.cursorHide = ESC$1 + '?25l';
591
- ansiEscapes.cursorShow = ESC$1 + '?25h';
522
+ ansiEscapes.cursorLeft = ESC$2 + 'G';
523
+ ansiEscapes.cursorSavePosition = isTerminalApp ? '\u001B7' : ESC$2 + 's';
524
+ ansiEscapes.cursorRestorePosition = isTerminalApp ? '\u001B8' : ESC$2 + 'u';
525
+ ansiEscapes.cursorGetPosition = ESC$2 + '6n';
526
+ ansiEscapes.cursorNextLine = ESC$2 + 'E';
527
+ ansiEscapes.cursorPrevLine = ESC$2 + 'F';
528
+ ansiEscapes.cursorHide = ESC$2 + '?25l';
529
+ ansiEscapes.cursorShow = ESC$2 + '?25h';
592
530
 
593
531
  ansiEscapes.eraseLines = count => {
594
532
  let clear = '';
@@ -604,24 +542,24 @@ ansiEscapes.eraseLines = count => {
604
542
  return clear;
605
543
  };
606
544
 
607
- ansiEscapes.eraseEndLine = ESC$1 + 'K';
608
- ansiEscapes.eraseStartLine = ESC$1 + '1K';
609
- ansiEscapes.eraseLine = ESC$1 + '2K';
610
- ansiEscapes.eraseDown = ESC$1 + 'J';
611
- ansiEscapes.eraseUp = ESC$1 + '1J';
612
- ansiEscapes.eraseScreen = ESC$1 + '2J';
613
- ansiEscapes.scrollUp = ESC$1 + 'S';
614
- ansiEscapes.scrollDown = ESC$1 + 'T';
545
+ ansiEscapes.eraseEndLine = ESC$2 + 'K';
546
+ ansiEscapes.eraseStartLine = ESC$2 + '1K';
547
+ ansiEscapes.eraseLine = ESC$2 + '2K';
548
+ ansiEscapes.eraseDown = ESC$2 + 'J';
549
+ ansiEscapes.eraseUp = ESC$2 + '1J';
550
+ ansiEscapes.eraseScreen = ESC$2 + '2J';
551
+ ansiEscapes.scrollUp = ESC$2 + 'S';
552
+ ansiEscapes.scrollDown = ESC$2 + 'T';
615
553
 
616
554
  ansiEscapes.clearScreen = '\u001Bc';
617
555
 
618
556
  ansiEscapes.clearTerminal = process.platform === 'win32' ?
619
- `${ansiEscapes.eraseScreen}${ESC$1}0f` :
557
+ `${ansiEscapes.eraseScreen}${ESC$2}0f` :
620
558
  // 1. Erases the screen (Only done in case `2` is not supported)
621
559
  // 2. Erases the whole screen including scrollback buffer
622
560
  // 3. Moves cursor to the top-left position
623
561
  // More info: https://www.real-world-systems.com/docs/ANSIcode.html
624
- `${ansiEscapes.eraseScreen}${ESC$1}3J${ESC$1}H`;
562
+ `${ansiEscapes.eraseScreen}${ESC$2}3J${ESC$2}H`;
625
563
 
626
564
  ansiEscapes.beep = BEL;
627
565
 
@@ -3026,10 +2964,10 @@ function lineNo(no = "") {
3026
2964
  }
3027
2965
 
3028
2966
  const PAD = " ";
3029
- const ESC = "\x1B[";
3030
- const ERASE_DOWN = `${ESC}J`;
3031
- const ERASE_SCROLLBACK = `${ESC}3J`;
3032
- const CURSOR_TO_START = `${ESC}1;1H`;
2967
+ const ESC$1 = "\x1B[";
2968
+ const ERASE_DOWN = `${ESC$1}J`;
2969
+ const ERASE_SCROLLBACK = `${ESC$1}3J`;
2970
+ const CURSOR_TO_START = `${ESC$1}1;1H`;
3033
2971
  const CLEAR_SCREEN = "\x1Bc";
3034
2972
  class Logger {
3035
2973
  constructor(ctx, outputStream = process.stdout, errorStream = process.stderr) {
@@ -3088,7 +3026,7 @@ class Logger {
3088
3026
  }
3089
3027
  printError(err, options = {}) {
3090
3028
  const { fullStack = false, type } = options;
3091
- const project = options.project ?? this.ctx.getCoreWorkspaceProject() ?? this.ctx.projects[0];
3029
+ const project = options.project ?? this.ctx.coreWorkspaceProject ?? this.ctx.projects[0];
3092
3030
  return printError(err, project, {
3093
3031
  type,
3094
3032
  showCodeFrame: options.showCodeFrame ?? true,
@@ -3137,8 +3075,7 @@ class Logger {
3137
3075
  }
3138
3076
  this.ctx.projects.forEach((project) => {
3139
3077
  const config2 = project.config;
3140
- const name = project.getName();
3141
- const output = project.isCore() || !name ? "" : `[${name}]`;
3078
+ const output = project.isRootProject() || !project.name ? "" : `[${project.name}]`;
3142
3079
  if (output) {
3143
3080
  this.console.error(c.bgCyan(`${output} Config`));
3144
3081
  }
@@ -3214,8 +3151,7 @@ Vitest is running in standalone mode. Edit a test file to rerun tests.`));
3214
3151
  if (!origin) {
3215
3152
  return;
3216
3153
  }
3217
- const name = project.getName();
3218
- const output = project.isCore() ? "" : formatProjectName(name);
3154
+ const output = project.isRootProject() ? "" : formatProjectName(project.name);
3219
3155
  const provider = project.browser.provider.name;
3220
3156
  const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
3221
3157
  this.log(
@@ -3233,15 +3169,15 @@ Vitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} du
3233
3169
  This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.`
3234
3170
  )
3235
3171
  );
3236
- this.log(c.red(divider(c.bold(c.inverse(" Unhandled Errors ")))));
3237
- this.log(errorMessage);
3172
+ this.error(c.red(divider(c.bold(c.inverse(" Unhandled Errors ")))));
3173
+ this.error(errorMessage);
3238
3174
  errors.forEach((err) => {
3239
3175
  this.printError(err, {
3240
3176
  fullStack: true,
3241
3177
  type: err.type || "Unhandled Error"
3242
3178
  });
3243
3179
  });
3244
- this.log(c.red(divider()));
3180
+ this.error(c.red(divider()));
3245
3181
  }
3246
3182
  printSourceTypeErrors(errors) {
3247
3183
  const errorMessage = c.red(
@@ -3295,8 +3231,8 @@ class BlobReporter {
3295
3231
  const modules = this.ctx.projects.map(
3296
3232
  (project) => {
3297
3233
  return [
3298
- project.getName(),
3299
- [...project.server.moduleGraph.idToModuleMap.entries()].map((mod) => {
3234
+ project.name,
3235
+ [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
3300
3236
  if (!mod[1].file) {
3301
3237
  return null;
3302
3238
  }
@@ -3363,7 +3299,7 @@ ${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`
3363
3299
  );
3364
3300
  }
3365
3301
  const projects = Object.fromEntries(
3366
- projectsArray.map((p) => [p.getName(), p])
3302
+ projectsArray.map((p) => [p.name, p])
3367
3303
  );
3368
3304
  blobs.forEach((blob) => {
3369
3305
  blob.moduleKeys.forEach(([projectName, moduleIds]) => {
@@ -3372,10 +3308,10 @@ ${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`
3372
3308
  return;
3373
3309
  }
3374
3310
  moduleIds.forEach(([moduleId, file, url]) => {
3375
- const moduleNode = project.server.moduleGraph.createFileOnlyEntry(file);
3311
+ const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
3376
3312
  moduleNode.url = url;
3377
3313
  moduleNode.id = moduleId;
3378
- project.server.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
3314
+ project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
3379
3315
  });
3380
3316
  });
3381
3317
  });
@@ -3402,7 +3338,6 @@ function hasFailedSnapshot(suite) {
3402
3338
  }
3403
3339
 
3404
3340
  const BADGE_PADDING = " ";
3405
- const LAST_RUN_LOG_TIMEOUT = 1500;
3406
3341
  class BaseReporter {
3407
3342
  start = 0;
3408
3343
  end = 0;
@@ -3410,19 +3345,17 @@ class BaseReporter {
3410
3345
  failedUnwatchedFiles = [];
3411
3346
  isTTY;
3412
3347
  ctx = void 0;
3348
+ renderSucceed = false;
3413
3349
  verbose = false;
3414
3350
  _filesInWatchMode = /* @__PURE__ */ new Map();
3415
3351
  _timeStart = formatTimeString(/* @__PURE__ */ new Date());
3416
- _lastRunTimeout = 0;
3417
- _lastRunTimer;
3418
- _lastRunCount = 0;
3419
3352
  constructor(options = {}) {
3420
3353
  this.isTTY = options.isTTY ?? ((isNode || isDeno) && process.stdout?.isTTY && !isCI);
3421
3354
  }
3422
3355
  onInit(ctx) {
3423
3356
  this.ctx = ctx;
3424
3357
  this.ctx.logger.printBanner();
3425
- this.start = performance.now();
3358
+ this.start = performance$1.now();
3426
3359
  }
3427
3360
  log(...messages) {
3428
3361
  this.ctx.logger.log(...messages);
@@ -3434,13 +3367,10 @@ class BaseReporter {
3434
3367
  return relative(this.ctx.config.root, path);
3435
3368
  }
3436
3369
  onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
3437
- this.end = performance.now();
3370
+ this.end = performance$1.now();
3438
3371
  this.reportSummary(files, errors);
3439
3372
  }
3440
3373
  onTaskUpdate(packs) {
3441
- if (this.isTTY) {
3442
- return;
3443
- }
3444
3374
  for (const pack of packs) {
3445
3375
  const task = this.ctx.state.idMap.get(pack[0]);
3446
3376
  if (task) {
@@ -3474,18 +3404,29 @@ class BaseReporter {
3474
3404
  title += ` ${formatProjectName(task.projectName, "")}`;
3475
3405
  }
3476
3406
  this.log(` ${title} ${task.name} ${suffix}`);
3407
+ const anyFailed = tests.some((test) => test.result?.state === "fail");
3477
3408
  for (const test of tests) {
3478
- const duration = test.result?.duration;
3409
+ const { duration, retryCount, repeatCount } = test.result || {};
3410
+ let suffix2 = "";
3411
+ if (retryCount != null && retryCount > 0) {
3412
+ suffix2 += c.yellow(` (retry x${retryCount})`);
3413
+ }
3414
+ if (repeatCount != null && repeatCount > 0) {
3415
+ suffix2 += c.yellow(` (repeat x${repeatCount})`);
3416
+ }
3479
3417
  if (test.result?.state === "fail") {
3480
- const suffix2 = this.getDurationPrefix(test);
3481
- this.log(c.red(` ${taskFail} ${getTestName(test, c.dim(" > "))}${suffix2}`));
3418
+ this.log(c.red(` ${taskFail} ${getTestName(test, c.dim(" > "))}${this.getDurationPrefix(test)}`) + suffix2);
3482
3419
  test.result?.errors?.forEach((e) => {
3483
3420
  this.log(c.red(` ${F_RIGHT} ${e?.message}`));
3484
3421
  });
3485
3422
  } else if (duration && duration > this.ctx.config.slowTestThreshold) {
3486
3423
  this.log(
3487
- ` ${c.yellow(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))} ${c.yellow(Math.round(duration) + c.dim("ms"))}`
3424
+ ` ${c.yellow(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))} ${c.yellow(Math.round(duration) + c.dim("ms"))}${suffix2}`
3488
3425
  );
3426
+ } else if (this.ctx.config.hideSkippedTests && (test.mode === "skip" || test.result?.state === "skip")) ; else if (test.result?.state === "skip" && test.result.note) {
3427
+ this.log(` ${getStateSymbol(test)} ${getTestName(test)}${c.dim(c.gray(` [${test.result.note}]`))}`);
3428
+ } else if (this.renderSucceed || anyFailed) {
3429
+ this.log(` ${c.dim(getStateSymbol(test))} ${getTestName(test, c.dim(" > "))}${suffix2}`);
3489
3430
  }
3490
3431
  }
3491
3432
  }
@@ -3497,7 +3438,6 @@ class BaseReporter {
3497
3438
  return color(` ${Math.round(task.result.duration)}${c.dim("ms")}`);
3498
3439
  }
3499
3440
  onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
3500
- this.resetLastRunLog();
3501
3441
  const failed = errors.length > 0 || hasFailed(files);
3502
3442
  if (failed) {
3503
3443
  this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
@@ -3513,34 +3453,8 @@ class BaseReporter {
3513
3453
  hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
3514
3454
  }
3515
3455
  this.log(BADGE_PADDING + hints.join(c.dim(", ")));
3516
- if (this._lastRunCount) {
3517
- const LAST_RUN_TEXT = `rerun x${this._lastRunCount}`;
3518
- const LAST_RUN_TEXTS = [
3519
- c.blue(LAST_RUN_TEXT),
3520
- c.gray(LAST_RUN_TEXT),
3521
- c.dim(c.gray(LAST_RUN_TEXT))
3522
- ];
3523
- this.ctx.logger.logUpdate(BADGE_PADDING + LAST_RUN_TEXTS[0]);
3524
- this._lastRunTimeout = 0;
3525
- this._lastRunTimer = setInterval(() => {
3526
- this._lastRunTimeout += 1;
3527
- if (this._lastRunTimeout >= LAST_RUN_TEXTS.length) {
3528
- this.resetLastRunLog();
3529
- } else {
3530
- this.ctx.logger.logUpdate(
3531
- BADGE_PADDING + LAST_RUN_TEXTS[this._lastRunTimeout]
3532
- );
3533
- }
3534
- }, LAST_RUN_LOG_TIMEOUT / LAST_RUN_TEXTS.length);
3535
- }
3536
- }
3537
- resetLastRunLog() {
3538
- clearInterval(this._lastRunTimer);
3539
- this._lastRunTimer = void 0;
3540
- this.ctx.logger.logUpdate.clear();
3541
3456
  }
3542
3457
  onWatcherRerun(files, trigger) {
3543
- this.resetLastRunLog();
3544
3458
  this.watchFilters = files;
3545
3459
  this.failedUnwatchedFiles = this.ctx.state.getFiles().filter(
3546
3460
  (file) => !files.includes(file.filepath) && hasFailed(file)
@@ -3550,9 +3464,7 @@ class BaseReporter {
3550
3464
  this._filesInWatchMode.set(filepath, ++reruns);
3551
3465
  });
3552
3466
  let banner = trigger ? c.dim(`${this.relative(trigger)} `) : "";
3553
- if (files.length > 1 || !files.length) {
3554
- this._lastRunCount = 0;
3555
- } else if (files.length === 1) {
3467
+ if (files.length === 1) {
3556
3468
  const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
3557
3469
  banner += c.blue(`x${rerun} `);
3558
3470
  }
@@ -3568,13 +3480,11 @@ class BaseReporter {
3568
3480
  this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
3569
3481
  }
3570
3482
  this.log("");
3571
- if (!this.isTTY) {
3572
- for (const task of this.failedUnwatchedFiles) {
3573
- this.printTask(task);
3574
- }
3483
+ for (const task of this.failedUnwatchedFiles) {
3484
+ this.printTask(task);
3575
3485
  }
3576
3486
  this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
3577
- this.start = performance.now();
3487
+ this.start = performance$1.now();
3578
3488
  }
3579
3489
  onUserConsoleLog(log) {
3580
3490
  if (!this.shouldLog(log)) {
@@ -3595,7 +3505,7 @@ class BaseReporter {
3595
3505
  if (log.browser) {
3596
3506
  write("\n");
3597
3507
  }
3598
- const project = log.taskId ? this.ctx.getProjectByTaskId(log.taskId) : this.ctx.getCoreWorkspaceProject();
3508
+ const project = log.taskId ? this.ctx.getProjectByTaskId(log.taskId) : this.ctx.getRootTestProject();
3599
3509
  const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
3600
3510
  const highlight = task && stack.find((i) => i.file === task.file.filepath);
3601
3511
  for (const frame of stack) {
@@ -3639,6 +3549,7 @@ class BaseReporter {
3639
3549
  }
3640
3550
  }
3641
3551
  reportTestSummary(files, errors) {
3552
+ this.log();
3642
3553
  const affectedFiles = [
3643
3554
  ...this.failedUnwatchedFiles,
3644
3555
  ...files
@@ -3650,32 +3561,32 @@ class BaseReporter {
3650
3561
  );
3651
3562
  for (const [index, snapshot] of snapshotOutput.entries()) {
3652
3563
  const title = index === 0 ? "Snapshots" : "";
3653
- this.log(`${padTitle(title)} ${snapshot}`);
3564
+ this.log(`${padSummaryTitle(title)} ${snapshot}`);
3654
3565
  }
3655
3566
  if (snapshotOutput.length > 1) {
3656
3567
  this.log();
3657
3568
  }
3658
- this.log(padTitle("Test Files"), getStateString(affectedFiles));
3659
- this.log(padTitle("Tests"), getStateString(tests));
3569
+ this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
3570
+ this.log(padSummaryTitle("Tests"), getStateString$1(tests));
3660
3571
  if (this.ctx.projects.some((c2) => c2.config.typecheck.enabled)) {
3661
3572
  const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
3662
3573
  this.log(
3663
- padTitle("Type Errors"),
3574
+ padSummaryTitle("Type Errors"),
3664
3575
  failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors")
3665
3576
  );
3666
3577
  }
3667
3578
  if (errors.length) {
3668
3579
  this.log(
3669
- padTitle("Errors"),
3580
+ padSummaryTitle("Errors"),
3670
3581
  c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`))
3671
3582
  );
3672
3583
  }
3673
- this.log(padTitle("Start at"), this._timeStart);
3584
+ this.log(padSummaryTitle("Start at"), this._timeStart);
3674
3585
  const collectTime = sum(files, (file) => file.collectDuration);
3675
3586
  const testsTime = sum(files, (file) => file.result?.duration);
3676
3587
  const setupTime = sum(files, (file) => file.setupDuration);
3677
3588
  if (this.watchFilters) {
3678
- this.log(padTitle("Duration"), time(collectTime + testsTime + setupTime));
3589
+ this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
3679
3590
  } else {
3680
3591
  const executionTime = this.end - this.start;
3681
3592
  const environmentTime = sum(files, (file) => file.environmentLoad);
@@ -3683,15 +3594,15 @@ class BaseReporter {
3683
3594
  const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
3684
3595
  const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
3685
3596
  const timers = [
3686
- `transform ${time(transformTime)}`,
3687
- `setup ${time(setupTime)}`,
3688
- `collect ${time(collectTime)}`,
3689
- `tests ${time(testsTime)}`,
3690
- `environment ${time(environmentTime)}`,
3691
- `prepare ${time(prepareTime)}`,
3692
- typecheck && `typecheck ${time(typecheck)}`
3597
+ `transform ${formatTime(transformTime)}`,
3598
+ `setup ${formatTime(setupTime)}`,
3599
+ `collect ${formatTime(collectTime)}`,
3600
+ `tests ${formatTime(testsTime)}`,
3601
+ `environment ${formatTime(environmentTime)}`,
3602
+ `prepare ${formatTime(prepareTime)}`,
3603
+ typecheck && `typecheck ${formatTime(typecheck)}`
3693
3604
  ].filter(Boolean).join(", ");
3694
- this.log(padTitle("Duration"), time(executionTime) + c.dim(` (${timers})`));
3605
+ this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
3695
3606
  }
3696
3607
  this.log();
3697
3608
  }
@@ -3705,12 +3616,14 @@ class BaseReporter {
3705
3616
  const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}
3706
3617
  `);
3707
3618
  if (failedSuites.length) {
3708
- this.error(`${errorBanner(`Failed Suites ${failedSuites.length}`)}
3619
+ this.error(`
3620
+ ${errorBanner(`Failed Suites ${failedSuites.length}`)}
3709
3621
  `);
3710
3622
  this.printTaskErrors(failedSuites, errorDivider);
3711
3623
  }
3712
3624
  if (failedTests.length) {
3713
- this.error(`${errorBanner(`Failed Tests ${failedTests.length}`)}
3625
+ this.error(`
3626
+ ${errorBanner(`Failed Tests ${failedTests.length}`)}
3714
3627
  `);
3715
3628
  this.printTaskErrors(failedTests, errorDivider);
3716
3629
  }
@@ -3769,7 +3682,7 @@ class BaseReporter {
3769
3682
  name += c.dim(` [ ${this.relative(filepath)} ]`);
3770
3683
  }
3771
3684
  this.ctx.logger.error(
3772
- `${c.red(c.bold(c.inverse(" FAIL ")))}${formatProjectName(projectName)} ${name}`
3685
+ `${c.red(c.bold(c.inverse(" FAIL ")))} ${formatProjectName(projectName)}${name}`
3773
3686
  );
3774
3687
  }
3775
3688
  const screenshotPaths = tasks2.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
@@ -3786,15 +3699,6 @@ class BaseReporter {
3786
3699
  function errorBanner(message) {
3787
3700
  return c.red(divider(c.bold(c.inverse(` ${message} `))));
3788
3701
  }
3789
- function padTitle(str) {
3790
- return c.dim(`${str.padStart(11)} `);
3791
- }
3792
- function time(time2) {
3793
- if (time2 > 1e3) {
3794
- return `${(time2 / 1e3).toFixed(2)}s`;
3795
- }
3796
- return `${Math.round(time2)}ms`;
3797
- }
3798
3702
  function sum(items, cb) {
3799
3703
  return items.reduce((total, next) => {
3800
3704
  return total + Math.max(cb(next) || 0, 0);
@@ -3806,423 +3710,669 @@ class BasicReporter extends BaseReporter {
3806
3710
  super();
3807
3711
  this.isTTY = false;
3808
3712
  }
3713
+ onInit(ctx) {
3714
+ super.onInit(ctx);
3715
+ ctx.logger.log(c.inverse(c.bold(c.yellow(" DEPRECATED "))), c.yellow(
3716
+ `'basic' reporter is deprecated and will be removed in Vitest v3.
3717
+ Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:
3718
+ ${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`
3719
+ ));
3720
+ }
3809
3721
  reportSummary(files, errors) {
3810
3722
  this.ctx.logger.log();
3811
3723
  return super.reportSummary(files, errors);
3812
3724
  }
3813
3725
  }
3814
3726
 
3815
- const outputMap$1 = /* @__PURE__ */ new WeakMap();
3816
- function formatFilepath$1(path) {
3817
- const lastSlash = Math.max(path.lastIndexOf("/") + 1, 0);
3818
- const basename = path.slice(lastSlash);
3819
- let firstDot = basename.indexOf(".");
3820
- if (firstDot < 0) {
3821
- firstDot = basename.length;
3727
+ const DEFAULT_RENDER_INTERVAL = 16;
3728
+ const ESC = "\x1B[";
3729
+ const CLEAR_LINE = `${ESC}K`;
3730
+ const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
3731
+ const HIDE_CURSOR = `${ESC}?25l`;
3732
+ const SHOW_CURSOR = `${ESC}?25h`;
3733
+ const SYNC_START = `${ESC}?2026h`;
3734
+ const SYNC_END = `${ESC}?2026l`;
3735
+ class WindowRenderer {
3736
+ options;
3737
+ streams;
3738
+ buffer = [];
3739
+ renderInterval = void 0;
3740
+ windowHeight = 0;
3741
+ finished = false;
3742
+ cleanups = [];
3743
+ constructor(options) {
3744
+ this.options = {
3745
+ interval: DEFAULT_RENDER_INTERVAL,
3746
+ ...options
3747
+ };
3748
+ this.streams = {
3749
+ output: options.logger.outputStream.write.bind(options.logger.outputStream),
3750
+ error: options.logger.errorStream.write.bind(options.logger.errorStream)
3751
+ };
3752
+ this.cleanups.push(
3753
+ this.interceptStream(process.stdout, "output"),
3754
+ this.interceptStream(process.stderr, "error"),
3755
+ this.addProcessExitListeners()
3756
+ );
3757
+ this.write(HIDE_CURSOR, "output");
3758
+ this.start();
3822
3759
  }
3823
- firstDot += lastSlash;
3824
- return c.dim(path.slice(0, lastSlash)) + path.slice(lastSlash, firstDot) + c.dim(path.slice(firstDot));
3825
- }
3826
- function formatNumber$1(number) {
3827
- const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
3828
- return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
3829
- }
3830
- function renderHookState(task, hookName, level = 0) {
3831
- const state = task.result?.hooks?.[hookName];
3832
- if (state && state === "run") {
3833
- return `${" ".repeat(level)} ${getHookStateSymbol(task, hookName)} ${c.dim(
3834
- `[ ${hookName} ]`
3835
- )}`;
3836
- }
3837
- return "";
3838
- }
3839
- function renderBenchmarkItems$1(result) {
3840
- return [
3841
- result.name,
3842
- formatNumber$1(result.hz || 0),
3843
- formatNumber$1(result.p99 || 0),
3844
- `\xB1${result.rme.toFixed(2)}%`,
3845
- result.samples.length.toString()
3846
- ];
3847
- }
3848
- function renderBenchmark$1(task, tasks) {
3849
- const result = task.result?.benchmark;
3850
- if (!result) {
3851
- return task.name;
3852
- }
3853
- const benches = tasks.map((i) => i.meta?.benchmark ? i.result?.benchmark : void 0).filter(notNullish);
3854
- const allItems = benches.map(renderBenchmarkItems$1);
3855
- const items = renderBenchmarkItems$1(result);
3856
- const padded = items.map((i, idx) => {
3857
- const width = Math.max(...allItems.map((i2) => i2[idx].length));
3858
- return idx ? i.padStart(width, " ") : i.padEnd(width, " ");
3859
- });
3860
- return [
3861
- padded[0],
3862
- // name
3863
- c.dim(" "),
3864
- c.blue(padded[1]),
3865
- c.dim(" ops/sec "),
3866
- c.cyan(padded[3]),
3867
- c.dim(` (${padded[4]} samples)`),
3868
- result.rank === 1 ? c.bold(c.green(" fastest")) : result.rank === benches.length && benches.length > 2 ? c.bold(c.gray(" slowest")) : ""
3869
- ].join("");
3870
- }
3871
- function renderTree$1(tasks, options, level = 0, maxRows) {
3872
- const output = [];
3873
- let currentRowCount = 0;
3874
- for (const task of [...tasks].reverse()) {
3875
- const taskOutput = [];
3876
- let suffix = "";
3877
- let prefix = ` ${getStateSymbol(task)} `;
3878
- if (level === 0 && task.type === "suite" && "projectName" in task) {
3879
- prefix += formatProjectName(task.projectName);
3880
- }
3881
- if (level === 0 && task.type === "suite" && task.meta.typecheck) {
3882
- prefix += c.bgBlue(c.bold(" TS "));
3883
- prefix += " ";
3760
+ start() {
3761
+ this.finished = false;
3762
+ this.renderInterval = setInterval(() => this.flushBuffer(), this.options.interval);
3763
+ }
3764
+ stop() {
3765
+ this.write(SHOW_CURSOR, "output");
3766
+ this.cleanups.splice(0).map((fn) => fn());
3767
+ clearInterval(this.renderInterval);
3768
+ }
3769
+ /**
3770
+ * Write all buffered output and stop buffering.
3771
+ * All intercepted writes are forwarded to actual write after this.
3772
+ */
3773
+ finish() {
3774
+ this.finished = true;
3775
+ this.flushBuffer();
3776
+ clearInterval(this.renderInterval);
3777
+ }
3778
+ getColumns() {
3779
+ return "columns" in this.options.logger.outputStream ? this.options.logger.outputStream.columns : 80;
3780
+ }
3781
+ flushBuffer() {
3782
+ if (this.buffer.length === 0) {
3783
+ return this.render();
3784
+ }
3785
+ let current;
3786
+ for (const next of this.buffer.splice(0)) {
3787
+ if (!current) {
3788
+ current = next;
3789
+ continue;
3790
+ }
3791
+ if (current.type !== next.type) {
3792
+ this.render(current.message, current.type);
3793
+ current = next;
3794
+ continue;
3795
+ }
3796
+ current.message += next.message;
3884
3797
  }
3885
- if (task.type === "test" && task.result?.retryCount && task.result.retryCount > 0) {
3886
- suffix += c.yellow(` (retry x${task.result.retryCount})`);
3798
+ if (current) {
3799
+ this.render(current?.message, current?.type);
3887
3800
  }
3888
- if (task.type === "suite") {
3889
- const tests = getTests(task);
3890
- suffix += c.dim(` (${tests.length})`);
3801
+ }
3802
+ render(message, type = "output") {
3803
+ if (this.finished) {
3804
+ this.clearWindow();
3805
+ return this.write(message || "", type);
3891
3806
  }
3892
- if (task.mode === "skip" || task.mode === "todo") {
3893
- const note = task.result?.note || "skipped";
3894
- suffix += ` ${c.dim(c.gray(`[${note}]`))}`;
3807
+ const windowContent = this.options.getWindow();
3808
+ const rowCount = getRenderedRowCount(windowContent, this.getColumns());
3809
+ let padding = this.windowHeight - rowCount;
3810
+ if (padding > 0 && message) {
3811
+ padding -= getRenderedRowCount([message], this.getColumns());
3895
3812
  }
3896
- if (task.type === "test" && task.result?.repeatCount && task.result.repeatCount > 0) {
3897
- suffix += c.yellow(` (repeat x${task.result.repeatCount})`);
3813
+ this.write(SYNC_START);
3814
+ this.clearWindow();
3815
+ if (message) {
3816
+ this.write(message, type);
3898
3817
  }
3899
- if (task.result?.duration != null) {
3900
- if (task.result.duration > options.slowTestThreshold) {
3901
- suffix += c.yellow(
3902
- ` ${Math.round(task.result.duration)}${c.dim("ms")}`
3903
- );
3904
- }
3818
+ if (padding > 0) {
3819
+ this.write("\n".repeat(padding));
3905
3820
  }
3906
- if (options.showHeap && task.result?.heap != null) {
3907
- suffix += c.magenta(
3908
- ` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`
3909
- );
3821
+ this.write(windowContent.join("\n"));
3822
+ this.write(SYNC_END);
3823
+ this.windowHeight = rowCount + Math.max(0, padding);
3824
+ }
3825
+ clearWindow() {
3826
+ if (this.windowHeight === 0) {
3827
+ return;
3910
3828
  }
3911
- let name = task.name;
3912
- if (level === 0) {
3913
- name = formatFilepath$1(name);
3829
+ this.write(CLEAR_LINE);
3830
+ for (let i = 1; i < this.windowHeight; i++) {
3831
+ this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
3914
3832
  }
3915
- const padding = " ".repeat(level);
3916
- const body = task.meta?.benchmark ? renderBenchmark$1(task, tasks) : name;
3917
- taskOutput.push(padding + prefix + body + suffix);
3918
- if (task.result?.state !== "pass" && outputMap$1.get(task) != null) {
3919
- let data = outputMap$1.get(task);
3920
- if (typeof data === "string") {
3921
- data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
3922
- if (data === "") {
3923
- data = void 0;
3833
+ this.windowHeight = 0;
3834
+ }
3835
+ interceptStream(stream, type) {
3836
+ const original = stream.write;
3837
+ stream.write = (chunk, _, callback) => {
3838
+ if (chunk) {
3839
+ if (this.finished) {
3840
+ this.write(chunk.toString(), type);
3841
+ } else {
3842
+ this.buffer.push({ type, message: chunk.toString() });
3924
3843
  }
3925
3844
  }
3926
- if (data != null) {
3927
- const out = `${" ".repeat(level)}${F_RIGHT} ${data}`;
3928
- taskOutput.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`);
3845
+ callback?.();
3846
+ };
3847
+ return function restore() {
3848
+ stream.write = original;
3849
+ };
3850
+ }
3851
+ write(message, type = "output") {
3852
+ this.streams[type](message);
3853
+ }
3854
+ addProcessExitListeners() {
3855
+ const onExit = (signal, exitCode) => {
3856
+ this.flushBuffer();
3857
+ this.stop();
3858
+ if (process.exitCode === void 0) {
3859
+ process.exitCode = exitCode !== void 0 ? 128 + exitCode : Number(signal);
3929
3860
  }
3930
- }
3931
- taskOutput.push(renderHookState(task, "beforeAll", level + 1));
3932
- taskOutput.push(renderHookState(task, "beforeEach", level + 1));
3933
- if (task.type === "suite" && task.tasks.length > 0) {
3934
- if (task.result?.state === "fail" || task.result?.state === "run" || options.renderSucceed) {
3935
- if (options.logger.ctx.config.hideSkippedTests) {
3936
- const filteredTasks = task.tasks.filter(
3937
- (t) => t.mode !== "skip" && t.mode !== "todo"
3938
- );
3939
- taskOutput.push(
3940
- renderTree$1(filteredTasks, options, level + 1, maxRows)
3941
- );
3861
+ process.exit();
3862
+ };
3863
+ process.once("SIGINT", onExit);
3864
+ process.once("SIGTERM", onExit);
3865
+ process.once("exit", onExit);
3866
+ return function cleanup() {
3867
+ process.off("SIGINT", onExit);
3868
+ process.off("SIGTERM", onExit);
3869
+ process.off("exit", onExit);
3870
+ };
3871
+ }
3872
+ }
3873
+ function getRenderedRowCount(rows, columns) {
3874
+ let count = 0;
3875
+ for (const row of rows) {
3876
+ const text = stripVTControlCharacters(row);
3877
+ count += Math.max(1, Math.ceil(text.length / columns));
3878
+ }
3879
+ return count;
3880
+ }
3881
+
3882
+ class TaskParser {
3883
+ ctx;
3884
+ onInit(ctx) {
3885
+ this.ctx = ctx;
3886
+ }
3887
+ onHookStart(_options) {
3888
+ }
3889
+ onHookEnd(_options) {
3890
+ }
3891
+ onTestStart(_test) {
3892
+ }
3893
+ onTestFinished(_test) {
3894
+ }
3895
+ onTestFilePrepare(_file) {
3896
+ }
3897
+ onTestFileFinished(_file) {
3898
+ }
3899
+ onTaskUpdate(packs) {
3900
+ const startingTestFiles = [];
3901
+ const finishedTestFiles = [];
3902
+ const startingTests = [];
3903
+ const finishedTests = [];
3904
+ const startingHooks = [];
3905
+ const endingHooks = [];
3906
+ for (const pack of packs) {
3907
+ const task = this.ctx.state.idMap.get(pack[0]);
3908
+ if (task?.type === "suite" && "filepath" in task && task.result?.state) {
3909
+ if (task?.result?.state === "run") {
3910
+ startingTestFiles.push(task);
3942
3911
  } else {
3943
- taskOutput.push(renderTree$1(task.tasks, options, level + 1, maxRows));
3912
+ for (const test of getTests(task)) {
3913
+ if (!test.result || test.result?.state === "skip") {
3914
+ finishedTests.push(test);
3915
+ }
3916
+ }
3917
+ finishedTestFiles.push(task.file);
3918
+ }
3919
+ }
3920
+ if (task?.type === "test") {
3921
+ if (task.result?.state === "run") {
3922
+ startingTests.push(task);
3923
+ } else if (task.result?.hooks?.afterEach !== "run") {
3924
+ finishedTests.push(task);
3925
+ }
3926
+ }
3927
+ if (task?.result?.hooks) {
3928
+ for (const [hook, state] of Object.entries(task.result.hooks)) {
3929
+ if (state === "run") {
3930
+ startingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type });
3931
+ } else {
3932
+ endingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type });
3933
+ }
3944
3934
  }
3945
3935
  }
3946
3936
  }
3947
- taskOutput.push(renderHookState(task, "afterAll", level + 1));
3948
- taskOutput.push(renderHookState(task, "afterEach", level + 1));
3949
- const rows = taskOutput.filter(Boolean);
3950
- output.push(rows.join("\n"));
3951
- currentRowCount += rows.length;
3952
- if (maxRows && currentRowCount >= maxRows) {
3953
- break;
3954
- }
3937
+ endingHooks.forEach((hook) => this.onHookEnd(hook));
3938
+ finishedTests.forEach((test) => this.onTestFinished(test));
3939
+ finishedTestFiles.forEach((file) => this.onTestFileFinished(file));
3940
+ startingTestFiles.forEach((file) => this.onTestFilePrepare(file));
3941
+ startingTests.forEach((test) => this.onTestStart(test));
3942
+ startingHooks.forEach(
3943
+ (hook) => this.onHookStart(hook)
3944
+ );
3955
3945
  }
3956
- return output.reverse().join("\n");
3957
3946
  }
3958
- function createListRenderer(_tasks, options) {
3959
- let tasks = _tasks;
3960
- let timer;
3961
- const log = options.logger.logUpdate;
3962
- function update() {
3963
- if (options.logger.ctx.config.hideSkippedTests) {
3964
- const filteredTasks = tasks.filter(
3965
- (t) => t.mode !== "skip" && t.mode !== "todo"
3966
- );
3967
- log(
3968
- renderTree$1(
3969
- filteredTasks,
3970
- options,
3971
- 0,
3972
- // log-update already limits the amount of printed rows to fit the current terminal
3973
- // but we can optimize performance by doing it ourselves
3974
- process.stdout.rows
3975
- )
3976
- );
3977
- } else {
3978
- log(
3979
- renderTree$1(
3980
- tasks,
3981
- options,
3982
- 0,
3983
- // log-update already limits the amount of printed rows to fit the current terminal
3984
- // but we can optimize performance by doing it ourselves
3985
- process.stdout.rows
3986
- )
3987
- );
3947
+
3948
+ const DURATION_UPDATE_INTERVAL_MS = 100;
3949
+ const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
3950
+ class SummaryReporter extends TaskParser {
3951
+ options;
3952
+ renderer;
3953
+ suites = emptyCounters();
3954
+ tests = emptyCounters();
3955
+ maxParallelTests = 0;
3956
+ /** Currently running tests, may include finished tests too */
3957
+ runningTests = /* @__PURE__ */ new Map();
3958
+ /** ID of finished `this.runningTests` that are currently being shown */
3959
+ finishedTests = /* @__PURE__ */ new Map();
3960
+ /** IDs of all finished tests */
3961
+ allFinishedTests = /* @__PURE__ */ new Set();
3962
+ startTime = "";
3963
+ currentTime = 0;
3964
+ duration = 0;
3965
+ durationInterval = void 0;
3966
+ onInit(ctx, options = {}) {
3967
+ this.ctx = ctx;
3968
+ this.options = {
3969
+ verbose: false,
3970
+ ...options
3971
+ };
3972
+ this.renderer = new WindowRenderer({
3973
+ logger: ctx.logger,
3974
+ getWindow: () => this.createSummary()
3975
+ });
3976
+ this.startTimers();
3977
+ this.ctx.onClose(() => {
3978
+ clearInterval(this.durationInterval);
3979
+ this.renderer.stop();
3980
+ });
3981
+ }
3982
+ onPathsCollected(paths) {
3983
+ this.suites.total = (paths || []).length;
3984
+ }
3985
+ onWatcherRerun() {
3986
+ this.runningTests.clear();
3987
+ this.finishedTests.clear();
3988
+ this.allFinishedTests.clear();
3989
+ this.suites = emptyCounters();
3990
+ this.tests = emptyCounters();
3991
+ this.startTimers();
3992
+ this.renderer.start();
3993
+ }
3994
+ onFinished() {
3995
+ this.runningTests.clear();
3996
+ this.finishedTests.clear();
3997
+ this.allFinishedTests.clear();
3998
+ this.renderer.finish();
3999
+ clearInterval(this.durationInterval);
4000
+ }
4001
+ onTestFilePrepare(file) {
4002
+ if (this.allFinishedTests.has(file.id) || this.runningTests.has(file.id)) {
4003
+ return;
3988
4004
  }
4005
+ const total = getTests(file).length;
4006
+ this.tests.total += total;
4007
+ if (this.finishedTests.size) {
4008
+ const finished = this.finishedTests.keys().next().value;
4009
+ this.removeTestFile(finished);
4010
+ }
4011
+ this.runningTests.set(file.id, {
4012
+ total,
4013
+ completed: 0,
4014
+ filename: file.name,
4015
+ projectName: file.projectName,
4016
+ tests: /* @__PURE__ */ new Map()
4017
+ });
4018
+ this.maxParallelTests = Math.max(this.maxParallelTests, this.runningTests.size);
3989
4019
  }
3990
- return {
3991
- start() {
3992
- if (timer) {
3993
- return this;
4020
+ onHookStart(options) {
4021
+ const stats = this.getHookStats(options);
4022
+ if (!stats) {
4023
+ return;
4024
+ }
4025
+ const hook = {
4026
+ name: options.name,
4027
+ visible: false,
4028
+ startTime: performance.now(),
4029
+ onFinish: () => {
3994
4030
  }
3995
- timer = setInterval(update, 16);
3996
- return this;
3997
- },
3998
- update(_tasks2) {
3999
- tasks = _tasks2;
4000
- return this;
4001
- },
4002
- stop() {
4003
- if (timer) {
4004
- clearInterval(timer);
4005
- timer = void 0;
4031
+ };
4032
+ stats.hook?.onFinish?.();
4033
+ stats.hook = hook;
4034
+ const timeout = setTimeout(() => {
4035
+ hook.visible = true;
4036
+ }, this.ctx.config.slowTestThreshold).unref();
4037
+ hook.onFinish = () => clearTimeout(timeout);
4038
+ }
4039
+ onHookEnd(options) {
4040
+ const stats = this.getHookStats(options);
4041
+ if (stats?.hook?.name !== options.name) {
4042
+ return;
4043
+ }
4044
+ stats.hook.onFinish();
4045
+ stats.hook.visible = false;
4046
+ }
4047
+ onTestStart(test) {
4048
+ if (!this.options.verbose) {
4049
+ return;
4050
+ }
4051
+ const stats = this.getTestStats(test);
4052
+ if (!stats || stats.tests.has(test.id)) {
4053
+ return;
4054
+ }
4055
+ const slowTest = {
4056
+ name: test.name,
4057
+ visible: false,
4058
+ startTime: performance.now(),
4059
+ onFinish: () => {
4006
4060
  }
4007
- log.clear();
4008
- if (options.logger.ctx.config.hideSkippedTests) {
4009
- const filteredTasks = tasks.filter(
4010
- (t) => t.mode !== "skip" && t.mode !== "todo"
4061
+ };
4062
+ const timeout = setTimeout(() => {
4063
+ slowTest.visible = true;
4064
+ }, this.ctx.config.slowTestThreshold).unref();
4065
+ slowTest.onFinish = () => {
4066
+ slowTest.hook?.onFinish();
4067
+ clearTimeout(timeout);
4068
+ };
4069
+ stats.tests.set(test.id, slowTest);
4070
+ }
4071
+ onTestFinished(test) {
4072
+ const stats = this.getTestStats(test);
4073
+ if (!stats) {
4074
+ return;
4075
+ }
4076
+ stats.tests.get(test.id)?.onFinish();
4077
+ stats.tests.delete(test.id);
4078
+ stats.completed++;
4079
+ const result = test.result;
4080
+ if (result?.state === "pass") {
4081
+ this.tests.passed++;
4082
+ } else if (result?.state === "fail") {
4083
+ this.tests.failed++;
4084
+ } else if (!result?.state || result?.state === "skip" || result?.state === "todo") {
4085
+ this.tests.skipped++;
4086
+ }
4087
+ }
4088
+ onTestFileFinished(file) {
4089
+ if (this.allFinishedTests.has(file.id)) {
4090
+ return;
4091
+ }
4092
+ this.allFinishedTests.add(file.id);
4093
+ this.suites.completed++;
4094
+ if (file.result?.state === "pass") {
4095
+ this.suites.passed++;
4096
+ } else if (file.result?.state === "fail") {
4097
+ this.suites.failed++;
4098
+ } else if (file.result?.state === "skip") {
4099
+ this.suites.skipped++;
4100
+ } else if (file.result?.state === "todo") {
4101
+ this.suites.todo++;
4102
+ }
4103
+ const left = this.suites.total - this.suites.completed;
4104
+ if (left > this.maxParallelTests) {
4105
+ this.finishedTests.set(file.id, setTimeout(() => {
4106
+ this.removeTestFile(file.id);
4107
+ }, FINISHED_TEST_CLEANUP_TIME_MS).unref());
4108
+ } else {
4109
+ this.removeTestFile(file.id);
4110
+ }
4111
+ }
4112
+ getTestStats(test) {
4113
+ const file = test.file;
4114
+ let stats = this.runningTests.get(file.id);
4115
+ if (!stats) {
4116
+ this.onTestFilePrepare(test.file);
4117
+ stats = this.runningTests.get(file.id);
4118
+ if (!stats) {
4119
+ return;
4120
+ }
4121
+ }
4122
+ return stats;
4123
+ }
4124
+ getHookStats({ file, id, type }) {
4125
+ if (!this.options.verbose) {
4126
+ return;
4127
+ }
4128
+ const stats = this.runningTests.get(file.id);
4129
+ if (!stats) {
4130
+ return;
4131
+ }
4132
+ return type === "suite" ? stats : stats?.tests.get(id);
4133
+ }
4134
+ createSummary() {
4135
+ const summary = [""];
4136
+ for (const testFile of Array.from(this.runningTests.values()).sort(sortRunningTests)) {
4137
+ summary.push(
4138
+ c.bold(c.yellow(` ${F_POINTER} `)) + formatProjectName(testFile.projectName) + testFile.filename + c.dim(` ${testFile.completed}/${testFile.total}`)
4139
+ );
4140
+ const slowTasks = [
4141
+ testFile.hook,
4142
+ ...Array.from(testFile.tests.values())
4143
+ ].filter((t) => t != null && t.visible);
4144
+ for (const [index, task] of slowTasks.entries()) {
4145
+ const elapsed = this.currentTime - task.startTime;
4146
+ const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
4147
+ summary.push(
4148
+ c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`))
4011
4149
  );
4012
- options.logger.log(renderTree$1(filteredTasks, options));
4013
- } else {
4014
- options.logger.log(renderTree$1(tasks, options));
4150
+ if (task.hook?.visible) {
4151
+ summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
4152
+ }
4015
4153
  }
4016
- return this;
4017
- },
4018
- clear() {
4019
- log.clear();
4020
4154
  }
4021
- };
4155
+ if (this.runningTests.size > 0) {
4156
+ summary.push("");
4157
+ }
4158
+ summary.push(padSummaryTitle("Test Files") + getStateString(this.suites));
4159
+ summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
4160
+ summary.push(padSummaryTitle("Start at") + this.startTime);
4161
+ summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
4162
+ summary.push("");
4163
+ return summary;
4164
+ }
4165
+ startTimers() {
4166
+ const start = performance.now();
4167
+ this.startTime = formatTimeString(/* @__PURE__ */ new Date());
4168
+ this.durationInterval = setInterval(() => {
4169
+ this.currentTime = performance.now();
4170
+ this.duration = this.currentTime - start;
4171
+ }, DURATION_UPDATE_INTERVAL_MS).unref();
4172
+ }
4173
+ removeTestFile(id) {
4174
+ if (!id) {
4175
+ return;
4176
+ }
4177
+ const testFile = this.runningTests.get(id);
4178
+ testFile?.hook?.onFinish();
4179
+ testFile?.tests?.forEach((test) => test.onFinish());
4180
+ this.runningTests.delete(id);
4181
+ clearTimeout(this.finishedTests.get(id));
4182
+ this.finishedTests.delete(id);
4183
+ }
4184
+ }
4185
+ function emptyCounters() {
4186
+ return { completed: 0, passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
4187
+ }
4188
+ function getStateString(entry) {
4189
+ return [
4190
+ entry.failed ? c.bold(c.red(`${entry.failed} failed`)) : null,
4191
+ c.bold(c.green(`${entry.passed} passed`)),
4192
+ entry.skipped ? c.yellow(`${entry.skipped} skipped`) : null,
4193
+ entry.todo ? c.gray(`${entry.todo} todo`) : null
4194
+ ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
4195
+ }
4196
+ function sortRunningTests(a, b) {
4197
+ if ((a.projectName || "") > (b.projectName || "")) {
4198
+ return 1;
4199
+ }
4200
+ if ((a.projectName || "") < (b.projectName || "")) {
4201
+ return -1;
4202
+ }
4203
+ return a.filename.localeCompare(b.filename);
4022
4204
  }
4023
4205
 
4024
4206
  class DefaultReporter extends BaseReporter {
4025
- renderer;
4026
- rendererOptions = {};
4027
- renderSucceedDefault;
4207
+ options;
4208
+ summary;
4209
+ constructor(options = {}) {
4210
+ super(options);
4211
+ this.options = {
4212
+ summary: true,
4213
+ ...options
4214
+ };
4215
+ if (!this.isTTY) {
4216
+ this.options.summary = false;
4217
+ }
4218
+ if (this.options.summary) {
4219
+ this.summary = new SummaryReporter();
4220
+ }
4221
+ }
4222
+ onInit(ctx) {
4223
+ super.onInit(ctx);
4224
+ this.summary?.onInit(ctx, { verbose: this.verbose });
4225
+ }
4028
4226
  onPathsCollected(paths = []) {
4029
4227
  if (this.isTTY) {
4030
- if (this.renderSucceedDefault === void 0) {
4031
- this.renderSucceedDefault = !!this.rendererOptions.renderSucceed;
4228
+ if (this.renderSucceed === void 0) {
4229
+ this.renderSucceed = !!this.renderSucceed;
4032
4230
  }
4033
- if (this.renderSucceedDefault !== true) {
4034
- this.rendererOptions.renderSucceed = paths.length <= 1;
4231
+ if (this.renderSucceed !== true) {
4232
+ this.renderSucceed = paths.length <= 1;
4035
4233
  }
4036
4234
  }
4235
+ this.summary?.onPathsCollected(paths);
4037
4236
  }
4038
- async onTestRemoved(trigger) {
4039
- this.stopListRender();
4040
- this.ctx.logger.clearScreen(
4041
- c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]
4042
- `) : ""),
4043
- true
4044
- );
4045
- const files = this.ctx.state.getFiles(this.watchFilters);
4046
- createListRenderer(files, this.rendererOptions).stop();
4047
- this.ctx.logger.log();
4048
- super.reportSummary(files, this.ctx.state.getUnhandledErrors());
4049
- super.onWatcherStart();
4237
+ onTaskUpdate(packs) {
4238
+ this.summary?.onTaskUpdate(packs);
4239
+ super.onTaskUpdate(packs);
4240
+ }
4241
+ onWatcherRerun(files, trigger) {
4242
+ this.summary?.onWatcherRerun();
4243
+ super.onWatcherRerun(files, trigger);
4244
+ }
4245
+ onFinished(files, errors) {
4246
+ this.summary?.onFinished();
4247
+ super.onFinished(files, errors);
4050
4248
  }
4051
- onCollected() {
4249
+ }
4250
+
4251
+ class DotReporter extends BaseReporter {
4252
+ summary;
4253
+ onInit(ctx) {
4254
+ super.onInit(ctx);
4052
4255
  if (this.isTTY) {
4053
- this.rendererOptions.logger = this.ctx.logger;
4054
- this.rendererOptions.showHeap = this.ctx.config.logHeapUsage;
4055
- this.rendererOptions.slowTestThreshold = this.ctx.config.slowTestThreshold;
4056
- this.rendererOptions.mode = this.ctx.config.mode;
4057
- const files = this.ctx.state.getFiles(this.watchFilters);
4058
- if (!this.renderer) {
4059
- this.renderer = createListRenderer(files, this.rendererOptions).start();
4060
- } else {
4061
- this.renderer.update(files);
4062
- }
4256
+ this.summary = new DotSummary();
4257
+ this.summary.onInit(ctx);
4063
4258
  }
4064
4259
  }
4065
- onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
4066
- this.renderer?.update([
4067
- ...this.failedUnwatchedFiles,
4068
- ...files
4069
- ]);
4070
- this.stopListRender();
4071
- this.ctx.logger.log();
4260
+ onTaskUpdate(packs) {
4261
+ this.summary?.onTaskUpdate(packs);
4262
+ if (!this.isTTY) {
4263
+ super.onTaskUpdate(packs);
4264
+ }
4265
+ }
4266
+ onWatcherRerun(files, trigger) {
4267
+ this.summary?.onWatcherRerun();
4268
+ super.onWatcherRerun(files, trigger);
4269
+ }
4270
+ onFinished(files, errors) {
4271
+ this.summary?.onFinished();
4072
4272
  super.onFinished(files, errors);
4073
4273
  }
4074
- async onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
4075
- this.stopListRender();
4076
- await super.onWatcherStart(files, errors);
4274
+ }
4275
+ class DotSummary extends TaskParser {
4276
+ renderer;
4277
+ tests = /* @__PURE__ */ new Map();
4278
+ finishedTests = /* @__PURE__ */ new Set();
4279
+ onInit(ctx) {
4280
+ this.ctx = ctx;
4281
+ this.renderer = new WindowRenderer({
4282
+ logger: ctx.logger,
4283
+ getWindow: () => this.createSummary()
4284
+ });
4285
+ this.ctx.onClose(() => this.renderer.stop());
4077
4286
  }
4078
- stopListRender() {
4079
- this.renderer?.stop();
4080
- this.renderer = void 0;
4287
+ onWatcherRerun() {
4288
+ this.tests.clear();
4289
+ this.renderer.start();
4081
4290
  }
4082
- async onWatcherRerun(files, trigger) {
4083
- this.stopListRender();
4084
- await super.onWatcherRerun(files, trigger);
4291
+ onFinished() {
4292
+ const finalLog = formatTests(Array.from(this.tests.values()));
4293
+ this.ctx.logger.log(finalLog);
4294
+ this.tests.clear();
4295
+ this.renderer.finish();
4085
4296
  }
4086
- onUserConsoleLog(log) {
4087
- if (!this.shouldLog(log)) {
4297
+ onTestFilePrepare(file) {
4298
+ for (const test of getTests(file)) {
4299
+ this.onTestStart(test);
4300
+ }
4301
+ }
4302
+ onTestStart(test) {
4303
+ if (this.finishedTests.has(test.id)) {
4088
4304
  return;
4089
4305
  }
4090
- this.renderer?.clear();
4091
- super.onUserConsoleLog(log);
4306
+ this.tests.set(test.id, test.mode || "run");
4307
+ }
4308
+ onTestFinished(test) {
4309
+ if (this.finishedTests.has(test.id)) {
4310
+ return;
4311
+ }
4312
+ this.finishedTests.add(test.id);
4313
+ this.tests.set(test.id, test.result?.state || "skip");
4314
+ }
4315
+ onTestFileFinished() {
4316
+ const columns = this.renderer.getColumns();
4317
+ if (this.tests.size < columns) {
4318
+ return;
4319
+ }
4320
+ const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "run");
4321
+ if (finishedTests.length < columns) {
4322
+ return;
4323
+ }
4324
+ const states = [];
4325
+ let count = 0;
4326
+ for (const [id, state] of finishedTests) {
4327
+ if (count++ >= columns) {
4328
+ break;
4329
+ }
4330
+ this.tests.delete(id);
4331
+ states.push(state);
4332
+ }
4333
+ this.ctx.logger.log(formatTests(states));
4334
+ }
4335
+ createSummary() {
4336
+ return [
4337
+ formatTests(Array.from(this.tests.values())),
4338
+ ""
4339
+ ];
4092
4340
  }
4093
4341
  }
4094
-
4095
- const check = { char: "\xB7", color: c.green };
4096
- const cross = { char: "x", color: c.red };
4342
+ const pass = { char: "\xB7", color: c.green };
4343
+ const fail = { char: "x", color: c.red };
4097
4344
  const pending = { char: "*", color: c.yellow };
4098
4345
  const skip = { char: "-", color: (char) => c.dim(c.gray(char)) };
4099
- function getIcon(task) {
4100
- if (task.mode === "skip" || task.mode === "todo") {
4101
- return skip;
4102
- }
4103
- switch (task.result?.state) {
4346
+ function getIcon(state) {
4347
+ switch (state) {
4104
4348
  case "pass":
4105
- return check;
4349
+ return pass;
4106
4350
  case "fail":
4107
- return cross;
4351
+ return fail;
4352
+ case "skip":
4353
+ case "todo":
4354
+ return skip;
4108
4355
  default:
4109
4356
  return pending;
4110
4357
  }
4111
4358
  }
4112
- function render(tasks, width) {
4113
- const all = getTests(tasks);
4359
+ function formatTests(states) {
4114
4360
  let currentIcon = pending;
4115
- let currentTasks = 0;
4116
- let previousLineWidth = 0;
4361
+ let count = 0;
4117
4362
  let output = "";
4118
- const addOutput = () => {
4119
- const { char, color } = currentIcon;
4120
- const availableWidth = width - previousLineWidth;
4121
- if (availableWidth > currentTasks) {
4122
- output += color(char.repeat(currentTasks));
4123
- previousLineWidth += currentTasks;
4124
- } else {
4125
- let buf = `${char.repeat(availableWidth)}
4126
- `;
4127
- const remaining = currentTasks - availableWidth;
4128
- const fullRows = Math.floor(remaining / width);
4129
- buf += `${char.repeat(width)}
4130
- `.repeat(fullRows);
4131
- const partialRow = remaining % width;
4132
- if (partialRow > 0) {
4133
- buf += char.repeat(partialRow);
4134
- previousLineWidth = partialRow;
4135
- } else {
4136
- previousLineWidth = 0;
4137
- }
4138
- output += color(buf);
4139
- }
4140
- };
4141
- for (const task of all) {
4142
- const icon = getIcon(task);
4143
- if (icon === currentIcon) {
4144
- currentTasks++;
4363
+ for (const state of states) {
4364
+ const icon = getIcon(state);
4365
+ if (currentIcon === icon) {
4366
+ count++;
4145
4367
  continue;
4146
4368
  }
4147
- addOutput();
4148
- currentTasks = 1;
4369
+ output += currentIcon.color(currentIcon.char.repeat(count));
4370
+ count = 1;
4149
4371
  currentIcon = icon;
4150
4372
  }
4151
- addOutput();
4373
+ output += currentIcon.color(currentIcon.char.repeat(count));
4152
4374
  return output;
4153
4375
  }
4154
- function createDotRenderer(_tasks, options) {
4155
- let tasks = _tasks;
4156
- let timer;
4157
- const { logUpdate: log, outputStream } = options.logger;
4158
- const columns = "columns" in outputStream ? outputStream.columns : 80;
4159
- function update() {
4160
- log(render(tasks, columns));
4161
- }
4162
- return {
4163
- start() {
4164
- if (timer) {
4165
- return this;
4166
- }
4167
- timer = setInterval(update, 16);
4168
- return this;
4169
- },
4170
- update(_tasks2) {
4171
- tasks = _tasks2;
4172
- return this;
4173
- },
4174
- async stop() {
4175
- if (timer) {
4176
- clearInterval(timer);
4177
- timer = void 0;
4178
- }
4179
- log.clear();
4180
- options.logger.log(render(tasks, columns));
4181
- return this;
4182
- },
4183
- clear() {
4184
- log.clear();
4185
- }
4186
- };
4187
- }
4188
-
4189
- class DotReporter extends BaseReporter {
4190
- renderer;
4191
- onCollected() {
4192
- if (this.isTTY) {
4193
- const files = this.ctx.state.getFiles(this.watchFilters);
4194
- if (!this.renderer) {
4195
- this.renderer = createDotRenderer(files, {
4196
- logger: this.ctx.logger
4197
- }).start();
4198
- } else {
4199
- this.renderer.update(files);
4200
- }
4201
- }
4202
- }
4203
- async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
4204
- await this.stopListRender();
4205
- this.ctx.logger.log();
4206
- super.onFinished(files, errors);
4207
- }
4208
- async onWatcherStart() {
4209
- await this.stopListRender();
4210
- super.onWatcherStart();
4211
- }
4212
- async stopListRender() {
4213
- this.renderer?.stop();
4214
- this.renderer = void 0;
4215
- await new Promise((resolve) => setTimeout(resolve, 10));
4216
- }
4217
- async onWatcherRerun(files, trigger) {
4218
- await this.stopListRender();
4219
- super.onWatcherRerun(files, trigger);
4220
- }
4221
- onUserConsoleLog(log) {
4222
- this.renderer?.clear();
4223
- super.onUserConsoleLog(log);
4224
- }
4225
- }
4226
4376
 
4227
4377
  class GithubActionsReporter {
4228
4378
  ctx = void 0;
@@ -4233,7 +4383,7 @@ class GithubActionsReporter {
4233
4383
  const projectErrors = new Array();
4234
4384
  for (const error of errors) {
4235
4385
  projectErrors.push({
4236
- project: this.ctx.getCoreWorkspaceProject(),
4386
+ project: this.ctx.getRootTestProject(),
4237
4387
  title: "Unhandled error",
4238
4388
  error
4239
4389
  });
@@ -4571,10 +4721,22 @@ class JUnitReporter {
4571
4721
  }
4572
4722
  async writeTasks(tasks, filename) {
4573
4723
  for (const task of tasks) {
4724
+ let classname = filename;
4725
+ const templateVars = {
4726
+ filename: task.file.name,
4727
+ filepath: task.file.filepath
4728
+ };
4729
+ if (typeof this.options.classnameTemplate === "function") {
4730
+ classname = this.options.classnameTemplate(templateVars);
4731
+ } else if (typeof this.options.classnameTemplate === "string") {
4732
+ classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
4733
+ } else if (typeof this.options.classname === "string") {
4734
+ classname = this.options.classname;
4735
+ }
4574
4736
  await this.writeElement(
4575
4737
  "testcase",
4576
4738
  {
4577
- classname: this.options.classname ?? filename,
4739
+ classname,
4578
4740
  file: this.options.addFileAttribute ? filename : void 0,
4579
4741
  name: task.name,
4580
4742
  time: getDuration(task)
@@ -4666,6 +4828,7 @@ class JUnitReporter {
4666
4828
  (stats2, file) => {
4667
4829
  stats2.tests += file.tasks.length;
4668
4830
  stats2.failures += file.stats.failures;
4831
+ stats2.time += file.result?.duration || 0;
4669
4832
  return stats2;
4670
4833
  },
4671
4834
  {
@@ -4674,10 +4837,10 @@ class JUnitReporter {
4674
4837
  failures: 0,
4675
4838
  errors: 0,
4676
4839
  // we cannot detect those
4677
- time: executionTime((/* @__PURE__ */ new Date()).getTime() - this._timeStart.getTime())
4840
+ time: 0
4678
4841
  }
4679
4842
  );
4680
- await this.writeElement("testsuites", stats, async () => {
4843
+ await this.writeElement("testsuites", { ...stats, time: executionTime(stats.time) }, async () => {
4681
4844
  for (const file of transformed) {
4682
4845
  const filename = relative(this.ctx.config.root, file.filepath);
4683
4846
  await this.writeElement(
@@ -4820,13 +4983,10 @@ class TapFlatReporter extends TapReporter {
4820
4983
 
4821
4984
  class VerboseReporter extends DefaultReporter {
4822
4985
  verbose = true;
4823
- constructor() {
4824
- super();
4825
- this.rendererOptions.renderSucceed = true;
4826
- }
4986
+ renderSucceed = true;
4827
4987
  onTaskUpdate(packs) {
4828
4988
  if (this.isTTY) {
4829
- return;
4989
+ return super.onTaskUpdate(packs);
4830
4990
  }
4831
4991
  for (const pack of packs) {
4832
4992
  const task = this.ctx.state.idMap.get(pack[0]);
@@ -5250,4 +5410,4 @@ const ReportersMap = {
5250
5410
  "github-actions": GithubActionsReporter
5251
5411
  };
5252
5412
 
5253
- export { BasicReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, Logger as L, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, TestFile as d, TestCase as e, TestModule as f, TestSuite as g, BenchmarkReportsMap as h, TestProject as i, generateCodeFrame as j, BlobReporter as k, parse as p, readBlobs as r, stringify as s };
5413
+ export { BasicReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, Logger as L, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, TestFile as d, TestCase as e, TestModule as f, TestSuite as g, BenchmarkReportsMap as h, generateCodeFrame as i, BlobReporter as j, parse as p, readBlobs as r, stringify as s };