as-test 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -3,7 +3,7 @@ import chalk from "chalk";
3
3
  import { build } from "./build.js";
4
4
  import { createRunReporter, run } from "./run.js";
5
5
  import { init } from "./init.js";
6
- import { getCliVersion, loadConfig } from "./util.js";
6
+ import { getCliVersion, loadConfig, resolveModeNames } from "./util.js";
7
7
  import * as path from "path";
8
8
  import { glob } from "glob";
9
9
  const _args = process.argv.slice(2);
@@ -12,6 +12,7 @@ const args = [];
12
12
  const COMMANDS = ["run", "build", "test", "init"];
13
13
  const version = getCliVersion();
14
14
  const configPath = resolveConfigPath(_args);
15
+ const selectedModes = resolveModeNames(_args);
15
16
  for (const arg of _args) {
16
17
  if (arg.startsWith("-"))
17
18
  flags.push(arg);
@@ -27,36 +28,54 @@ if (!args.length) {
27
28
  }
28
29
  }
29
30
  else if (COMMANDS.includes(args[0])) {
30
- const command = args.shift();
31
- const commandArgs = resolveCommandArgs(_args, command ?? "");
32
- const runFlags = {
33
- snapshot: !flags.includes("--no-snapshot"),
34
- updateSnapshots: flags.includes("--update-snapshots"),
35
- clean: flags.includes("--clean"),
36
- showCoverage: flags.includes("--show-coverage"),
37
- verbose: flags.includes("--verbose"),
38
- };
39
- if (command === "build") {
40
- build(configPath).catch((error) => {
41
- printCliError(error);
42
- process.exit(1);
43
- });
44
- }
45
- else if (command === "run") {
46
- run(runFlags, configPath);
47
- }
48
- else if (command === "test") {
49
- runTestSequential(runFlags, configPath, commandArgs).catch((error) => {
50
- printCliError(error);
51
- process.exit(1);
52
- });
31
+ try {
32
+ const command = args.shift();
33
+ const commandArgs = resolveCommandArgs(_args, command ?? "");
34
+ const featureToggles = resolveFeatureToggles(_args, command ?? "");
35
+ const buildFeatureToggles = {
36
+ tryAs: featureToggles.tryAs,
37
+ coverage: featureToggles.coverage,
38
+ };
39
+ const runFlags = {
40
+ snapshot: !flags.includes("--no-snapshot"),
41
+ updateSnapshots: flags.includes("--update-snapshots"),
42
+ clean: flags.includes("--clean"),
43
+ showCoverage: flags.includes("--show-coverage"),
44
+ verbose: flags.includes("--verbose"),
45
+ coverage: featureToggles.coverage,
46
+ };
47
+ if (command === "build") {
48
+ const modeTargets = resolveExecutionModes(configPath, selectedModes);
49
+ runBuildModes(configPath, commandArgs, modeTargets, buildFeatureToggles).catch((error) => {
50
+ printCliError(error);
51
+ process.exit(1);
52
+ });
53
+ }
54
+ else if (command === "run") {
55
+ const modeTargets = resolveExecutionModes(configPath, selectedModes);
56
+ runRuntimeModes(runFlags, configPath, commandArgs, modeTargets).catch((error) => {
57
+ printCliError(error);
58
+ process.exit(1);
59
+ });
60
+ }
61
+ else if (command === "test") {
62
+ const modeTargets = resolveExecutionModes(configPath, selectedModes);
63
+ runTestModes(runFlags, configPath, commandArgs, modeTargets, buildFeatureToggles).catch((error) => {
64
+ printCliError(error);
65
+ process.exit(1);
66
+ });
67
+ }
68
+ else if (command === "init") {
69
+ const commandTokens = resolveCommandTokens(_args, command ?? "");
70
+ init(commandTokens).catch((error) => {
71
+ printCliError(error);
72
+ process.exit(1);
73
+ });
74
+ }
53
75
  }
54
- else if (command === "init") {
55
- const commandTokens = resolveCommandTokens(_args, command ?? "");
56
- init(commandTokens).catch((error) => {
57
- printCliError(error);
58
- process.exit(1);
59
- });
76
+ catch (error) {
77
+ printCliError(error);
78
+ process.exit(1);
60
79
  }
61
80
  }
62
81
  else {
@@ -109,6 +128,10 @@ function info() {
109
128
  "Initialize an empty testing template");
110
129
  console.log("");
111
130
  console.log(chalk.bold("Flags:"));
131
+ console.log(" " +
132
+ chalk.bold.blue("--mode <name[,name...]>") +
133
+ " " +
134
+ "Run one or multiple named config modes");
112
135
  console.log(" " +
113
136
  chalk.bold.blue("--config <path>") +
114
137
  " " +
@@ -129,10 +152,22 @@ function info() {
129
152
  chalk.bold.blue("--show-coverage") +
130
153
  " " +
131
154
  "Print all coverage points with line:column refs");
155
+ console.log(" " +
156
+ chalk.bold.blue("--enable <feature>") +
157
+ " " +
158
+ "Enable as-test feature (coverage|try-as)");
159
+ console.log(" " +
160
+ chalk.bold.blue("--disable <feature>") +
161
+ " " +
162
+ "Disable as-test feature (coverage|try-as)");
132
163
  console.log(" " +
133
164
  chalk.bold.blue("--verbose") +
134
165
  " " +
135
166
  "Print each suite start/end line");
167
+ console.log(" " +
168
+ chalk.bold.blue("--reporter <name|path>") +
169
+ " " +
170
+ "Use built-in reporter (default|tap) or custom module path");
136
171
  console.log("");
137
172
  console.log(chalk.dim("If your using this, consider dropping a star, it would help a lot!") + "\n");
138
173
  console.log("View the repo: " +
@@ -176,9 +211,33 @@ function resolveCommandArgs(rawArgs, command) {
176
211
  i++;
177
212
  continue;
178
213
  }
214
+ if (arg == "--mode") {
215
+ i++;
216
+ continue;
217
+ }
179
218
  if (arg.startsWith("--config=")) {
180
219
  continue;
181
220
  }
221
+ if (arg.startsWith("--mode=")) {
222
+ continue;
223
+ }
224
+ if (arg == "--reporter") {
225
+ i++;
226
+ continue;
227
+ }
228
+ if (arg.startsWith("--reporter=")) {
229
+ continue;
230
+ }
231
+ if (arg == "--tap") {
232
+ continue;
233
+ }
234
+ if (arg == "--enable" || arg == "--disable") {
235
+ i++;
236
+ continue;
237
+ }
238
+ if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
239
+ continue;
240
+ }
182
241
  if (arg.startsWith("-")) {
183
242
  continue;
184
243
  }
@@ -186,6 +245,50 @@ function resolveCommandArgs(rawArgs, command) {
186
245
  }
187
246
  return values;
188
247
  }
248
+ function resolveFeatureToggles(rawArgs, command) {
249
+ if (command !== "build" && command !== "run" && command !== "test")
250
+ return {};
251
+ const out = {};
252
+ let seenCommand = false;
253
+ for (let i = 0; i < rawArgs.length; i++) {
254
+ const arg = rawArgs[i];
255
+ if (!seenCommand) {
256
+ if (arg == command)
257
+ seenCommand = true;
258
+ continue;
259
+ }
260
+ if (arg == "--enable" || arg == "--disable") {
261
+ const enabled = arg == "--enable";
262
+ const next = rawArgs[i + 1];
263
+ if (next && !next.startsWith("-")) {
264
+ applyFeatureToggle(out, next, enabled);
265
+ i++;
266
+ }
267
+ continue;
268
+ }
269
+ if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
270
+ const enabled = arg.startsWith("--enable=");
271
+ const eq = arg.indexOf("=");
272
+ const value = arg.slice(eq + 1).trim();
273
+ if (value.length) {
274
+ applyFeatureToggle(out, value, enabled);
275
+ }
276
+ }
277
+ }
278
+ return out;
279
+ }
280
+ function applyFeatureToggle(out, rawFeature, enabled) {
281
+ const key = rawFeature.trim().toLowerCase();
282
+ if (key == "coverage") {
283
+ out.coverage = enabled;
284
+ return;
285
+ }
286
+ if (key == "try-as" || key == "try_as" || key == "tryas") {
287
+ out.tryAs = enabled;
288
+ return;
289
+ }
290
+ throw new Error(`unknown feature "${rawFeature}". Supported features: coverage, try-as`);
291
+ }
189
292
  function resolveCommandTokens(rawArgs, command) {
190
293
  const values = [];
191
294
  let seenCommand = false;
@@ -200,7 +303,7 @@ function resolveCommandTokens(rawArgs, command) {
200
303
  }
201
304
  return values;
202
305
  }
203
- async function runTestSequential(runFlags, configPath, selectors) {
306
+ async function runTestSequential(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, modeName) {
204
307
  const files = await resolveSelectedFiles(configPath, selectors);
205
308
  if (!files.length) {
206
309
  const scope = selectors.length > 0
@@ -208,7 +311,7 @@ async function runTestSequential(runFlags, configPath, selectors) {
208
311
  : "configured input patterns";
209
312
  throw new Error(`No test files matched: ${scope}`);
210
313
  }
211
- const reporterSession = await createRunReporter(configPath);
314
+ const reporterSession = await createRunReporter(configPath, undefined, modeName);
212
315
  const reporter = reporterSession.reporter;
213
316
  const snapshotEnabled = runFlags.snapshot !== false;
214
317
  reporter.onRunStart?.({
@@ -221,7 +324,7 @@ async function runTestSequential(runFlags, configPath, selectors) {
221
324
  const results = [];
222
325
  let failed = false;
223
326
  for (const file of files) {
224
- await build(configPath, [file]);
327
+ await build(configPath, [file], modeName, buildFeatureToggles);
225
328
  const artifactKey = path.basename(file).replace(/[^a-zA-Z0-9._-]/g, "_");
226
329
  const result = await run(runFlags, configPath, [file], false, {
227
330
  reporter,
@@ -229,12 +332,133 @@ async function runTestSequential(runFlags, configPath, selectors) {
229
332
  emitRunComplete: false,
230
333
  logFileName: `test.${artifactKey}.log.json`,
231
334
  coverageFileName: `coverage.${artifactKey}.log.json`,
335
+ modeName,
232
336
  });
233
337
  results.push(result);
234
338
  if (result?.failed)
235
339
  failed = true;
236
340
  }
237
341
  const summary = aggregateRunResults(results);
342
+ summary.stats = applyConfiguredFileTotalToStats(summary.stats, fileSummaryTotal);
343
+ reporter.onRunComplete?.({
344
+ clean: runFlags.clean,
345
+ snapshotEnabled,
346
+ showCoverage: runFlags.showCoverage,
347
+ snapshotSummary: summary.snapshotSummary,
348
+ coverageSummary: summary.coverageSummary,
349
+ stats: summary.stats,
350
+ reports: summary.reports,
351
+ modeSummary: buildSingleModeSummary(summary.stats, summary.snapshotSummary, modeSummaryTotal),
352
+ });
353
+ return failed;
354
+ }
355
+ async function runBuildModes(configPath, selectors, modes, buildFeatureToggles) {
356
+ for (const modeName of modes) {
357
+ await build(configPath, selectors, modeName, buildFeatureToggles);
358
+ }
359
+ }
360
+ async function runRuntimeModes(runFlags, configPath, selectors, modes) {
361
+ const modeSummaryTotal = resolveConfiguredModeTotal(configPath);
362
+ const fileSummaryTotal = await resolveConfiguredFileTotal(configPath);
363
+ if (modes.length > 1) {
364
+ const failed = await runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal);
365
+ process.exit(failed ? 1 : 0);
366
+ return;
367
+ }
368
+ let failed = false;
369
+ for (const modeName of modes) {
370
+ const result = await run(runFlags, configPath, selectors, false, {
371
+ modeName,
372
+ modeSummaryTotal,
373
+ modeSummaryExecuted: 1,
374
+ fileSummaryTotal,
375
+ });
376
+ if (result.failed)
377
+ failed = true;
378
+ }
379
+ process.exit(failed ? 1 : 0);
380
+ }
381
+ async function runRuntimeMatrix(runFlags, configPath, selectors, modes, modeSummaryTotal, fileSummaryTotal) {
382
+ const files = await resolveSelectedFiles(configPath, selectors);
383
+ if (!files.length) {
384
+ const scope = selectors.length > 0
385
+ ? selectors.join(", ")
386
+ : "configured input patterns";
387
+ throw new Error(`No test files matched: ${scope}`);
388
+ }
389
+ const reporterSession = await createRunReporter(configPath);
390
+ const reporter = reporterSession.reporter;
391
+ const snapshotEnabled = runFlags.snapshot !== false;
392
+ reporter.onRunStart?.({
393
+ runtimeName: reporterSession.runtimeName,
394
+ clean: runFlags.clean,
395
+ verbose: runFlags.verbose,
396
+ snapshotEnabled,
397
+ updateSnapshots: runFlags.updateSnapshots,
398
+ });
399
+ const silentReporter = {};
400
+ const allResults = [];
401
+ const modeLabels = modes.map((modeName) => modeName ?? "default");
402
+ const showPerModeTimes = Boolean(runFlags.verbose);
403
+ const liveMatrix = reporterSession.reporterKind == "default" && canRewriteStdout();
404
+ const modeState = modes.map(() => ({
405
+ failed: false,
406
+ passed: false,
407
+ }));
408
+ const fileState = files.map(() => ({
409
+ failed: false,
410
+ passed: false,
411
+ }));
412
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
413
+ const file = files[fileIndex];
414
+ const fileName = path.basename(file);
415
+ const fileResults = [];
416
+ const modeTimes = modes.map(() => "...");
417
+ if (liveMatrix) {
418
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
419
+ }
420
+ for (let i = 0; i < modes.length; i++) {
421
+ const modeName = modes[i];
422
+ try {
423
+ const artifactKey = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
424
+ const result = await run(runFlags, configPath, [file], false, {
425
+ reporter: silentReporter,
426
+ reporterKind: "default",
427
+ emitRunStart: false,
428
+ emitRunComplete: false,
429
+ logFileName: `run.${artifactKey}.log.json`,
430
+ coverageFileName: `coverage.${artifactKey}.log.json`,
431
+ modeName,
432
+ });
433
+ modeTimes[i] = formatMatrixModeTime(result.stats.time);
434
+ if (liveMatrix) {
435
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
436
+ }
437
+ if (result.failed) {
438
+ modeState[i].failed = true;
439
+ }
440
+ else if (result.stats.passedFiles > 0) {
441
+ modeState[i].passed = true;
442
+ }
443
+ fileResults.push(result);
444
+ allResults.push(result);
445
+ }
446
+ catch (error) {
447
+ clearLiveLine();
448
+ throw error;
449
+ }
450
+ }
451
+ renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
452
+ const verdict = resolveMatrixVerdict(fileResults);
453
+ if (verdict == "fail") {
454
+ fileState[fileIndex].failed = true;
455
+ }
456
+ else if (verdict == "ok") {
457
+ fileState[fileIndex].passed = true;
458
+ }
459
+ }
460
+ const summary = aggregateRunResults(allResults);
461
+ summary.stats = applyMatrixFileSummaryToStats(summary.stats, fileState, fileSummaryTotal);
238
462
  reporter.onRunComplete?.({
239
463
  clean: runFlags.clean,
240
464
  snapshotEnabled,
@@ -243,23 +467,264 @@ async function runTestSequential(runFlags, configPath, selectors) {
243
467
  coverageSummary: summary.coverageSummary,
244
468
  stats: summary.stats,
245
469
  reports: summary.reports,
470
+ modeSummary: buildModeSummary(modeState, modeSummaryTotal),
246
471
  });
472
+ return allResults.some((result) => result.failed);
473
+ }
474
+ async function runTestModes(runFlags, configPath, selectors, modes, buildFeatureToggles) {
475
+ const modeSummaryTotal = resolveConfiguredModeTotal(configPath);
476
+ const fileSummaryTotal = await resolveConfiguredFileTotal(configPath);
477
+ if (modes.length > 1) {
478
+ const failed = await runTestMatrix(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal);
479
+ process.exit(failed ? 1 : 0);
480
+ return;
481
+ }
482
+ let failed = false;
483
+ for (const modeName of modes) {
484
+ const modeFailed = await runTestSequential(runFlags, configPath, selectors, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal, modeName);
485
+ if (modeFailed)
486
+ failed = true;
487
+ }
247
488
  process.exit(failed ? 1 : 0);
248
489
  }
490
+ async function runTestMatrix(runFlags, configPath, selectors, modes, buildFeatureToggles, modeSummaryTotal, fileSummaryTotal) {
491
+ const files = await resolveSelectedFiles(configPath, selectors);
492
+ if (!files.length) {
493
+ const scope = selectors.length > 0
494
+ ? selectors.join(", ")
495
+ : "configured input patterns";
496
+ throw new Error(`No test files matched: ${scope}`);
497
+ }
498
+ const reporterSession = await createRunReporter(configPath);
499
+ const reporter = reporterSession.reporter;
500
+ const snapshotEnabled = runFlags.snapshot !== false;
501
+ reporter.onRunStart?.({
502
+ runtimeName: reporterSession.runtimeName,
503
+ clean: runFlags.clean,
504
+ verbose: runFlags.verbose,
505
+ snapshotEnabled,
506
+ updateSnapshots: runFlags.updateSnapshots,
507
+ });
508
+ const silentReporter = {};
509
+ const allResults = [];
510
+ const modeLabels = modes.map((modeName) => modeName ?? "default");
511
+ const showPerModeTimes = Boolean(runFlags.verbose);
512
+ const liveMatrix = reporterSession.reporterKind == "default" && canRewriteStdout();
513
+ const modeState = modes.map(() => ({
514
+ failed: false,
515
+ passed: false,
516
+ }));
517
+ const fileState = files.map(() => ({
518
+ failed: false,
519
+ passed: false,
520
+ }));
521
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
522
+ const file = files[fileIndex];
523
+ const fileName = path.basename(file);
524
+ const fileResults = [];
525
+ const modeTimes = modes.map(() => "...");
526
+ if (liveMatrix) {
527
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
528
+ }
529
+ for (let i = 0; i < modes.length; i++) {
530
+ const modeName = modes[i];
531
+ try {
532
+ await build(configPath, [file], modeName, buildFeatureToggles);
533
+ const artifactKey = fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
534
+ const result = await run(runFlags, configPath, [file], false, {
535
+ reporter: silentReporter,
536
+ reporterKind: "default",
537
+ emitRunStart: false,
538
+ emitRunComplete: false,
539
+ logFileName: `test.${artifactKey}.log.json`,
540
+ coverageFileName: `coverage.${artifactKey}.log.json`,
541
+ modeName,
542
+ });
543
+ modeTimes[i] = formatMatrixModeTime(result.stats.time);
544
+ if (liveMatrix) {
545
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
546
+ }
547
+ if (result.failed) {
548
+ modeState[i].failed = true;
549
+ }
550
+ else if (result.stats.passedFiles > 0) {
551
+ modeState[i].passed = true;
552
+ }
553
+ fileResults.push(result);
554
+ allResults.push(result);
555
+ }
556
+ catch (error) {
557
+ clearLiveLine();
558
+ throw error;
559
+ }
560
+ }
561
+ renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
562
+ const verdict = resolveMatrixVerdict(fileResults);
563
+ if (verdict == "fail") {
564
+ fileState[fileIndex].failed = true;
565
+ }
566
+ else if (verdict == "ok") {
567
+ fileState[fileIndex].passed = true;
568
+ }
569
+ }
570
+ const summary = aggregateRunResults(allResults);
571
+ summary.stats = applyMatrixFileSummaryToStats(summary.stats, fileState, fileSummaryTotal);
572
+ reporter.onRunComplete?.({
573
+ clean: runFlags.clean,
574
+ snapshotEnabled,
575
+ showCoverage: runFlags.showCoverage,
576
+ snapshotSummary: summary.snapshotSummary,
577
+ coverageSummary: summary.coverageSummary,
578
+ stats: summary.stats,
579
+ reports: summary.reports,
580
+ modeSummary: buildModeSummary(modeState, modeSummaryTotal),
581
+ });
582
+ return allResults.some((result) => result.failed);
583
+ }
584
+ function renderMatrixFileResult(file, modes, results, modeTimes, liveMatrix, showPerModeTimes) {
585
+ const verdict = resolveMatrixVerdict(results);
586
+ const badge = verdict == "fail"
587
+ ? chalk.bgRed.white(" FAIL ")
588
+ : verdict == "ok"
589
+ ? chalk.bgGreenBright.black(" PASS ")
590
+ : chalk.bgBlackBright.white(" SKIP ");
591
+ const avg = formatMatrixAverageTime(results);
592
+ const timingText = showPerModeTimes ? modeTimes.join(",") : avg;
593
+ const suffix = showPerModeTimes ? ` ${chalk.dim(`(${modes.join(",")})`)}` : "";
594
+ const line = `${badge} ${file} ${chalk.dim(timingText)}${suffix}`;
595
+ if (liveMatrix)
596
+ clearLiveLine();
597
+ process.stdout.write(line + "\n");
598
+ }
599
+ function resolveMatrixVerdict(results) {
600
+ if (results.some((result) => result.failed))
601
+ return "fail";
602
+ const hasPass = results.some((result) => result.stats.passedFiles > 0);
603
+ if (hasPass)
604
+ return "ok";
605
+ return "skip";
606
+ }
607
+ function canRewriteStdout() {
608
+ return Boolean(process.stdout.isTTY);
609
+ }
610
+ function clearLiveLine() {
611
+ if (!canRewriteStdout())
612
+ return;
613
+ process.stdout.write("\r\x1b[2K");
614
+ }
615
+ function renderMatrixLiveLine(file, modes, modeTimes, showPerModeTimes) {
616
+ if (!canRewriteStdout())
617
+ return;
618
+ const timingText = showPerModeTimes ? modeTimes.join(",") : "...";
619
+ const suffix = showPerModeTimes ? ` ${chalk.dim(`(${modes.join(",")})`)}` : "";
620
+ const line = `${chalk.bgBlackBright.white(" .... ")} ${file} ${chalk.dim(timingText)}${suffix}`;
621
+ process.stdout.write(`\r\x1b[2K${line}`);
622
+ }
623
+ function formatMatrixModeTime(ms) {
624
+ const safeMs = Number.isFinite(ms) ? Math.max(0, ms) : 0;
625
+ return `${safeMs.toFixed(1)}ms`;
626
+ }
627
+ function formatMatrixAverageTime(results) {
628
+ if (!results.length)
629
+ return "0.0ms";
630
+ let total = 0;
631
+ for (const result of results) {
632
+ total += Number.isFinite(result.stats.time) ? Math.max(0, result.stats.time) : 0;
633
+ }
634
+ return `${(total / results.length).toFixed(1)}ms`;
635
+ }
636
+ function buildModeSummary(modeState, totalModes) {
637
+ const total = Math.max(totalModes, modeState.length, 1);
638
+ let skipped = Math.max(0, total - modeState.length);
639
+ let failed = 0;
640
+ for (const mode of modeState) {
641
+ if (mode.failed) {
642
+ failed++;
643
+ }
644
+ else if (!mode.passed) {
645
+ skipped++;
646
+ }
647
+ }
648
+ return {
649
+ failed,
650
+ skipped,
651
+ total,
652
+ };
653
+ }
654
+ function buildSingleModeSummary(stats, snapshotSummary, totalModes) {
655
+ const total = Math.max(totalModes, 1);
656
+ const failed = stats.failedFiles > 0 || snapshotSummary.failed > 0 ? 1 : 0;
657
+ const skippedInExecuted = failed ? 0 : stats.passedFiles > 0 ? 0 : 1;
658
+ return {
659
+ failed,
660
+ skipped: Math.max(0, total - 1) + skippedInExecuted,
661
+ total,
662
+ };
663
+ }
664
+ function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
665
+ const total = Math.max(fileSummaryTotal, 0);
666
+ const executed = stats.failedFiles + stats.passedFiles + stats.skippedFiles;
667
+ const unexecuted = Math.max(0, total - executed);
668
+ return {
669
+ ...stats,
670
+ skippedFiles: stats.skippedFiles + unexecuted,
671
+ };
672
+ }
673
+ function applyMatrixFileSummaryToStats(stats, fileState, fileSummaryTotal) {
674
+ let failedFiles = 0;
675
+ let passedFiles = 0;
676
+ let skippedFiles = 0;
677
+ for (const file of fileState) {
678
+ if (file.failed)
679
+ failedFiles++;
680
+ else if (file.passed)
681
+ passedFiles++;
682
+ else
683
+ skippedFiles++;
684
+ }
685
+ const total = Math.max(fileSummaryTotal, fileState.length, 0);
686
+ const unexecuted = Math.max(0, total - fileState.length);
687
+ return {
688
+ ...stats,
689
+ failedFiles,
690
+ passedFiles,
691
+ skippedFiles: skippedFiles + unexecuted,
692
+ };
693
+ }
694
+ function resolveConfiguredModeTotal(configPath) {
695
+ const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
696
+ const config = loadConfig(resolvedConfigPath, false);
697
+ const configuredModes = Object.keys(config.modes).length;
698
+ return configuredModes || 1;
699
+ }
700
+ async function resolveConfiguredFileTotal(configPath) {
701
+ const files = await resolveSelectedFiles(configPath, []);
702
+ return files.length;
703
+ }
704
+ function resolveExecutionModes(configPath, selectedModes) {
705
+ if (selectedModes.length)
706
+ return selectedModes;
707
+ const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
708
+ const config = loadConfig(resolvedConfigPath, false);
709
+ const configuredModes = Object.keys(config.modes);
710
+ if (!configuredModes.length)
711
+ return [undefined];
712
+ return configuredModes;
713
+ }
249
714
  async function resolveSelectedFiles(configPath, selectors) {
250
715
  const resolvedConfigPath = configPath ?? path.join(process.cwd(), "./as-test.config.json");
251
716
  const config = loadConfig(resolvedConfigPath, true);
252
717
  const patterns = resolveInputPatterns(config.input, selectors);
253
718
  const matches = await glob(patterns);
254
719
  const specs = matches.filter((file) => file.endsWith(".spec.ts"));
255
- return [...new Set(specs)];
720
+ return [...new Set(specs)].sort((a, b) => a.localeCompare(b));
256
721
  }
257
722
  function resolveInputPatterns(configured, selectors) {
258
723
  const configuredInputs = Array.isArray(configured) ? configured : [configured];
259
724
  if (!selectors.length)
260
725
  return configuredInputs;
261
726
  const patterns = new Set();
262
- for (const selector of selectors) {
727
+ for (const selector of expandSelectors(selectors)) {
263
728
  if (!selector)
264
729
  continue;
265
730
  if (isBareSuiteSelector(selector)) {
@@ -273,6 +738,30 @@ function resolveInputPatterns(configured, selectors) {
273
738
  }
274
739
  return [...patterns];
275
740
  }
741
+ function expandSelectors(selectors) {
742
+ const expanded = [];
743
+ for (const selector of selectors) {
744
+ if (!selector)
745
+ continue;
746
+ if (!shouldSplitSelector(selector)) {
747
+ expanded.push(selector);
748
+ continue;
749
+ }
750
+ for (const token of selector.split(",")) {
751
+ const trimmed = token.trim();
752
+ if (!trimmed.length)
753
+ continue;
754
+ expanded.push(trimmed);
755
+ }
756
+ }
757
+ return expanded;
758
+ }
759
+ function shouldSplitSelector(selector) {
760
+ return (selector.includes(",") &&
761
+ !selector.includes("/") &&
762
+ !selector.includes("\\") &&
763
+ !/[*?[\]{}]/.test(selector));
764
+ }
276
765
  function isBareSuiteSelector(selector) {
277
766
  return (!selector.includes("/") &&
278
767
  !selector.includes("\\") &&