vitest-auto-spy 5.12.0 → 5.14.0

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 (2) hide show
  1. package/dist/cli.js +125 -36
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
19
19
  "max-file-ms",
20
20
  "max-test-ms",
21
21
  "max-wall-ms",
22
+ "min-severity",
22
23
  "only",
23
24
  "out",
24
25
  "skip",
@@ -416,11 +417,12 @@ function formatOne(finding2) {
416
417
  "\n"
417
418
  );
418
419
  }
419
- function formatFindings(findings) {
420
- if (findings.length === 0) {
420
+ function formatFindings(findings, minSeverity = "info") {
421
+ const reported = findings.filter((finding2) => SEVERITY_ORDER[finding2.severity] <= SEVERITY_ORDER[minSeverity]);
422
+ if (reported.length === 0) {
421
423
  return "";
422
424
  }
423
- return sortFindings(findings).map(formatOne).join("\n\n");
425
+ return sortFindings(reported).map(formatOne).join("\n\n");
424
426
  }
425
427
  function summarize(findings) {
426
428
  const counts = { error: 0, warning: 0, info: 0 };
@@ -2453,7 +2455,7 @@ function checkForeignPragma(graph) {
2453
2455
  }
2454
2456
 
2455
2457
  // src/cli/checks/export-map.generated.ts
2456
- var EXPORT_MAP_VERSION = "5.12.0";
2458
+ var EXPORT_MAP_VERSION = "5.14.0";
2457
2459
  var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin";
2458
2460
  var EXPORTED_BY = {
2459
2461
  AccessorImplementations: "0 1 2 3 4 12 17 18 19",
@@ -3484,6 +3486,11 @@ Options
3484
3486
  perf only. How many times its recorded share a file has to
3485
3487
  take (2), and the absolute floor under which it is noise
3486
3488
  (500 ms).
3489
+ --min-severity <error|warning|info>
3490
+ The quietest findings the report prints. Default info, which
3491
+ prints everything. The tally line still counts what was hidden,
3492
+ and nothing about the exit code moves \u2014 a note never failed a
3493
+ run. Works for doctor and for perf.
3487
3494
  --top <n> perf only. Rows in the "slowest files" and "slowest bodies"
3488
3495
  tables. 0 turns them off. Asked for explicitly, a run whose
3489
3496
  slowest file is under the one-second floor says so rather
@@ -4305,6 +4312,17 @@ var NO_PER_TEST = "\u2014";
4305
4312
  var INDENT = " ";
4306
4313
  var GAP = 4;
4307
4314
  var ELLIPSIS = "\u2026";
4315
+ var RED = "\x1B[31m";
4316
+ var DIM = "\x1B[2m";
4317
+ var OFF = "\x1B[0m";
4318
+ var MONOCHROME = { red: (text) => text, dim: (text) => text };
4319
+ var TERMINAL = {
4320
+ red: (text) => `${RED}${text}${OFF}`,
4321
+ dim: (text) => `${DIM}${text}${OFF}`
4322
+ };
4323
+ function colorWanted(env) {
4324
+ return env["NO_COLOR"] === void 0 && env["FORCE_COLOR"] !== "0" && env["TERM"] !== "dumb";
4325
+ }
4308
4326
  function fileHotspots(run, cwd, limit) {
4309
4327
  const found = [];
4310
4328
  for (const [file, measured] of measuredFiles(run, cwd)) {
@@ -4331,28 +4349,58 @@ function caseHotspots(run, cwd, limit) {
4331
4349
  function totalTestMs(run, cwd) {
4332
4350
  return [...measuredFiles(run, cwd).values()].reduce((total, file) => total + file.tests, 0);
4333
4351
  }
4334
- function truncateLeft(text, width) {
4335
- return text.length <= width ? text : `${ELLIPSIS}${text.slice(text.length - width + ELLIPSIS.length)}`;
4352
+ function truncateLeft(text, width, paint) {
4353
+ if (text.length <= width) {
4354
+ return text;
4355
+ }
4356
+ return `${paint.dim(ELLIPSIS)}${text.slice(text.length - width + ELLIPSIS.length)}`;
4357
+ }
4358
+ function truncatePath(path, width, paint) {
4359
+ if (path.length <= width) {
4360
+ return path;
4361
+ }
4362
+ const first = path.indexOf("/");
4363
+ if (first === -1) {
4364
+ return truncateLeft(path, width, paint);
4365
+ }
4366
+ const head = path.slice(0, first);
4367
+ const rest = path.slice(first + 1).split("/");
4368
+ const budget = width - head.length - 3;
4369
+ const tail = [];
4370
+ let used = 0;
4371
+ for (const segment of [...rest].reverse()) {
4372
+ const next = used === 0 ? segment.length : used + 1 + segment.length;
4373
+ if (next > budget) {
4374
+ break;
4375
+ }
4376
+ tail.unshift(segment);
4377
+ used = next;
4378
+ }
4379
+ if (tail.length === 0) {
4380
+ return truncateLeft(path, width, paint);
4381
+ }
4382
+ return `${head}/${paint.dim(ELLIPSIS)}/${tail.join("/")}`;
4336
4383
  }
4337
4384
  function widthOf(head, cells) {
4338
4385
  return cells.reduce((widest, cell) => Math.max(widest, cell.length), head.length);
4339
4386
  }
4340
- function table(label, numbers, width) {
4387
+ function table(label, numbers, width, truncate, decorate) {
4341
4388
  const spent = numbers.reduce((sum, column) => sum + widthOf(column.head, column.cells) + GAP, 0);
4342
4389
  const labelWidth = Math.max(Math.min(widthOf(label.head, label.cells), width - INDENT.length - spent), label.head.length);
4343
4390
  const laid = [
4344
- { width: labelWidth, left: true, texts: [label.head, ...label.cells.map((cell) => truncateLeft(cell, labelWidth))] },
4391
+ { width: labelWidth, left: true, texts: [label.head, ...label.cells.map((cell) => truncate(cell, labelWidth))] },
4345
4392
  ...numbers.map((column) => ({ width: widthOf(column.head, column.cells) + GAP, left: false, texts: [column.head, ...column.cells] }))
4346
4393
  ];
4347
4394
  return laid.reduce(
4348
- (lines, column) => column.texts.map(
4349
- (text, index) => `${lines[index] ?? INDENT}${column.left ? text.padEnd(column.width) : text.padStart(column.width)}`
4395
+ (lines, column, current) => column.texts.map(
4396
+ (text, index) => `${lines[index] ?? INDENT}${decorate(index, current, column.left ? text.padEnd(column.width) : text.padStart(column.width))}`
4350
4397
  ),
4351
4398
  []
4352
4399
  ).join("\n");
4353
4400
  }
4354
- function fileSection(files, total, width) {
4401
+ function fileSection(files, total, width, floorMs, paint) {
4355
4402
  const listed = files.reduce((sum, hotspot) => sum + hotspot.ms, 0);
4403
+ const overBudget = new Set(files.flatMap((hotspot, index) => hotspot.testCount > 0 && hotspot.perTest >= floorMs ? [index + 1] : []));
4356
4404
  const drawn = table(
4357
4405
  { head: "file", cells: files.map((hotspot) => hotspot.file) },
4358
4406
  [
@@ -4360,7 +4408,9 @@ function fileSection(files, total, width) {
4360
4408
  { head: "ms/test", cells: files.map((hotspot) => hotspot.testCount === 0 ? NO_PER_TEST : formatMs(hotspot.perTest)) },
4361
4409
  { head: "share", cells: files.map((hotspot) => formatShare(hotspot.ms / total)) }
4362
4410
  ],
4363
- width
4411
+ width,
4412
+ (cell, columnWidth) => truncatePath(cell, columnWidth, paint),
4413
+ (row, column, padded) => row === 0 ? paint.dim(padded) : column === 2 && overBudget.has(row) ? paint.red(padded) : padded
4364
4414
  );
4365
4415
  const counted = files.length === 1 ? "That one file is" : `Those ${files.length} files are`;
4366
4416
  const prose = [
@@ -4370,27 +4420,51 @@ function fileSection(files, total, width) {
4370
4420
  ].join("\n");
4371
4421
  return ["slowest files \u2014 what every body in the file cost, and what one of them cost", drawn, prose];
4372
4422
  }
4373
- function caseSection(cases, width) {
4423
+ function caseSection(cases, width, floorMs, paint) {
4374
4424
  if (cases.length === 0) {
4375
4425
  return [];
4376
4426
  }
4427
+ const timeWidth = widthOf(
4428
+ "time",
4429
+ cases.map((entry) => formatMs(entry.ms))
4430
+ ) + GAP;
4431
+ const nameWidth = Math.max(width - 2 * INDENT.length - timeWidth, "test".length);
4432
+ const groups = /* @__PURE__ */ new Map();
4433
+ for (const entry of cases) {
4434
+ const bucket = groups.get(entry.file);
4435
+ bucket === void 0 ? groups.set(entry.file, [entry]) : bucket.push(entry);
4436
+ }
4437
+ const lines = [];
4438
+ for (const [file, entries] of groups) {
4439
+ lines.push(`${INDENT}${truncatePath(file, width - INDENT.length, paint)}`);
4440
+ for (const entry of entries) {
4441
+ const time = formatMs(entry.ms).padStart(timeWidth);
4442
+ lines.push(
4443
+ `${INDENT}${INDENT}${truncateLeft(entry.name, nameWidth, paint).padEnd(nameWidth)}${entry.ms >= floorMs ? paint.red(time) : time}`
4444
+ );
4445
+ }
4446
+ }
4377
4447
  return [
4378
4448
  `slowest test bodies \u2014 a body under ${formatMs(CASE_FLOOR_MS)} is not in the report at all, so a fast one is absent rather than cheap`,
4379
- table(
4380
- { head: "test", cells: cases.map((entry) => `${entry.file} \u203A ${entry.name}`) },
4381
- [{ head: "time", cells: cases.map((entry) => formatMs(entry.ms)) }],
4382
- width
4383
- )
4449
+ lines.join("\n")
4384
4450
  ];
4385
4451
  }
4386
4452
  function formatHotspots(run, cwd, options = {}) {
4387
- const { limit, floorMs, width } = { ...HOTSPOT_DEFAULTS, ...options };
4453
+ const { limit, floorMs, width, colors } = { ...HOTSPOT_DEFAULTS, ...options };
4454
+ const paint = withColors(colors ?? colorWanted(process.env));
4388
4455
  const files = fileHotspots(run, cwd, limit);
4389
4456
  const [slowest] = files;
4390
4457
  if (slowest === void 0 || slowest.ms < floorMs) {
4391
4458
  return "";
4392
4459
  }
4393
- return [...fileSection(files, totalTestMs(run, cwd), width), ...caseSection(caseHotspots(run, cwd, limit), width)].join("\n\n");
4460
+ const sections = [
4461
+ ...fileSection(files, totalTestMs(run, cwd), width, floorMs, paint),
4462
+ ...caseSection(caseHotspots(run, cwd, limit), width, floorMs, paint)
4463
+ ];
4464
+ return sections.join("\n\n");
4465
+ }
4466
+ function withColors(on) {
4467
+ return on ? TERMINAL : MONOCHROME;
4394
4468
  }
4395
4469
  function hotspotFloorNote(run, cwd, floorMs = HOTSPOT_DEFAULTS.floorMs) {
4396
4470
  const [slowest] = fileHotspots(run, cwd, 1);
@@ -4709,16 +4783,22 @@ function formatPhases(phases) {
4709
4783
  const rows = phases.map((phase) => ` ${phase.name.padEnd(14)}${formatMs(phase.ms).padStart(10)}${formatShare(phase.share).padStart(9)}`);
4710
4784
  return [header2, ...rows].join("\n");
4711
4785
  }
4712
- function reportFindings(analysis, io) {
4786
+ function reportFindings(analysis, io, minSeverity) {
4713
4787
  if (analysis.findings.length === 0) {
4714
4788
  io.out(`
4715
4789
  ${nothingToDo(analysis)}`);
4716
4790
  return;
4717
4791
  }
4792
+ reportOrTally(analysis.findings, io, minSeverity);
4793
+ }
4794
+ function reportOrTally(findings, io, minSeverity) {
4795
+ const report2 = formatFindings(findings, minSeverity);
4796
+ if (report2 !== "") {
4797
+ io.out(`
4798
+ ${report2}`);
4799
+ }
4718
4800
  io.out(`
4719
- ${formatFindings(analysis.findings)}`);
4720
- io.out(`
4721
- ${summarize(analysis.findings)}`);
4801
+ ${summarize(findings)}`);
4722
4802
  }
4723
4803
  var PERF_GATE_FAILED = 1;
4724
4804
  var PERF_NO_MEASUREMENT = 2;
@@ -4771,7 +4851,7 @@ function unmatchedScope(run, cwd, only) {
4771
4851
  const paths = [...measuredFiles(run, cwd).keys()];
4772
4852
  return only.filter((entry) => !paths.some((path) => isJudged(path, [entry])));
4773
4853
  }
4774
- function runGate(run, cwd, io, gate, extra) {
4854
+ function runGate(run, cwd, io, gate, extra, minSeverity) {
4775
4855
  const unmatched = unmatchedScope(run, cwd, gate.options.only);
4776
4856
  if (unmatched.length > 0) {
4777
4857
  io.err(
@@ -4789,10 +4869,7 @@ perf gate: nothing over budget \u2014 no test body over ${formatMs(gate.options.
4789
4869
  return 0;
4790
4870
  }
4791
4871
  const verdict = gateVerdict(candidates2, confirmRun(gate, suspectFiles(candidates2), cwd, io), cwd, gate.trustSingle);
4792
- io.out(`
4793
- ${formatFindings(verdict.findings)}`);
4794
- io.out(`
4795
- ${summarize(verdict.findings)}`);
4872
+ reportOrTally(verdict.findings, io, minSeverity);
4796
4873
  return verdict.failed ? PERF_GATE_FAILED : 0;
4797
4874
  }
4798
4875
  function baselineCandidates(run, cwd, request, io) {
@@ -4868,7 +4945,7 @@ function renderPerf(source, profile, io, options = {}) {
4868
4945
  }
4869
4946
  io.out(formatPhases(analysis.phases));
4870
4947
  reportHotspots(source.run, profile.cwd, options.top, io);
4871
- reportFindings(analysis, io);
4948
+ reportFindings(analysis, io, options.minSeverity);
4872
4949
  if (options.baseline?.update === true) {
4873
4950
  recordBaseline(source.run, profile.cwd, options.baseline.path, io);
4874
4951
  return 0;
@@ -4877,7 +4954,7 @@ function renderPerf(source, profile, io, options = {}) {
4877
4954
  if (gate === void 0) {
4878
4955
  if (regressions.length > 0) {
4879
4956
  io.out(`
4880
- ${formatFindings(gateVerdict(regressions, void 0, profile.cwd, false).findings)}`);
4957
+ ${formatFindings(gateVerdict(regressions, void 0, profile.cwd, false).findings, options.minSeverity)}`);
4881
4958
  }
4882
4959
  return 0;
4883
4960
  }
@@ -4887,7 +4964,7 @@ ${formatFindings(gateVerdict(regressions, void 0, profile.cwd, false).findings)}
4887
4964
  );
4888
4965
  return PERF_NO_MEASUREMENT;
4889
4966
  }
4890
- return runGate(source.run, profile.cwd, io, gate, regressions);
4967
+ return runGate(source.run, profile.cwd, io, gate, regressions, options.minSeverity);
4891
4968
  }
4892
4969
  var ROOT_CONFIGS = [
4893
4970
  "vitest.config.ts",
@@ -5198,7 +5275,14 @@ function perfRemeasure(options, spawn = spawnProcess, packageRoot = ownPackageRo
5198
5275
 
5199
5276
  // src/cli/main.ts
5200
5277
  var STATUS_WIDTH = 10;
5201
- function doctorCommand(cwd, io) {
5278
+ function minSeverityOf(args) {
5279
+ const raw = flagValue(args, "min-severity")?.trim().toLowerCase();
5280
+ if (raw === "error" || raw === "info") {
5281
+ return raw;
5282
+ }
5283
+ return raw === "warning" || raw === "warn" ? "warning" : void 0;
5284
+ }
5285
+ function doctorCommand(cwd, argv, io) {
5202
5286
  const profile = readProfile(cwd);
5203
5287
  const findings = runDoctor(profile);
5204
5288
  io.out(`vitest-auto-spy doctor \u2014 ${cwd}`);
@@ -5208,7 +5292,10 @@ function doctorCommand(cwd, io) {
5208
5292
  io.out("No problems found.");
5209
5293
  return 0;
5210
5294
  }
5211
- io.out(formatFindings(findings));
5295
+ const report2 = formatFindings(findings, minSeverityOf(parseArgs(argv)));
5296
+ if (report2 !== "") {
5297
+ io.out(report2);
5298
+ }
5212
5299
  io.out(`
5213
5300
  ${summarize(findings)}`);
5214
5301
  return hasFailures(findings) ? 1 : 0;
@@ -5252,10 +5339,12 @@ function perfCommand(cwd, argv, io) {
5252
5339
  const gate = flagEnabled(args, "gate") ? { options: gateOptions(args), remeasure: trustSingle ? void 0 : perfRemeasure(options), trustSingle } : void 0;
5253
5340
  const baseline = baselineRequest(args, cwd);
5254
5341
  const top = flagNumber(args, "top");
5342
+ const minSeverity = minSeverityOf(args);
5255
5343
  return renderPerf(readPerfRun(options), profile, io, {
5256
5344
  ...gate === void 0 ? {} : { gate },
5257
5345
  ...baseline === void 0 ? {} : { baseline },
5258
- ...top === void 0 ? {} : { top }
5346
+ ...top === void 0 ? {} : { top },
5347
+ ...minSeverity === void 0 ? {} : { minSeverity }
5259
5348
  });
5260
5349
  }
5261
5350
  function formatAction(action) {
@@ -5318,7 +5407,7 @@ function runCli(argv, io) {
5318
5407
  return args.command === void 0 && !flagEnabled(args, "help") ? 2 : 0;
5319
5408
  }
5320
5409
  if (args.command === "doctor") {
5321
- return doctorCommand(cwd, io);
5410
+ return doctorCommand(cwd, argv, io);
5322
5411
  }
5323
5412
  if (args.command === "init") {
5324
5413
  return initCommand(cwd, argv, io);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.12.0",
3
+ "version": "5.14.0",
4
4
  "description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
5
5
  "keywords": [
6
6
  "auto-mock",