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

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/cli.js CHANGED
@@ -22,6 +22,9 @@ function buildAutoImportManifest(root) {
22
22
  const entries = [];
23
23
  for (const name of readdirSync(dir, { withFileTypes: true })) {
24
24
  if (!name.isDirectory()) continue;
25
+ if (name.name.startsWith(".") || name.name === "node_modules" || name.name === "dist" || name.name === "__screenshots__" || name.name === "coverage") {
26
+ continue;
27
+ }
25
28
  for (const platform of ["web", "common"]) {
26
29
  for (const ext of ["tsx", "ts"]) {
27
30
  const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
@@ -38,7 +41,7 @@ function buildAutoImportManifest(root) {
38
41
  function scanFixtures() {
39
42
  const dir = path.join(root, "fixtures");
40
43
  if (!existsSync(dir)) return [];
41
- return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
44
+ return readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
42
45
  }
43
46
  return {
44
47
  pageObjects: scanEntityDir("page-objects", (p) => `${p}.tsx`),
@@ -60,7 +63,7 @@ import { defineConfig } from '${vitestConfigUrl}';
60
63
  ${opts.semanticReporter ? `import { SpectraVitestReporter } from '${reporterUrl}';` : ""}
61
64
 
62
65
  export default defineConfig({
63
- root: ${JSON.stringify(normalizedPackageDir)},
66
+ root: ${JSON.stringify(normalizedTestRoot)},
64
67
  plugins: [react()],
65
68
  define: {
66
69
  __TESTSPECTRA_AUTO_IMPORT_MANIFEST__: ${JSON.stringify(JSON.stringify(manifest))},
@@ -92,7 +95,20 @@ export default defineConfig({
92
95
  ${opts.outputFile ? `['json', { outputFile: ${JSON.stringify(opts.outputFile)} }],` : ""}
93
96
  ]` : opts.outputFile ? `['default', ['json', { outputFile: ${JSON.stringify(opts.outputFile)} }]]` : "'default'"},
94
97
  dir: ${JSON.stringify(normalizedTestRoot)},
95
- include: ['specs/**/web.test.tsx', 'specs/**/*.test.tsx'],
98
+ include: [
99
+ 'specs/**/web.test.tsx',
100
+ 'specs/**/*.test.tsx',
101
+ 'specs/**/web.test.ts',
102
+ 'specs/**/*.test.ts',
103
+ ],
104
+ exclude: [
105
+ '**/node_modules/**',
106
+ '**/dist/**',
107
+ '**/__screenshots__/**',
108
+ '**/.testspectra/**',
109
+ '**/.git/**',
110
+ '**/coverage/**',
111
+ ],
96
112
  browser: {
97
113
  enabled: true,
98
114
  provider: 'playwright',
package/dist/index.js CHANGED
@@ -322,12 +322,26 @@ var DomDriverBridge = class {
322
322
  };
323
323
 
324
324
  // src/mount.ts
325
- import * as ReactDOMClient from "react-dom/client";
326
325
  var ROOT_ID = "spectra-root";
327
326
  var cachedRoot = null;
328
- function getCreateRoot() {
329
- const client = ReactDOMClient;
330
- return client.createRoot || client.default?.createRoot || client.default;
327
+ var dynamicCreateRoot = null;
328
+ async function getCreateRoot() {
329
+ if (typeof window !== "undefined" && window.__TESTSPECTRA_CREATE_ROOT__) {
330
+ return window.__TESTSPECTRA_CREATE_ROOT__;
331
+ }
332
+ if (dynamicCreateRoot) {
333
+ return dynamicCreateRoot;
334
+ }
335
+ try {
336
+ const mod = await import(
337
+ /* @vite-ignore */
338
+ "react-dom/client"
339
+ );
340
+ dynamicCreateRoot = mod.createRoot || mod.default?.createRoot || mod.default;
341
+ if (dynamicCreateRoot) return dynamicCreateRoot;
342
+ } catch {
343
+ }
344
+ throw new Error("Could not find react-dom/client createRoot. Ensure react-dom is installed in your project.");
331
345
  }
332
346
  function ensureContainer() {
333
347
  let container = document.getElementById(ROOT_ID);
@@ -341,7 +355,7 @@ function ensureContainer() {
341
355
  async function mount(element) {
342
356
  const container = ensureContainer();
343
357
  if (!cachedRoot) {
344
- const createRootFn = getCreateRoot();
358
+ const createRootFn = await getCreateRoot();
345
359
  cachedRoot = createRootFn(container);
346
360
  }
347
361
  const componentName = (typeof element.type === "string" ? element.type : element.type?.displayName || element.type?.name) || "component";
@@ -350,7 +364,7 @@ async function mount(element) {
350
364
  { type: "action", key: "mount", target: componentName, args: [] },
351
365
  async () => {
352
366
  cachedRoot.render(element);
353
- await new Promise((r) => queueMicrotask(r));
367
+ await new Promise((r) => setTimeout(r, 20));
354
368
  return globalThis.__TS_RUNTIME__.createElementProxy(`#${ROOT_ID}`);
355
369
  }
356
370
  );
@@ -380,6 +394,9 @@ function buildAutoImportManifest(root) {
380
394
  const entries = [];
381
395
  for (const name of readdirSync(dir, { withFileTypes: true })) {
382
396
  if (!name.isDirectory()) continue;
397
+ if (name.name.startsWith(".") || name.name === "node_modules" || name.name === "dist" || name.name === "__screenshots__" || name.name === "coverage") {
398
+ continue;
399
+ }
383
400
  for (const platform of ["web", "common"]) {
384
401
  for (const ext of ["tsx", "ts"]) {
385
402
  const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
@@ -396,7 +413,7 @@ function buildAutoImportManifest(root) {
396
413
  function scanFixtures() {
397
414
  const dir = path.join(root, "fixtures");
398
415
  if (!existsSync(dir)) return [];
399
- return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
416
+ return readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
400
417
  }
401
418
  return {
402
419
  pageObjects: scanEntityDir("page-objects", (p) => `${p}.tsx`),
@@ -418,7 +435,7 @@ import { defineConfig } from '${vitestConfigUrl}';
418
435
  ${opts.semanticReporter ? `import { SpectraVitestReporter } from '${reporterUrl}';` : ""}
419
436
 
420
437
  export default defineConfig({
421
- root: ${JSON.stringify(normalizedPackageDir)},
438
+ root: ${JSON.stringify(normalizedTestRoot)},
422
439
  plugins: [react()],
423
440
  define: {
424
441
  __TESTSPECTRA_AUTO_IMPORT_MANIFEST__: ${JSON.stringify(JSON.stringify(manifest))},
@@ -450,7 +467,20 @@ export default defineConfig({
450
467
  ${opts.outputFile ? `['json', { outputFile: ${JSON.stringify(opts.outputFile)} }],` : ""}
451
468
  ]` : opts.outputFile ? `['default', ['json', { outputFile: ${JSON.stringify(opts.outputFile)} }]]` : "'default'"},
452
469
  dir: ${JSON.stringify(normalizedTestRoot)},
453
- include: ['specs/**/web.test.tsx', 'specs/**/*.test.tsx'],
470
+ include: [
471
+ 'specs/**/web.test.tsx',
472
+ 'specs/**/*.test.tsx',
473
+ 'specs/**/web.test.ts',
474
+ 'specs/**/*.test.ts',
475
+ ],
476
+ exclude: [
477
+ '**/node_modules/**',
478
+ '**/dist/**',
479
+ '**/__screenshots__/**',
480
+ '**/.testspectra/**',
481
+ '**/.git/**',
482
+ '**/coverage/**',
483
+ ],
454
484
  browser: {
455
485
  enabled: true,
456
486
  provider: 'playwright',
@@ -621,29 +651,96 @@ import http from "node:http";
621
651
  import path3 from "node:path";
622
652
  import { fileURLToPath as fileURLToPath2 } from "node:url";
623
653
  import * as esbuild from "esbuild";
654
+ import { createRequire as createRequire2 } from "node:module";
624
655
  var bundleCache = /* @__PURE__ */ new Map();
625
656
  var inFlightBuilds = /* @__PURE__ */ new Map();
657
+ var cssInjectorPlugin = {
658
+ name: "spectra-css-injector",
659
+ setup(build2) {
660
+ build2.onLoad({ filter: /\.(css|scss|sass|less)$/ }, async (args) => {
661
+ try {
662
+ const raw = await fs.promises.readFile(args.path, "utf8");
663
+ const escaped = JSON.stringify(raw);
664
+ const sourcePath = JSON.stringify(args.path);
665
+ return {
666
+ contents: `
667
+ if (typeof document !== 'undefined') {
668
+ const style = document.createElement('style');
669
+ style.setAttribute('data-spectra-css', ${sourcePath});
670
+ style.textContent = ${escaped};
671
+ document.head.appendChild(style);
672
+ }
673
+ `,
674
+ loader: "js"
675
+ };
676
+ } catch (e) {
677
+ return {
678
+ contents: `/* failed to load css: ${String(e)} */`,
679
+ loader: "js"
680
+ };
681
+ }
682
+ });
683
+ }
684
+ };
685
+ var SHARED_REACT_EXTERNALS = [
686
+ "react",
687
+ "react/jsx-runtime",
688
+ "react/jsx-dev-runtime",
689
+ "react/compiler-runtime",
690
+ "react-dom",
691
+ "react-dom/client"
692
+ ];
626
693
  async function buildBundle(entrypoint, opts = {}) {
627
- const result = await esbuild.build({
628
- entryPoints: [entrypoint],
694
+ const mergedExternals = opts.skipSharedExternals ? opts.external ?? [] : Array.from(/* @__PURE__ */ new Set([...opts.external ?? [], ...SHARED_REACT_EXTERNALS]));
695
+ const buildOptions = {
629
696
  bundle: true,
630
697
  format: "esm",
631
698
  platform: "browser",
632
699
  target: "esnext",
633
- external: opts.external ?? [],
700
+ external: mergedExternals,
634
701
  minify: false,
635
702
  write: false,
636
- // Inline source maps so Chrome DevTools can map errors back to .tsx source lines.
703
+ plugins: [cssInjectorPlugin],
704
+ loader: {
705
+ ".png": "dataurl",
706
+ ".jpg": "dataurl",
707
+ ".jpeg": "dataurl",
708
+ ".gif": "dataurl",
709
+ ".webp": "dataurl",
710
+ ".svg": "text",
711
+ ".json": "json",
712
+ ".woff": "dataurl",
713
+ ".woff2": "dataurl",
714
+ ".ttf": "dataurl",
715
+ ".eot": "dataurl"
716
+ },
717
+ logLevel: "error",
718
+ logOverride: {
719
+ "ignored-bare-import": "silent"
720
+ },
637
721
  sourcemap: "inline",
638
- // React 17+ automatic JSX runtime — test files do not need `import React from 'react'`.
639
722
  jsx: "automatic",
640
723
  jsxImportSource: "react"
641
- });
724
+ };
725
+ if (opts.virtualContents) {
726
+ buildOptions.stdin = {
727
+ contents: opts.virtualContents,
728
+ resolveDir: opts.resolveDir ?? process.cwd(),
729
+ sourcefile: entrypoint,
730
+ loader: "ts"
731
+ };
732
+ } else {
733
+ buildOptions.entryPoints = [entrypoint];
734
+ }
735
+ const result = await esbuild.build(buildOptions);
642
736
  if (result.errors.length > 0) {
643
737
  const msgs = result.errors.map((e) => e.text).join("\n");
644
738
  throw new Error(`esbuild failed for ${entrypoint}:
645
739
  ${msgs}`);
646
740
  }
741
+ if (!result.outputFiles || result.outputFiles.length === 0) {
742
+ throw new Error(`esbuild produced no output files for ${entrypoint}`);
743
+ }
647
744
  return result.outputFiles[0].text;
648
745
  }
649
746
  async function getCachedBundle(cacheKey, entrypoint, opts) {
@@ -665,16 +762,134 @@ async function getCachedBundle(cacheKey, entrypoint, opts) {
665
762
  inFlightBuilds.set(cacheKey, buildPromise);
666
763
  return buildPromise;
667
764
  }
765
+ function buildCombinedReactBundleCode(testRoot) {
766
+ let req;
767
+ try {
768
+ req = createRequire2(path3.join(testRoot, "package.json"));
769
+ } catch {
770
+ req = createRequire2(import.meta.url);
771
+ }
772
+ const resolveSafe = (specifier) => {
773
+ try {
774
+ return req.resolve(specifier);
775
+ } catch {
776
+ try {
777
+ return createRequire2(import.meta.url).resolve(specifier);
778
+ } catch {
779
+ return null;
780
+ }
781
+ }
782
+ };
783
+ const loadSafe = (specifier) => {
784
+ try {
785
+ return req(specifier);
786
+ } catch {
787
+ try {
788
+ return createRequire2(import.meta.url)(specifier);
789
+ } catch {
790
+ return null;
791
+ }
792
+ }
793
+ };
794
+ const reactPath = resolveSafe("react");
795
+ const jsxPath = resolveSafe("react/jsx-runtime");
796
+ const compilerPath = resolveSafe("react/compiler-runtime");
797
+ const domPath = resolveSafe("react-dom");
798
+ const domClientPath = resolveSafe("react-dom/client");
799
+ const reactPkg = loadSafe("react") ?? {};
800
+ const jsxPkg = loadSafe("react/jsx-runtime") ?? {};
801
+ const compilerPkg = loadSafe("react/compiler-runtime") ?? {};
802
+ const domPkg = loadSafe("react-dom") ?? {};
803
+ const domClientPkg = loadSafe("react-dom/client") ?? {};
804
+ const allKeys = /* @__PURE__ */ new Set();
805
+ const exportLines = [];
806
+ const addExports = (varName, pkg) => {
807
+ const keys = Object.keys(pkg).filter((k) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k));
808
+ for (const k of keys) {
809
+ if (!allKeys.has(k)) {
810
+ allKeys.add(k);
811
+ exportLines.push(`export const ${k} = (${varName} as any).${k};`);
812
+ }
813
+ }
814
+ };
815
+ addExports("_react", reactPkg);
816
+ addExports("_jsx", jsxPkg);
817
+ addExports("_compiler", compilerPkg);
818
+ addExports("_dom", domPkg);
819
+ addExports("_domClient", domClientPkg);
820
+ const importParts = [];
821
+ const initParts = [];
822
+ if (reactPath) {
823
+ importParts.push(`import * as _rawReact from ${JSON.stringify(reactPath)};`);
824
+ initParts.push(`const _react: any = (_rawReact as any).default || _rawReact;`);
825
+ } else {
826
+ initParts.push(`const _react: any = {};`);
827
+ }
828
+ if (jsxPath) {
829
+ importParts.push(`import * as _rawJsx from ${JSON.stringify(jsxPath)};`);
830
+ initParts.push(`const _jsx: any = (_rawJsx as any).default || _rawJsx;`);
831
+ } else {
832
+ initParts.push(`const _jsx: any = {};`);
833
+ }
834
+ if (compilerPath) {
835
+ importParts.push(`import * as _rawCompiler from ${JSON.stringify(compilerPath)};`);
836
+ initParts.push(`const _compiler: any = (_rawCompiler as any).default || _rawCompiler;`);
837
+ } else {
838
+ initParts.push(`const _compiler: any = {};`);
839
+ }
840
+ if (domPath) {
841
+ importParts.push(`import * as _rawDom from ${JSON.stringify(domPath)};`);
842
+ initParts.push(`const _dom: any = (_rawDom as any).default || _rawDom;`);
843
+ } else {
844
+ initParts.push(`const _dom: any = {};`);
845
+ }
846
+ if (domClientPath) {
847
+ importParts.push(`import * as _rawDomClient from ${JSON.stringify(domClientPath)};`);
848
+ initParts.push(`const _domClient: any = (_rawDomClient as any).default || _rawDomClient;`);
849
+ } else {
850
+ initParts.push(`const _domClient: any = {};`);
851
+ }
852
+ return `
853
+ ${importParts.join("\n")}
854
+
855
+ ${initParts.join("\n")}
856
+
857
+ ${exportLines.join("\n")}
858
+
859
+ export default _react;
860
+ `;
861
+ }
668
862
  function buildHarnessHtml(manifest, stepDelayMs) {
669
863
  const manifestJson = JSON.stringify(manifest);
670
864
  return `<!DOCTYPE html>
671
865
  <html>
672
- <head><meta charset="utf-8"><title>Spectra Component Test</title></head>
866
+ <head>
867
+ <meta charset="utf-8">
868
+ <title>Spectra Component Test</title>
869
+ <script type="importmap">
870
+ {
871
+ "imports": {
872
+ "react": "/@modules/react.js",
873
+ "react/jsx-runtime": "/@modules/react.js",
874
+ "react/jsx-dev-runtime": "/@modules/react.js",
875
+ "react/compiler-runtime": "/@modules/react.js",
876
+ "react-dom": "/@modules/react.js",
877
+ "react-dom/client": "/@modules/react.js"
878
+ }
879
+ }
880
+ </script>
881
+ </head>
673
882
  <body>
674
- <script>
883
+ <script type="module">
675
884
  window.__TESTSPECTRA_AUTO_IMPORT_MANIFEST__ = ${manifestJson};
676
885
  window.__TESTSPECTRA_STEP_DELAY_MS__ = ${JSON.stringify(stepDelayMs)};
677
886
  window.__TESTSPECTRA_SEMANTIC_REPORTER__ = true;
887
+ try {
888
+ const rdc = await import('react-dom/client');
889
+ window.__TESTSPECTRA_CREATE_ROOT__ = rdc.createRoot || rdc.default?.createRoot || rdc.default;
890
+ } catch (e) {
891
+ console.warn('[TestSpectra] createRoot pre-load warning:', e);
892
+ }
678
893
  </script>
679
894
  <script type="module" src="/runner.js"></script>
680
895
  <script type="module" src="/setup.js"></script>
@@ -709,28 +924,20 @@ function makeHandler(testRoot, packageDir, harnessHtml, setupEntrypoint, runnerE
709
924
  respond(res, 200, "text/html; charset=utf-8", harnessHtml);
710
925
  return;
711
926
  }
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
- }
927
+ if (url.pathname === "/@modules/react.js" || url.pathname.startsWith("/@modules/react")) {
725
928
  try {
726
- const js = await getCachedBundle(resolved, resolved);
929
+ const js = await getCachedBundle("module_react_combined", path3.join(testRoot, "__virtual_react_combined.js"), {
930
+ virtualContents: buildCombinedReactBundleCode(testRoot),
931
+ resolveDir: testRoot,
932
+ skipSharedExternals: true
933
+ });
727
934
  respond(res, 200, "application/javascript; charset=utf-8", js);
728
935
  } catch (err) {
729
936
  respond(
730
937
  res,
731
- 200,
938
+ 500,
732
939
  "application/javascript",
733
- `throw new Error(${JSON.stringify(`Module bundle error for ${fsPath}: ${String(err)}`)});`
940
+ `throw new Error(${JSON.stringify(`Failed to bundle shared React runtime: ${String(err)}`)});`
734
941
  );
735
942
  }
736
943
  return;
@@ -761,9 +968,8 @@ function makeHandler(testRoot, packageDir, harnessHtml, setupEntrypoint, runnerE
761
968
  }
762
969
  const testFilePath = decodeURIComponent(encodedFile);
763
970
  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");
971
+ if (!fs.existsSync(resolved)) {
972
+ respond(res, 404, "text/plain", `File not found: ${testFilePath}`);
767
973
  return;
768
974
  }
769
975
  if (resolved.endsWith(".json")) {
package/dist/runner.js CHANGED
@@ -1 +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}))}};
1
+ var T=[],c=[],f=[];globalThis.it=function(n,o){T.push({title:n,fn:o})};globalThis.beforeEach=function(n){c.push(n)};globalThis.afterEach=function(n){f.push(n)};function R(){let n=globalThis.Spectra;if(typeof n?.unmount=="function")n.unmount();else{let o=document.getElementById("spectra-root");o&&(o.innerHTML="")}}function e(n,o){console.log(`[${n}] ${o}`)}globalThis.__SPECTRA_RUNNER__={async run(n,o){e("TESTSPECTRA_SUITE_START",JSON.stringify({file:n}));let u=globalThis.__bootstrapAutoImports;if(typeof u=="function")try{await u(o)}catch(t){return e("TESTSPECTRA_SUITE_ERROR",JSON.stringify({file:n,error:`Failed to bootstrap auto imports: ${String(t)}`})),{passed:0,failed:1}}let l=encodeURIComponent(n);try{await import(`/test?file=${l}`)}catch(t){return e("TESTSPECTRA_SUITE_ERROR",JSON.stringify({file:n,error:String(t)})),{passed:0,failed:1}}let i=0,a=0;for(let{title:t,fn:d}of T){e("TESTSPECTRA_TEST_START",t),globalThis.__CURRENT_TEST_TITLE__=t;let S={task:{name:t}},E=performance.now();try{for(let s of c)await s(S);await d();for(let s of f)await s(S);let r=Math.round(performance.now()-E);e("TESTSPECTRA_TEST_PASS",`${t} (${r}ms)`),i++}catch(r){let s=Math.round(performance.now()-E),g=r instanceof Error?r.message:String(r);e("TESTSPECTRA_TEST_ERROR",g),e("TESTSPECTRA_TEST_FAIL",`${t} (${s}ms)`),a++}finally{R()}}return T.length=0,c.length=0,f.length=0,e("TESTSPECTRA_SUITE_END",JSON.stringify({file:n,passed:i,failed:a})),{passed:i,failed:a}}};