@testspectra/react 1.1.8-rc.22 → 1.1.8-rc.23

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
@@ -1,37 +1,149 @@
1
1
  // src/cli.ts
2
+ import { existsSync as existsSync2 } from "node:fs";
3
+ import path2 from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import spawn from "cross-spawn";
6
+
7
+ // src/config_generator.ts
8
+ import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { createRequire } from "node:module";
10
+ import os from "node:os";
3
11
  import path from "node:path";
4
- import { fileURLToPath } from "node:url";
12
+ import { pathToFileURL } from "node:url";
13
+ function buildAutoImportManifest(root) {
14
+ function scanEntityDir(subdir, filePattern) {
15
+ const dir = path.join(root, subdir);
16
+ if (!existsSync(dir)) return [];
17
+ const entries = [];
18
+ for (const name of readdirSync(dir, { withFileTypes: true })) {
19
+ if (!name.isDirectory()) continue;
20
+ for (const platform of ["web", "common"]) {
21
+ for (const ext of ["tsx", "ts"]) {
22
+ const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
23
+ if (existsSync(candidate)) {
24
+ entries.push({ name: name.name, path: candidate.replace(/\\/g, "/") });
25
+ break;
26
+ }
27
+ }
28
+ if (entries.at(-1)?.name === name.name) break;
29
+ }
30
+ }
31
+ return entries;
32
+ }
33
+ function scanFixtures() {
34
+ const dir = path.join(root, "fixtures");
35
+ if (!existsSync(dir)) return [];
36
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
37
+ }
38
+ return {
39
+ pageObjects: scanEntityDir("page-objects", (p) => `${p}.tsx`),
40
+ steps: scanEntityDir("support/steps", (p) => `${p}.step.tsx`),
41
+ fixtures: scanFixtures()
42
+ };
43
+ }
44
+ function generateVitestConfig(opts) {
45
+ const req = createRequire(path.join(opts.packageDir, "package.json"));
46
+ const reactPluginUrl = pathToFileURL(req.resolve("@vitejs/plugin-react")).href;
47
+ const vitestConfigUrl = pathToFileURL(req.resolve("vitest/config")).href;
48
+ const manifest = buildAutoImportManifest(opts.testRoot);
49
+ const setupFile = path.join(opts.packageDir, "dist/setup.js").replace(/\\/g, "/");
50
+ const normalizedPackageDir = opts.packageDir.replace(/\\/g, "/");
51
+ const normalizedTestRoot = opts.testRoot.replace(/\\/g, "/");
52
+ const configContent = `import react from '${reactPluginUrl}';
53
+ import { defineConfig } from '${vitestConfigUrl}';
54
+
55
+ export default defineConfig({
56
+ root: ${JSON.stringify(normalizedPackageDir)},
57
+ plugins: [react()],
58
+ define: {
59
+ __TESTSPECTRA_AUTO_IMPORT_MANIFEST__: ${JSON.stringify(JSON.stringify(manifest))},
60
+ __TESTSPECTRA_STEP_DELAY_MS__: ${JSON.stringify(opts.stepDelayMs)},
61
+ __TESTSPECTRA_SEMANTIC_REPORTER__: ${JSON.stringify(opts.semanticReporter)},
62
+ },
63
+ optimizeDeps: {
64
+ include: [
65
+ 'react',
66
+ 'react-dom',
67
+ 'react-dom/client',
68
+ '@testing-library/react',
69
+ '@testing-library/user-event',
70
+ ],
71
+ },
72
+ server: {
73
+ fs: {
74
+ allow: [
75
+ ${JSON.stringify(normalizedPackageDir)},
76
+ ${JSON.stringify(normalizedTestRoot)},
77
+ ],
78
+ },
79
+ },
80
+ test: {
81
+ globals: true,
82
+ setupFiles: [${JSON.stringify(setupFile)}],
83
+ reporters: ${opts.semanticReporter ? "[]" : "'default'"},
84
+ dir: ${JSON.stringify(normalizedTestRoot)},
85
+ include: ['specs/**/web.test.tsx', 'specs/**/*.test.tsx'],
86
+ browser: {
87
+ enabled: true,
88
+ provider: 'playwright',
89
+ name: 'chromium',
90
+ headless: ${opts.headless},
91
+ fileParallelism: ${opts.fileParallelism},
92
+ ${opts.browserExecutable ? `providerOptions: { launch: { executablePath: ${JSON.stringify(opts.browserExecutable)} } },` : ""}
93
+ },
94
+ },
95
+ });
96
+ `;
97
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), "testspectra-react-"));
98
+ const configPath = path.join(tmpDir, "vitest.config.mjs");
99
+ writeFileSync(configPath, configContent, "utf-8");
100
+ return {
101
+ configPath,
102
+ cleanup: () => {
103
+ try {
104
+ rmSync(tmpDir, { recursive: true, force: true });
105
+ } catch {
106
+ }
107
+ }
108
+ };
109
+ }
110
+
111
+ // src/cli.ts
5
112
  async function runComponentTests(options = {}) {
6
113
  const rawCwd = options.cwd ?? process.cwd();
7
- const cwd = path.resolve(rawCwd);
8
- const configPath = fileURLToPath(new URL("../vitest.config.ts", import.meta.url));
114
+ const cwd = path2.resolve(rawCwd);
115
+ const packageDir = fileURLToPath(new URL("..", import.meta.url));
9
116
  const ext = process.platform === "win32" ? ".cmd" : "";
10
- const vitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
117
+ const localVitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
118
+ const consumerVitestBin = path2.join(cwd, `node_modules/.bin/vitest${ext}`);
119
+ const vitestBin = existsSync2(localVitestBin) ? localVitestBin : consumerVitestBin;
120
+ const semanticReporter = Boolean(options.onLine);
121
+ const { configPath, cleanup } = generateVitestConfig({
122
+ packageDir,
123
+ testRoot: cwd,
124
+ headless: options.headless !== false,
125
+ stepDelayMs: options.stepDelayMs ?? 0,
126
+ semanticReporter,
127
+ fileParallelism: options.concurrency !== 1,
128
+ browserExecutable: options.browserExecutablePath
129
+ });
11
130
  const args = ["run", "--config", configPath];
12
131
  if (options.outputFile) {
13
132
  args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
14
133
  }
15
134
  if (options.spec) {
16
135
  let specPattern = options.spec.replace(/\\/g, "/");
17
- if (path.isAbsolute(options.spec)) {
18
- specPattern = path.relative(cwd, options.spec).replace(/\\/g, "/");
136
+ if (path2.isAbsolute(options.spec)) {
137
+ specPattern = path2.relative(cwd, options.spec).replace(/\\/g, "/");
19
138
  }
20
139
  args.push(specPattern);
21
140
  }
22
- const semanticReporter = Boolean(options.onLine);
23
141
  return new Promise((resolve) => {
24
142
  const child = spawn(vitestBin, args, {
25
143
  stdio: semanticReporter ? ["inherit", "pipe", "pipe"] : "inherit",
26
144
  cwd,
27
145
  env: {
28
- ...process.env,
29
- TESTSPECTRA_TEST_ROOT: cwd.replace(/\\/g, "/"),
30
- TESTSPECTRA_HEADLESS: options.headless === false ? "false" : "true",
31
- TESTSPECTRA_STEP_DELAY: String(options.stepDelayMs ?? 0),
32
- TESTSPECTRA_FILE_PARALLELISM: options.concurrency === 1 ? "false" : "true",
33
- ...options.browserExecutablePath ? { TESTSPECTRA_BROWSER_EXECUTABLE: options.browserExecutablePath } : {},
34
- TESTSPECTRA_SEMANTIC_REPORTER: semanticReporter ? "1" : "0"
146
+ ...process.env
35
147
  }
36
148
  });
37
149
  if (semanticReporter) {
@@ -43,8 +155,12 @@ async function runComponentTests(options = {}) {
43
155
  child.stdout?.on("data", handleData);
44
156
  child.stderr?.on("data", handleData);
45
157
  }
46
- child.on("exit", (code) => resolve(code === 0));
158
+ child.on("exit", (code) => {
159
+ cleanup();
160
+ resolve(code === 0);
161
+ });
47
162
  child.on("error", (err) => {
163
+ cleanup();
48
164
  console.error(err);
49
165
  resolve(false);
50
166
  });
package/dist/index.js CHANGED
@@ -317,9 +317,13 @@ var DomDriverBridge = class {
317
317
  };
318
318
 
319
319
  // src/mount.ts
320
- import { createRoot } from "react-dom/client";
320
+ import * as ReactDOMClient from "react-dom/client";
321
321
  var ROOT_ID = "spectra-root";
322
322
  var cachedRoot = null;
323
+ function getCreateRoot() {
324
+ const client = ReactDOMClient;
325
+ return client.createRoot || client.default?.createRoot || client.default;
326
+ }
323
327
  function ensureContainer() {
324
328
  let container = document.getElementById(ROOT_ID);
325
329
  if (!container) {
@@ -332,7 +336,8 @@ function ensureContainer() {
332
336
  async function mount(element) {
333
337
  const container = ensureContainer();
334
338
  if (!cachedRoot) {
335
- cachedRoot = createRoot(container);
339
+ const createRootFn = getCreateRoot();
340
+ cachedRoot = createRootFn(container);
336
341
  }
337
342
  const componentName = (typeof element.type === "string" ? element.type : element.type?.displayName || element.type?.name) || "component";
338
343
  return globalThis.__TS_RUNTIME__.trackStep(
@@ -347,39 +352,151 @@ async function mount(element) {
347
352
  }
348
353
 
349
354
  // src/cli.ts
355
+ import { existsSync as existsSync2 } from "node:fs";
356
+ import path2 from "node:path";
357
+ import { fileURLToPath } from "node:url";
350
358
  import spawn from "cross-spawn";
359
+
360
+ // src/config_generator.ts
361
+ import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
362
+ import { createRequire } from "node:module";
363
+ import os from "node:os";
351
364
  import path from "node:path";
352
- import { fileURLToPath } from "node:url";
365
+ import { pathToFileURL } from "node:url";
366
+ function buildAutoImportManifest(root) {
367
+ function scanEntityDir(subdir, filePattern) {
368
+ const dir = path.join(root, subdir);
369
+ if (!existsSync(dir)) return [];
370
+ const entries = [];
371
+ for (const name of readdirSync(dir, { withFileTypes: true })) {
372
+ if (!name.isDirectory()) continue;
373
+ for (const platform of ["web", "common"]) {
374
+ for (const ext of ["tsx", "ts"]) {
375
+ const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
376
+ if (existsSync(candidate)) {
377
+ entries.push({ name: name.name, path: candidate.replace(/\\/g, "/") });
378
+ break;
379
+ }
380
+ }
381
+ if (entries.at(-1)?.name === name.name) break;
382
+ }
383
+ }
384
+ return entries;
385
+ }
386
+ function scanFixtures() {
387
+ const dir = path.join(root, "fixtures");
388
+ if (!existsSync(dir)) return [];
389
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
390
+ }
391
+ return {
392
+ pageObjects: scanEntityDir("page-objects", (p) => `${p}.tsx`),
393
+ steps: scanEntityDir("support/steps", (p) => `${p}.step.tsx`),
394
+ fixtures: scanFixtures()
395
+ };
396
+ }
397
+ 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;
401
+ const manifest = buildAutoImportManifest(opts.testRoot);
402
+ const setupFile = path.join(opts.packageDir, "dist/setup.js").replace(/\\/g, "/");
403
+ const normalizedPackageDir = opts.packageDir.replace(/\\/g, "/");
404
+ const normalizedTestRoot = opts.testRoot.replace(/\\/g, "/");
405
+ const configContent = `import react from '${reactPluginUrl}';
406
+ import { defineConfig } from '${vitestConfigUrl}';
407
+
408
+ export default defineConfig({
409
+ root: ${JSON.stringify(normalizedPackageDir)},
410
+ plugins: [react()],
411
+ define: {
412
+ __TESTSPECTRA_AUTO_IMPORT_MANIFEST__: ${JSON.stringify(JSON.stringify(manifest))},
413
+ __TESTSPECTRA_STEP_DELAY_MS__: ${JSON.stringify(opts.stepDelayMs)},
414
+ __TESTSPECTRA_SEMANTIC_REPORTER__: ${JSON.stringify(opts.semanticReporter)},
415
+ },
416
+ optimizeDeps: {
417
+ include: [
418
+ 'react',
419
+ 'react-dom',
420
+ 'react-dom/client',
421
+ '@testing-library/react',
422
+ '@testing-library/user-event',
423
+ ],
424
+ },
425
+ server: {
426
+ fs: {
427
+ allow: [
428
+ ${JSON.stringify(normalizedPackageDir)},
429
+ ${JSON.stringify(normalizedTestRoot)},
430
+ ],
431
+ },
432
+ },
433
+ test: {
434
+ globals: true,
435
+ setupFiles: [${JSON.stringify(setupFile)}],
436
+ reporters: ${opts.semanticReporter ? "[]" : "'default'"},
437
+ dir: ${JSON.stringify(normalizedTestRoot)},
438
+ include: ['specs/**/web.test.tsx', 'specs/**/*.test.tsx'],
439
+ browser: {
440
+ enabled: true,
441
+ provider: 'playwright',
442
+ name: 'chromium',
443
+ headless: ${opts.headless},
444
+ fileParallelism: ${opts.fileParallelism},
445
+ ${opts.browserExecutable ? `providerOptions: { launch: { executablePath: ${JSON.stringify(opts.browserExecutable)} } },` : ""}
446
+ },
447
+ },
448
+ });
449
+ `;
450
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), "testspectra-react-"));
451
+ const configPath = path.join(tmpDir, "vitest.config.mjs");
452
+ writeFileSync(configPath, configContent, "utf-8");
453
+ return {
454
+ configPath,
455
+ cleanup: () => {
456
+ try {
457
+ rmSync(tmpDir, { recursive: true, force: true });
458
+ } catch {
459
+ }
460
+ }
461
+ };
462
+ }
463
+
464
+ // src/cli.ts
353
465
  async function runComponentTests(options = {}) {
354
466
  const rawCwd = options.cwd ?? process.cwd();
355
- const cwd = path.resolve(rawCwd);
356
- const configPath = fileURLToPath(new URL("../vitest.config.ts", import.meta.url));
467
+ const cwd = path2.resolve(rawCwd);
468
+ const packageDir = fileURLToPath(new URL("..", import.meta.url));
357
469
  const ext = process.platform === "win32" ? ".cmd" : "";
358
- const vitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
470
+ const localVitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
471
+ const consumerVitestBin = path2.join(cwd, `node_modules/.bin/vitest${ext}`);
472
+ const vitestBin = existsSync2(localVitestBin) ? localVitestBin : consumerVitestBin;
473
+ const semanticReporter = Boolean(options.onLine);
474
+ const { configPath, cleanup } = generateVitestConfig({
475
+ packageDir,
476
+ testRoot: cwd,
477
+ headless: options.headless !== false,
478
+ stepDelayMs: options.stepDelayMs ?? 0,
479
+ semanticReporter,
480
+ fileParallelism: options.concurrency !== 1,
481
+ browserExecutable: options.browserExecutablePath
482
+ });
359
483
  const args = ["run", "--config", configPath];
360
484
  if (options.outputFile) {
361
485
  args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
362
486
  }
363
487
  if (options.spec) {
364
488
  let specPattern = options.spec.replace(/\\/g, "/");
365
- if (path.isAbsolute(options.spec)) {
366
- specPattern = path.relative(cwd, options.spec).replace(/\\/g, "/");
489
+ if (path2.isAbsolute(options.spec)) {
490
+ specPattern = path2.relative(cwd, options.spec).replace(/\\/g, "/");
367
491
  }
368
492
  args.push(specPattern);
369
493
  }
370
- const semanticReporter = Boolean(options.onLine);
371
494
  return new Promise((resolve) => {
372
495
  const child = spawn(vitestBin, args, {
373
496
  stdio: semanticReporter ? ["inherit", "pipe", "pipe"] : "inherit",
374
497
  cwd,
375
498
  env: {
376
- ...process.env,
377
- TESTSPECTRA_TEST_ROOT: cwd.replace(/\\/g, "/"),
378
- TESTSPECTRA_HEADLESS: options.headless === false ? "false" : "true",
379
- TESTSPECTRA_STEP_DELAY: String(options.stepDelayMs ?? 0),
380
- TESTSPECTRA_FILE_PARALLELISM: options.concurrency === 1 ? "false" : "true",
381
- ...options.browserExecutablePath ? { TESTSPECTRA_BROWSER_EXECUTABLE: options.browserExecutablePath } : {},
382
- TESTSPECTRA_SEMANTIC_REPORTER: semanticReporter ? "1" : "0"
499
+ ...process.env
383
500
  }
384
501
  });
385
502
  if (semanticReporter) {
@@ -391,8 +508,12 @@ async function runComponentTests(options = {}) {
391
508
  child.stdout?.on("data", handleData);
392
509
  child.stderr?.on("data", handleData);
393
510
  }
394
- child.on("exit", (code) => resolve(code === 0));
511
+ child.on("exit", (code) => {
512
+ cleanup();
513
+ resolve(code === 0);
514
+ });
395
515
  child.on("error", (err) => {
516
+ cleanup();
396
517
  console.error(err);
397
518
  resolve(false);
398
519
  });