@testspectra/react 1.1.8-rc.25 → 1.1.8-rc.29

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/dist/index.js CHANGED
@@ -1,16 +1,19 @@
1
1
  // src/dom_driver_bridge.ts
2
- var cachedUser = null;
3
- async function getUser() {
4
- if (!cachedUser) {
5
- const { userEvent } = await import("@testing-library/user-event");
6
- cachedUser = userEvent.setup();
7
- }
8
- return cachedUser;
2
+ import { userEvent } from "@testing-library/user-event";
3
+ function getUser() {
4
+ return userEvent.setup({ delay: null });
9
5
  }
10
6
  function resolveSelector(selector) {
11
7
  const trimmed = selector.trim();
12
8
  if (trimmed.startsWith("~")) {
13
- return `[aria-label=${JSON.stringify(trimmed.slice(1))}]`;
9
+ const raw = trimmed.slice(1);
10
+ const bracketIndex = raw.indexOf("[");
11
+ if (bracketIndex !== -1) {
12
+ const label = raw.slice(0, bracketIndex);
13
+ const rest = raw.slice(bracketIndex);
14
+ return `[aria-label=${JSON.stringify(label)}]${rest}`;
15
+ }
16
+ return `[aria-label=${JSON.stringify(raw)}]`;
14
17
  }
15
18
  return trimmed;
16
19
  }
@@ -34,6 +37,8 @@ var DomDriverBridge = class {
34
37
  /** Polls until `selector` resolves to an element, then returns it — shared by every
35
38
  * interaction method below (user-event needs a real element handle, not a selector string). */
36
39
  async waitForEl(selector, index) {
40
+ const immediate = resolveEl(selector, index);
41
+ if (immediate) return immediate;
37
42
  let found = null;
38
43
  const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
39
44
  found = resolveEl(selector, index);
@@ -80,20 +85,20 @@ var DomDriverBridge = class {
80
85
  // ─── Element Actions ─────────────────────────────────────────────────────
81
86
  async click(selector, index = null) {
82
87
  const el = await this.waitForEl(selector, index);
83
- await (await getUser()).click(el);
88
+ await getUser().click(el);
84
89
  }
85
90
  async doubleClick(selector, index = null) {
86
91
  const el = await this.waitForEl(selector, index);
87
- await (await getUser()).dblClick(el);
92
+ await getUser().dblClick(el);
88
93
  }
89
94
  async rightClick(selector, index = null) {
90
95
  const el = await this.waitForEl(selector, index);
91
- await (await getUser()).pointer({ keys: "[MouseRight]", target: el });
96
+ await getUser().pointer({ keys: "[MouseRight]", target: el });
92
97
  }
93
98
  async setValue(selector, value, index = null) {
94
99
  const el = await this.waitForEl(selector, index);
95
100
  if (!isFormEl(el)) throw new Error("Element is not a form field: " + JSON.stringify(selector));
96
- const u = await getUser();
101
+ const u = getUser();
97
102
  await u.clear(el);
98
103
  if (String(value).length > 0) {
99
104
  await u.type(el, String(value));
@@ -101,7 +106,7 @@ var DomDriverBridge = class {
101
106
  }
102
107
  async clearValue(selector, index = null) {
103
108
  const el = await this.waitForEl(selector, index);
104
- await (await getUser()).clear(el);
109
+ await getUser().clear(el);
105
110
  }
106
111
  async select(selector, option, index = null) {
107
112
  const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
@@ -121,7 +126,7 @@ var DomDriverBridge = class {
121
126
  }
122
127
  async hover(selector, index = null) {
123
128
  const el = await this.waitForEl(selector, index);
124
- await (await getUser()).hover(el);
129
+ await getUser().hover(el);
125
130
  }
126
131
  async focus(selector, index = null) {
127
132
  const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
@@ -345,7 +350,7 @@ async function mount(element) {
345
350
  { type: "action", key: "mount", target: componentName, args: [] },
346
351
  async () => {
347
352
  cachedRoot.render(element);
348
- await new Promise((r) => setTimeout(r, 0));
353
+ await new Promise((r) => queueMicrotask(r));
349
354
  return globalThis.__TS_RUNTIME__.createElementProxy(`#${ROOT_ID}`);
350
355
  }
351
356
  );
@@ -363,6 +368,11 @@ import { createRequire } from "node:module";
363
368
  import os from "node:os";
364
369
  import path from "node:path";
365
370
  import { pathToFileURL } from "node:url";
371
+ function resolveEsmUrl(specifier, packageDir) {
372
+ const cjsPath = createRequire(path.join(packageDir, "package.json")).resolve(specifier);
373
+ const esmPath = cjsPath.replace(/\.cjs$/, ".js");
374
+ return pathToFileURL(existsSync(esmPath) ? esmPath : cjsPath).href;
375
+ }
366
376
  function buildAutoImportManifest(root) {
367
377
  function scanEntityDir(subdir, filePattern) {
368
378
  const dir = path.join(root, subdir);
@@ -395,15 +405,17 @@ function buildAutoImportManifest(root) {
395
405
  };
396
406
  }
397
407
  function generateVitestConfig(opts) {
398
- const req = createRequire(path.join(opts.packageDir, "package.json"));
399
- const reactPluginUrl = pathToFileURL(req.resolve("@vitejs/plugin-react")).href;
400
- const vitestConfigUrl = pathToFileURL(req.resolve("vitest/config")).href;
408
+ const reactPluginUrl = resolveEsmUrl("@vitejs/plugin-react", opts.packageDir);
409
+ const vitestConfigUrl = resolveEsmUrl("vitest/config", opts.packageDir);
401
410
  const manifest = buildAutoImportManifest(opts.testRoot);
402
411
  const setupFile = path.join(opts.packageDir, "dist/setup.js").replace(/\\/g, "/");
412
+ const reporterFile = path.join(opts.packageDir, "dist/vitest_reporter.js").replace(/\\/g, "/");
413
+ const reporterUrl = pathToFileURL(reporterFile).href;
403
414
  const normalizedPackageDir = opts.packageDir.replace(/\\/g, "/");
404
415
  const normalizedTestRoot = opts.testRoot.replace(/\\/g, "/");
405
416
  const configContent = `import react from '${reactPluginUrl}';
406
417
  import { defineConfig } from '${vitestConfigUrl}';
418
+ ${opts.semanticReporter ? `import { SpectraVitestReporter } from '${reporterUrl}';` : ""}
407
419
 
408
420
  export default defineConfig({
409
421
  root: ${JSON.stringify(normalizedPackageDir)},
@@ -433,7 +445,10 @@ export default defineConfig({
433
445
  test: {
434
446
  globals: true,
435
447
  setupFiles: [${JSON.stringify(setupFile)}],
436
- reporters: ${opts.semanticReporter ? "[]" : "'default'"},
448
+ reporters: ${opts.semanticReporter ? `[
449
+ new SpectraVitestReporter({ testRoot: ${JSON.stringify(normalizedTestRoot)} }),
450
+ ${opts.outputFile ? `['json', { outputFile: ${JSON.stringify(opts.outputFile)} }],` : ""}
451
+ ]` : opts.outputFile ? `['default', ['json', { outputFile: ${JSON.stringify(opts.outputFile)} }]]` : "'default'"},
437
452
  dir: ${JSON.stringify(normalizedTestRoot)},
438
453
  include: ['specs/**/web.test.tsx', 'specs/**/*.test.tsx'],
439
454
  browser: {
@@ -463,6 +478,12 @@ export default defineConfig({
463
478
 
464
479
  // src/cli.ts
465
480
  async function runComponentTests(options = {}) {
481
+ const runner = options.runner ?? "vitest";
482
+ if (runner === "spectra") {
483
+ console.log("TestSpectra native component testing is orchestrated by the Rust Core Engine.");
484
+ console.log("Use `spectra test` to run tests with full parallel CDP orchestration.");
485
+ return true;
486
+ }
466
487
  const rawCwd = options.cwd ?? process.cwd();
467
488
  const cwd = path2.resolve(rawCwd);
468
489
  const packageDir = fileURLToPath(new URL("..", import.meta.url));
@@ -478,12 +499,10 @@ async function runComponentTests(options = {}) {
478
499
  stepDelayMs: options.stepDelayMs ?? 0,
479
500
  semanticReporter,
480
501
  fileParallelism: options.concurrency !== 1,
481
- browserExecutable: options.browserExecutablePath
502
+ browserExecutable: options.browserExecutablePath,
503
+ outputFile: options.outputFile
482
504
  });
483
505
  const args = ["run", "--config", configPath];
484
- if (options.outputFile) {
485
- args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
486
- }
487
506
  if (options.spec) {
488
507
  let specPattern = options.spec.replace(/\\/g, "/");
489
508
  if (path2.isAbsolute(options.spec)) {
@@ -520,8 +539,18 @@ async function runComponentTests(options = {}) {
520
539
  });
521
540
  }
522
541
  function getFlagValue(argv, ...names) {
523
- const index = argv.findIndex((a) => names.includes(a));
524
- return index !== -1 ? argv[index + 1] : void 0;
542
+ for (let i = 0; i < argv.length; i++) {
543
+ const arg = argv[i];
544
+ for (const name of names) {
545
+ if (arg === name) {
546
+ return argv[i + 1];
547
+ }
548
+ if (arg.startsWith(`${name}=`)) {
549
+ return arg.slice(name.length + 1);
550
+ }
551
+ }
552
+ }
553
+ return void 0;
525
554
  }
526
555
  async function runCli(argv = process.argv.slice(2)) {
527
556
  const [command] = argv;
@@ -532,7 +561,9 @@ async function runCli(argv = process.argv.slice(2)) {
532
561
  let headless = true;
533
562
  if (argv.includes("--headed") || argv.includes("--no-headless")) headless = false;
534
563
  else if (argv.includes("--headless")) headless = true;
535
- const rawDelay = getFlagValue(argv, "--step-delay", "--delay");
564
+ const rawRunner = getFlagValue(argv, "--runner", "-r");
565
+ const runner = rawRunner === "vitest" ? "vitest" : "spectra";
566
+ const rawDelay = getFlagValue(argv, "--step-delay", "--delay", "-d");
536
567
  let stepDelayMs;
537
568
  if (rawDelay !== void 0) {
538
569
  const parsed = parseInt(String(rawDelay), 10);
@@ -541,8 +572,11 @@ async function runCli(argv = process.argv.slice(2)) {
541
572
  let spec = getFlagValue(argv, "--spec", "-s");
542
573
  if (!spec) {
543
574
  const flagsWithValues = /* @__PURE__ */ new Set([
575
+ "--runner",
576
+ "-r",
544
577
  "--step-delay",
545
578
  "--delay",
579
+ "-d",
546
580
  "--spec",
547
581
  "-s",
548
582
  "--output",
@@ -569,12 +603,337 @@ async function runCli(argv = process.argv.slice(2)) {
569
603
  if (!isNaN(parsed) && parsed >= 1) concurrency = parsed;
570
604
  }
571
605
  const browserExecutablePath = getFlagValue(argv, "--browser-executable");
572
- const ok = await runComponentTests({ headless, stepDelayMs, spec, outputFile, concurrency, browserExecutablePath });
606
+ const ok = await runComponentTests({
607
+ runner,
608
+ headless,
609
+ stepDelayMs,
610
+ spec,
611
+ outputFile,
612
+ concurrency,
613
+ browserExecutablePath
614
+ });
573
615
  process.exit(ok ? 0 : 1);
574
616
  }
617
+
618
+ // src/cdp_runner/bundle_server.ts
619
+ import fs from "node:fs";
620
+ import http from "node:http";
621
+ import path3 from "node:path";
622
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
623
+ import * as esbuild from "esbuild";
624
+ var bundleCache = /* @__PURE__ */ new Map();
625
+ var inFlightBuilds = /* @__PURE__ */ new Map();
626
+ async function buildBundle(entrypoint, opts = {}) {
627
+ const result = await esbuild.build({
628
+ entryPoints: [entrypoint],
629
+ bundle: true,
630
+ format: "esm",
631
+ platform: "browser",
632
+ target: "esnext",
633
+ external: opts.external ?? [],
634
+ minify: false,
635
+ write: false,
636
+ // Inline source maps so Chrome DevTools can map errors back to .tsx source lines.
637
+ sourcemap: "inline",
638
+ // React 17+ automatic JSX runtime — test files do not need `import React from 'react'`.
639
+ jsx: "automatic",
640
+ jsxImportSource: "react"
641
+ });
642
+ if (result.errors.length > 0) {
643
+ const msgs = result.errors.map((e) => e.text).join("\n");
644
+ throw new Error(`esbuild failed for ${entrypoint}:
645
+ ${msgs}`);
646
+ }
647
+ return result.outputFiles[0].text;
648
+ }
649
+ async function getCachedBundle(cacheKey, entrypoint, opts) {
650
+ const cached = bundleCache.get(cacheKey);
651
+ if (cached) return cached;
652
+ const existingInFlight = inFlightBuilds.get(cacheKey);
653
+ if (existingInFlight) return existingInFlight;
654
+ const buildPromise = buildBundle(entrypoint, opts).then(
655
+ (js) => {
656
+ bundleCache.set(cacheKey, js);
657
+ inFlightBuilds.delete(cacheKey);
658
+ return js;
659
+ },
660
+ (err) => {
661
+ inFlightBuilds.delete(cacheKey);
662
+ throw err;
663
+ }
664
+ );
665
+ inFlightBuilds.set(cacheKey, buildPromise);
666
+ return buildPromise;
667
+ }
668
+ function buildHarnessHtml(manifest, stepDelayMs) {
669
+ const manifestJson = JSON.stringify(manifest);
670
+ return `<!DOCTYPE html>
671
+ <html>
672
+ <head><meta charset="utf-8"><title>Spectra Component Test</title></head>
673
+ <body>
674
+ <script>
675
+ window.__TESTSPECTRA_AUTO_IMPORT_MANIFEST__ = ${manifestJson};
676
+ window.__TESTSPECTRA_STEP_DELAY_MS__ = ${JSON.stringify(stepDelayMs)};
677
+ window.__TESTSPECTRA_SEMANTIC_REPORTER__ = true;
678
+ </script>
679
+ <script type="module" src="/runner.js"></script>
680
+ <script type="module" src="/setup.js"></script>
681
+ </body>
682
+ </html>`;
683
+ }
684
+ function respond(res, status, contentType, body) {
685
+ res.writeHead(status, { "Content-Type": contentType });
686
+ res.end(body);
687
+ }
688
+ function resolveReactPackageDir() {
689
+ let curr = path3.dirname(fileURLToPath2(import.meta.url));
690
+ while (curr !== path3.dirname(curr)) {
691
+ const pkgPath = path3.join(curr, "package.json");
692
+ if (fs.existsSync(pkgPath)) {
693
+ try {
694
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
695
+ if (pkg.name === "@testspectra/react") {
696
+ return curr;
697
+ }
698
+ } catch {
699
+ }
700
+ }
701
+ curr = path3.dirname(curr);
702
+ }
703
+ return path3.resolve(path3.dirname(fileURLToPath2(import.meta.url)), "../..");
704
+ }
705
+ function makeHandler(testRoot, packageDir, harnessHtml, setupEntrypoint, runnerEntrypoint) {
706
+ return async (req, res) => {
707
+ const url = new URL(req.url ?? "/", `http://127.0.0.1`);
708
+ if (url.pathname === "/") {
709
+ respond(res, 200, "text/html; charset=utf-8", harnessHtml);
710
+ return;
711
+ }
712
+ if (url.pathname.startsWith("/@fs/")) {
713
+ const rawFsPath = url.pathname.slice("/@fs/".length);
714
+ const fsPath = rawFsPath.startsWith("/") ? rawFsPath : `/${rawFsPath}`;
715
+ const resolved = path3.resolve(fsPath);
716
+ if (resolved.endsWith(".json")) {
717
+ try {
718
+ const raw = fs.readFileSync(resolved, "utf-8");
719
+ respond(res, 200, "application/json; charset=utf-8", raw);
720
+ } catch (err) {
721
+ respond(res, 500, "application/json", JSON.stringify({ error: String(err) }));
722
+ }
723
+ return;
724
+ }
725
+ try {
726
+ const js = await getCachedBundle(resolved, resolved);
727
+ respond(res, 200, "application/javascript; charset=utf-8", js);
728
+ } catch (err) {
729
+ respond(
730
+ res,
731
+ 200,
732
+ "application/javascript",
733
+ `throw new Error(${JSON.stringify(`Module bundle error for ${fsPath}: ${String(err)}`)});`
734
+ );
735
+ }
736
+ return;
737
+ }
738
+ if (url.pathname === "/setup.js") {
739
+ try {
740
+ const js = await getCachedBundle("setup", setupEntrypoint);
741
+ respond(res, 200, "application/javascript; charset=utf-8", js);
742
+ } catch (err) {
743
+ respond(res, 500, "application/javascript", `// Bundle error: ${String(err)}`);
744
+ }
745
+ return;
746
+ }
747
+ if (url.pathname === "/runner.js") {
748
+ try {
749
+ const js = await getCachedBundle("runner", runnerEntrypoint);
750
+ respond(res, 200, "application/javascript; charset=utf-8", js);
751
+ } catch (err) {
752
+ respond(res, 500, "application/javascript", `// Bundle error: ${String(err)}`);
753
+ }
754
+ return;
755
+ }
756
+ if (url.pathname === "/test") {
757
+ const encodedFile = url.searchParams.get("file");
758
+ if (!encodedFile) {
759
+ respond(res, 400, "text/plain", "Missing ?file= parameter");
760
+ return;
761
+ }
762
+ const testFilePath = decodeURIComponent(encodedFile);
763
+ const resolved = path3.resolve(testFilePath);
764
+ const rootResolved = path3.resolve(testRoot);
765
+ if (!resolved.startsWith(rootResolved + path3.sep) && resolved !== rootResolved) {
766
+ respond(res, 403, "text/plain", "Forbidden: path outside test root");
767
+ return;
768
+ }
769
+ if (resolved.endsWith(".json")) {
770
+ try {
771
+ const raw = fs.readFileSync(resolved, "utf-8");
772
+ respond(res, 200, "application/json; charset=utf-8", raw);
773
+ } catch (err) {
774
+ respond(res, 500, "application/json", JSON.stringify({ error: String(err) }));
775
+ }
776
+ return;
777
+ }
778
+ try {
779
+ const js = await getCachedBundle(resolved, resolved);
780
+ respond(res, 200, "application/javascript; charset=utf-8", js);
781
+ } catch (err) {
782
+ const msg = String(err);
783
+ respond(res, 200, "application/javascript", `throw new Error(${JSON.stringify(`Test bundle error: ${msg}`)});`);
784
+ }
785
+ return;
786
+ }
787
+ respond(res, 404, "text/plain", "Not Found");
788
+ };
789
+ }
790
+ async function startBundleServer(opts = {}) {
791
+ const testRoot = path3.resolve(opts.testRoot ?? process.cwd());
792
+ const packageDir = opts.packageDir ?? resolveReactPackageDir();
793
+ const manifest = opts.manifest ?? { pageObjects: [], steps: [], fixtures: [] };
794
+ const stepDelayMs = opts.stepDelayMs ?? 0;
795
+ const setupEntrypoint = path3.join(packageDir, "src", "setup.ts");
796
+ const runnerEntrypoint = path3.join(packageDir, "src", "cdp_runner", "in_page_runner.ts");
797
+ const distSetup = path3.join(packageDir, "dist", "setup.js");
798
+ if (fs.existsSync(distSetup)) {
799
+ try {
800
+ bundleCache.set("setup", fs.readFileSync(distSetup, "utf-8"));
801
+ } catch {
802
+ }
803
+ }
804
+ const distRunner = path3.join(packageDir, "dist", "runner.js");
805
+ if (fs.existsSync(distRunner)) {
806
+ try {
807
+ bundleCache.set("runner", fs.readFileSync(distRunner, "utf-8"));
808
+ } catch {
809
+ }
810
+ }
811
+ if (!bundleCache.has("setup")) {
812
+ getCachedBundle("setup", setupEntrypoint).catch(() => {
813
+ });
814
+ }
815
+ if (!bundleCache.has("runner")) {
816
+ getCachedBundle("runner", runnerEntrypoint).catch(() => {
817
+ });
818
+ }
819
+ const harnessHtml = buildHarnessHtml(manifest, stepDelayMs);
820
+ const handler = makeHandler(testRoot, packageDir, harnessHtml, setupEntrypoint, runnerEntrypoint);
821
+ const server = http.createServer(handler);
822
+ await new Promise((resolve, reject) => {
823
+ server.listen(0, "127.0.0.1", () => resolve());
824
+ server.once("error", reject);
825
+ });
826
+ const addr = server.address();
827
+ const baseUrl = `http://127.0.0.1:${addr.port}`;
828
+ return {
829
+ url: baseUrl,
830
+ stop: () => server.close()
831
+ };
832
+ }
833
+
834
+ // src/sanitizer.ts
835
+ var VITE_FS_URL_PATTERN = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d+\/@fs\/([A-Za-z]:\/)/g;
836
+ var VITE_FS_UNIX_URL_PATTERN = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d+\/@fs\//g;
837
+ var VITE_ROOT_URL_PATTERN = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d+\//g;
838
+ var URL_QUERY_PATTERN = /(\.[a-zA-Z0-9]+)\?[^:\s'")]*/g;
839
+ var INTERNAL_FRAME_PATTERNS = [
840
+ /node_modules[\\/]vitest[\\/]/i,
841
+ /node_modules[\\/]@vitest[\\/]/i,
842
+ /node_modules[\\/]vite[\\/]/i,
843
+ /node_modules[\\/]@vitejs[\\/]/i,
844
+ /node_modules[\\/]playwright[\\/]/i,
845
+ /node_modules[\\/]@playwright[\\/]/i,
846
+ /node:internal[\\/]/i,
847
+ /dist[\\/]setup\.js/i,
848
+ /dist[\\/]vitest_reporter\.js/i,
849
+ /tools[\\/]react[\\/]/i,
850
+ /[\\/]vitest\.config\./i,
851
+ /@vitest/i,
852
+ /__vitest__/i
853
+ ];
854
+ function sanitizeViteUrl(text) {
855
+ if (!text) return "";
856
+ let result = text.replace(VITE_FS_URL_PATTERN, "$1");
857
+ result = result.replace(VITE_FS_UNIX_URL_PATTERN, "/");
858
+ result = result.replace(VITE_ROOT_URL_PATTERN, "./");
859
+ result = result.replace(URL_QUERY_PATTERN, "$1");
860
+ return result;
861
+ }
862
+ function sanitizeErrorMessage(msg) {
863
+ if (!msg) return "";
864
+ let sanitized = sanitizeViteUrl(msg);
865
+ sanitized = sanitized.replace(/Failed to fetch dynamically imported module:\s*/gi, "Failed to load module: ");
866
+ sanitized = sanitized.replace(/\[vite\]\s*(?:Internal server error:\s*)?/gi, "[TestSpectra] ");
867
+ sanitized = sanitized.replace(/@vitest\/browser/gi, "TestSpectra Browser");
868
+ sanitized = sanitized.replace(/@vitest\/runner/gi, "TestSpectra Runner");
869
+ sanitized = sanitized.replace(/\bvitest\.config\.[a-z0-9]+/gi, "spectra.config.ts");
870
+ sanitized = sanitized.replace(/\bVitest\b/g, "TestSpectra");
871
+ sanitized = sanitized.replace(/\bvitest\b/g, "testspectra");
872
+ return sanitized;
873
+ }
874
+ function sanitizeStackTrace(stack) {
875
+ if (!stack) return "";
876
+ const lines = stack.split("\n");
877
+ const filtered = [];
878
+ for (let i = 0; i < lines.length; i++) {
879
+ const line = lines[i];
880
+ if (i === 0) {
881
+ filtered.push(sanitizeErrorMessage(line));
882
+ continue;
883
+ }
884
+ const isInternal = INTERNAL_FRAME_PATTERNS.some((pattern) => pattern.test(line));
885
+ if (isInternal) {
886
+ continue;
887
+ }
888
+ filtered.push(sanitizeErrorMessage(line));
889
+ }
890
+ return filtered.join("\n").trim();
891
+ }
892
+ function sanitizeError(err) {
893
+ if (!err) return "";
894
+ if (typeof err === "string") {
895
+ return sanitizeStackTrace(err);
896
+ }
897
+ if (typeof err === "object") {
898
+ const errorObj = err;
899
+ const stack = errorObj.stack;
900
+ const message = errorObj.message || String(err);
901
+ if (stack && typeof stack === "string") {
902
+ const sanitizedStack = sanitizeStackTrace(stack);
903
+ if (sanitizedStack) return sanitizedStack;
904
+ }
905
+ return sanitizeErrorMessage(message);
906
+ }
907
+ return sanitizeErrorMessage(String(err));
908
+ }
909
+ function isVitestNoise(line) {
910
+ const trimmed = line.trim();
911
+ if (!trimmed) return true;
912
+ if (/^RUN\s+v\d+/i.test(trimmed)) return true;
913
+ if (/^DEV\s+v\d+/i.test(trimmed)) return true;
914
+ if (/^Test Files\s+\d+/i.test(trimmed)) return true;
915
+ if (/^Tests\s+\d+/i.test(trimmed)) return true;
916
+ if (/^Start at\s+\d+/i.test(trimmed)) return true;
917
+ if (/^Duration\s+[\d.]+/i.test(trimmed)) return true;
918
+ if (/^⎯{3,}/.test(trimmed)) return true;
919
+ if (/^Re-optimizing dependencies/i.test(trimmed)) return true;
920
+ if (/^JSON report written to/i.test(trimmed)) return true;
921
+ if (/^\(?transform \d+ms/i.test(trimmed)) return true;
922
+ if (/^[·✓❯⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s+/.test(trimmed)) return true;
923
+ if (/vite-cjs-node-api-deprecated/i.test(trimmed)) return true;
924
+ if (/CJS build of Vite's Node API is deprecated/i.test(trimmed)) return true;
925
+ if (/^\[?vite\]?\s*(deprecation|warning)/i.test(trimmed)) return true;
926
+ return false;
927
+ }
575
928
  export {
576
929
  DomDriverBridge,
930
+ isVitestNoise,
577
931
  mount,
578
932
  runCli,
579
- runComponentTests
933
+ runComponentTests,
934
+ sanitizeError,
935
+ sanitizeErrorMessage,
936
+ sanitizeStackTrace,
937
+ sanitizeViteUrl,
938
+ startBundleServer
580
939
  };
package/dist/runner.js ADDED
@@ -0,0 +1 @@
1
+ var i=[],a=[],T=[];globalThis.it=function(n,t){i.push({title:n,fn:t})};globalThis.beforeEach=function(n){a.push(n)};globalThis.afterEach=function(n){T.push(n)};function _(){let n=globalThis.Spectra;if(typeof n?.unmount=="function")n.unmount();else{let t=document.getElementById("spectra-root");t&&(t.innerHTML="")}}function r(n,t){console.log(`[${n}] ${t}`)}globalThis.__SPECTRA_RUNNER__={async run(n,t){r("TESTSPECTRA_SUITE_START",JSON.stringify({file:n}));let c=globalThis.__bootstrapAutoImports;if(typeof c=="function")try{await c(t)}catch(o){r("TESTSPECTRA_SUITE_ERROR",JSON.stringify({file:n,error:`Failed to bootstrap auto imports: ${String(o)}`}));return}let l=encodeURIComponent(n);try{await import(`/test?file=${l}`)}catch(o){r("TESTSPECTRA_SUITE_ERROR",JSON.stringify({file:n,error:String(o)}));return}let f=0,S=0;for(let{title:o,fn:g}of i){r("TESTSPECTRA_TEST_START",o),globalThis.__CURRENT_TEST_TITLE__=o;let E={task:{name:o}},u=performance.now();try{for(let e of a)await e(E);await g();for(let e of T)await e(E);let s=Math.round(performance.now()-u);r("TESTSPECTRA_TEST_PASS",`${o} (${s}ms)`),f++}catch(s){let e=Math.round(performance.now()-u),R=s instanceof Error?s.message:String(s);r("TESTSPECTRA_TEST_ERROR",R),r("TESTSPECTRA_TEST_FAIL",`${o} (${e}ms)`),S++}finally{_()}}i.length=0,a.length=0,T.length=0,r("TESTSPECTRA_SUITE_END",JSON.stringify({file:n,passed:f,failed:S}))}};