@remix-run/test 0.1.0 → 0.2.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 (143) hide show
  1. package/README.md +140 -35
  2. package/dist/app/client/entry.d.ts +2 -0
  3. package/dist/app/client/entry.d.ts.map +1 -0
  4. package/dist/app/client/entry.js +324 -0
  5. package/dist/app/client/iframe.d.ts +2 -0
  6. package/dist/app/client/iframe.d.ts.map +1 -0
  7. package/dist/app/client/iframe.js +22 -0
  8. package/dist/app/server.d.ts +6 -0
  9. package/dist/app/server.d.ts.map +1 -0
  10. package/dist/app/server.js +303 -0
  11. package/dist/cli-entry.d.ts +3 -0
  12. package/dist/cli-entry.d.ts.map +1 -0
  13. package/dist/cli-entry.js +14 -0
  14. package/dist/cli.d.ts +7 -2
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +273 -139
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/lib/colors.d.ts +2 -0
  20. package/dist/lib/colors.d.ts.map +1 -0
  21. package/dist/lib/colors.js +2 -0
  22. package/dist/lib/config.d.ts +32 -1
  23. package/dist/lib/config.d.ts.map +1 -1
  24. package/dist/lib/config.js +125 -22
  25. package/dist/lib/context.d.ts +37 -13
  26. package/dist/lib/context.d.ts.map +1 -1
  27. package/dist/lib/context.js +19 -3
  28. package/dist/lib/coverage-loader.d.ts +16 -0
  29. package/dist/lib/coverage-loader.d.ts.map +1 -0
  30. package/dist/lib/coverage-loader.js +20 -0
  31. package/dist/lib/coverage.d.ts +28 -0
  32. package/dist/lib/coverage.d.ts.map +1 -0
  33. package/dist/lib/coverage.js +212 -0
  34. package/dist/lib/executor.d.ts +3 -26
  35. package/dist/lib/executor.d.ts.map +1 -1
  36. package/dist/lib/executor.js +11 -6
  37. package/dist/lib/fake-timers.d.ts +6 -0
  38. package/dist/lib/fake-timers.d.ts.map +1 -0
  39. package/dist/lib/fake-timers.js +45 -0
  40. package/dist/lib/import-module.d.ts +2 -0
  41. package/dist/lib/import-module.d.ts.map +1 -0
  42. package/dist/lib/import-module.js +29 -0
  43. package/dist/lib/normalize.d.ts +2 -0
  44. package/dist/lib/normalize.d.ts.map +1 -0
  45. package/dist/lib/{utils.js → normalize.js} +0 -9
  46. package/dist/lib/playwright.d.ts +1 -1
  47. package/dist/lib/playwright.d.ts.map +1 -1
  48. package/dist/lib/playwright.js +5 -8
  49. package/dist/lib/reporters/dot.d.ts +1 -2
  50. package/dist/lib/reporters/dot.d.ts.map +1 -1
  51. package/dist/lib/reporters/dot.js +2 -1
  52. package/dist/lib/reporters/files.d.ts +1 -2
  53. package/dist/lib/reporters/files.d.ts.map +1 -1
  54. package/dist/lib/reporters/files.js +2 -1
  55. package/dist/lib/reporters/index.d.ts +4 -5
  56. package/dist/lib/reporters/index.d.ts.map +1 -1
  57. package/dist/lib/reporters/index.js +3 -3
  58. package/dist/lib/reporters/results.d.ts +30 -0
  59. package/dist/lib/reporters/results.d.ts.map +1 -0
  60. package/dist/lib/reporters/results.js +1 -0
  61. package/dist/lib/reporters/spec.d.ts +1 -2
  62. package/dist/lib/reporters/spec.d.ts.map +1 -1
  63. package/dist/lib/reporters/spec.js +2 -1
  64. package/dist/lib/reporters/tap.d.ts +1 -2
  65. package/dist/lib/reporters/tap.d.ts.map +1 -1
  66. package/dist/lib/reporters/tap.js +1 -1
  67. package/dist/lib/runner-browser.d.ts +21 -0
  68. package/dist/lib/runner-browser.d.ts.map +1 -0
  69. package/dist/lib/runner-browser.js +117 -0
  70. package/dist/lib/runner.d.ts +7 -2
  71. package/dist/lib/runner.d.ts.map +1 -1
  72. package/dist/lib/runner.js +33 -4
  73. package/dist/lib/runtime.d.ts +2 -0
  74. package/dist/lib/runtime.d.ts.map +1 -0
  75. package/dist/lib/runtime.js +2 -0
  76. package/dist/lib/ts-transform.d.ts +4 -0
  77. package/dist/lib/ts-transform.d.ts.map +1 -0
  78. package/dist/lib/ts-transform.js +29 -0
  79. package/dist/lib/worker-e2e.js +5 -4
  80. package/dist/lib/worker.js +31 -3
  81. package/dist/test/coverage/fixture.d.ts +5 -0
  82. package/dist/test/coverage/fixture.d.ts.map +1 -0
  83. package/dist/test/coverage/fixture.js +32 -0
  84. package/dist/test/coverage/test-browser.d.ts +2 -0
  85. package/dist/test/coverage/test-browser.d.ts.map +1 -0
  86. package/dist/test/coverage/test-browser.js +24 -0
  87. package/dist/test/coverage/test-e2e.d.ts +2 -0
  88. package/dist/test/coverage/test-e2e.d.ts.map +1 -0
  89. package/dist/test/coverage/test-e2e.js +60 -0
  90. package/dist/test/coverage/test-unit.d.ts +2 -0
  91. package/dist/test/coverage/test-unit.d.ts.map +1 -0
  92. package/dist/test/coverage/test-unit.js +27 -0
  93. package/dist/test/framework.test.browser.d.ts +2 -0
  94. package/dist/test/framework.test.browser.d.ts.map +1 -0
  95. package/dist/test/framework.test.browser.js +107 -0
  96. package/dist/test/framework.test.e2e.d.ts.map +1 -0
  97. package/dist/test/framework.test.e2e.js +34 -0
  98. package/package.json +30 -9
  99. package/src/app/client/entry.ts +353 -0
  100. package/src/app/client/iframe.ts +18 -0
  101. package/src/app/server.ts +336 -0
  102. package/src/cli-entry.ts +15 -0
  103. package/src/cli.ts +322 -148
  104. package/src/index.ts +1 -0
  105. package/src/lib/colors.ts +3 -0
  106. package/src/lib/config.ts +169 -23
  107. package/src/lib/context.ts +59 -17
  108. package/src/lib/coverage-loader.ts +31 -0
  109. package/src/lib/coverage.ts +320 -0
  110. package/src/lib/executor.ts +18 -35
  111. package/src/lib/fake-timers.ts +64 -0
  112. package/src/lib/import-module.ts +29 -0
  113. package/src/lib/{utils.ts → normalize.ts} +0 -18
  114. package/src/lib/playwright.ts +5 -7
  115. package/src/lib/reporters/dot.ts +3 -2
  116. package/src/lib/reporters/files.ts +3 -2
  117. package/src/lib/reporters/index.ts +4 -5
  118. package/src/lib/reporters/results.ts +29 -0
  119. package/src/lib/reporters/spec.ts +3 -2
  120. package/src/lib/reporters/tap.ts +2 -2
  121. package/src/lib/runner-browser.ts +165 -0
  122. package/src/lib/runner.ts +62 -10
  123. package/src/lib/runtime.ts +2 -0
  124. package/src/lib/ts-transform.ts +36 -0
  125. package/src/lib/worker-e2e.ts +7 -5
  126. package/src/lib/worker.ts +24 -4
  127. package/src/test/coverage/fixture.ts +34 -0
  128. package/src/test/coverage/test-browser.ts +29 -0
  129. package/src/test/coverage/test-e2e.ts +70 -0
  130. package/src/test/coverage/test-unit.ts +32 -0
  131. package/tsconfig.json +3 -1
  132. package/dist/lib/e2e-server.d.ts +0 -11
  133. package/dist/lib/e2e-server.d.ts.map +0 -1
  134. package/dist/lib/e2e-server.js +0 -15
  135. package/dist/lib/framework.test.d.ts +0 -2
  136. package/dist/lib/framework.test.d.ts.map +0 -1
  137. package/dist/lib/framework.test.e2e.d.ts.map +0 -1
  138. package/dist/lib/framework.test.e2e.js +0 -29
  139. package/dist/lib/framework.test.js +0 -283
  140. package/dist/lib/utils.d.ts +0 -16
  141. package/dist/lib/utils.d.ts.map +0 -1
  142. package/src/lib/e2e-server.ts +0 -28
  143. /package/dist/{lib → test}/framework.test.e2e.d.ts +0 -0
package/dist/cli.js CHANGED
@@ -1,171 +1,305 @@
1
- #!/usr/bin/env node
2
1
  import * as fsp from 'node:fs/promises';
3
2
  import * as path from 'node:path';
4
- import { tsImport } from 'tsx/esm/api';
5
- import { runServerTests } from "./lib/runner.js";
3
+ import { getRemixTestHelpText, IS_RUNNING_FROM_SRC, loadConfig, } from "./lib/config.js";
4
+ import { generateCombinedCoverageReport } from "./lib/coverage.js";
5
+ import { loadPlaywrightConfig, resolveProjects } from "./lib/playwright.js";
6
6
  import { createReporter } from "./lib/reporters/index.js";
7
+ import { runBrowserTests } from "./lib/runner-browser.js";
8
+ import { runServerTests } from "./lib/runner.js";
7
9
  import { createWatcher } from "./lib/watcher.js";
8
- import { loadPlaywrightConfig, resolveProjects } from "./lib/playwright.js";
9
- import { loadConfig } from "./lib/config.js";
10
- const config = await loadConfig();
11
- let hasExited = false;
12
- let latestExitCode = 0;
13
- let watcher;
14
- let running = false;
15
- let queued = false;
16
- let rerunTimer;
17
- process.on('SIGINT', () => cleanupAndExit(latestExitCode));
18
- process.on('SIGTERM', () => cleanupAndExit(latestExitCode));
19
- try {
20
- await executeRun();
21
- if (config.watch) {
22
- console.log('Watching for changes. Press Ctrl+C to stop.');
10
+ import { importModule } from "./lib/import-module.js";
11
+ import { IS_BUN } from "./lib/runtime.js";
12
+ import { isMainThread } from 'node:worker_threads';
13
+ export { getRemixTestHelpText };
14
+ export async function runRemixTest(options = {}) {
15
+ let argv = options.argv ?? process.argv.slice(2);
16
+ let cwd = await resolveCwd(options.cwd ?? process.cwd());
17
+ let previousCwd = process.cwd();
18
+ if (!isMainThread) {
19
+ return await runRemixTestInCwd(argv, cwd);
23
20
  }
24
- }
25
- catch {
26
- cleanupAndExit(1);
27
- }
28
- async function executeRun() {
29
- if (hasExited)
30
- return;
31
- running = true;
32
- let globalTeardown;
33
21
  try {
34
- if (config.setup) {
35
- let mod = await tsImport(path.resolve(process.cwd(), config.setup), {
36
- parentURL: import.meta.url,
37
- });
38
- let globalSetup = mod.globalSetup;
39
- globalTeardown = mod.globalTeardown;
40
- await globalSetup?.();
41
- }
42
- let { files, serverFiles, e2eFiles } = await discoverTests(config);
43
- if (config.watch) {
44
- watcher ??= createWatcher((file) => queueRerun(file));
45
- watcher.update(files);
46
- }
47
- let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
48
- ? await loadPlaywrightConfig(config.playwrightConfig)
49
- : config.playwrightConfig;
50
- let reporter = createReporter(config.reporter);
51
- let startTime = performance.now();
52
- let counts = {
53
- passed: 0,
54
- failed: 0,
55
- skipped: 0,
56
- todo: 0,
57
- };
58
- // Run server tests
59
- if (serverFiles.length > 0) {
60
- reporter.onSectionStart('\nRunning server tests:');
61
- let serverResult = await runServerTests(serverFiles, reporter, config.concurrency, 'server');
62
- counts.failed += serverResult.failed;
63
- counts.passed += serverResult.passed;
64
- counts.skipped += serverResult.skipped;
65
- counts.todo += serverResult.todo;
66
- }
67
- // Run e2e tests for all browsers configured by the user
68
- if (e2eFiles.length > 0) {
69
- let projects = resolveProjects(playwrightConfig);
70
- if (config.project) {
71
- let projectNames = config.project.split(',').map((p) => p.trim());
72
- projects = projects.filter((p) => p.name && projectNames.includes(p.name));
73
- if (projects.length === 0) {
74
- throw new Error(`No playwright projects found with name(s) "${config.project}"`);
75
- }
22
+ process.chdir(cwd);
23
+ return await runRemixTestInCwd(argv, cwd);
24
+ }
25
+ finally {
26
+ process.chdir(previousCwd);
27
+ }
28
+ }
29
+ async function runRemixTestInCwd(argv, cwd) {
30
+ if (argv.includes('--help') || argv.includes('-h')) {
31
+ console.log(getRemixTestHelpText());
32
+ return 0;
33
+ }
34
+ let config = await loadConfig(argv, cwd);
35
+ let hasExited = false;
36
+ let latestExitCode = 0;
37
+ let watcher;
38
+ let running = false;
39
+ let queued = false;
40
+ let rerunTimer;
41
+ let browserServer;
42
+ let browserServerFilesKey;
43
+ let browserPort;
44
+ let resolveRun;
45
+ let runPromise = new Promise((resolve) => {
46
+ resolveRun = resolve;
47
+ });
48
+ let cleanupAndExit = (code) => {
49
+ if (hasExited)
50
+ return;
51
+ hasExited = true;
52
+ watcher?.close();
53
+ browserServer?.close();
54
+ clearTimeout(rerunTimer);
55
+ process.off('SIGINT', handleInterrupt);
56
+ process.off('SIGTERM', handleInterrupt);
57
+ resolveRun?.(code);
58
+ };
59
+ let handleInterrupt = () => cleanupAndExit(latestExitCode);
60
+ let closeBrowserServer = async () => {
61
+ if (!browserServer)
62
+ return;
63
+ let server = browserServer;
64
+ await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
65
+ browserServer = undefined;
66
+ browserServerFilesKey = undefined;
67
+ browserPort = undefined;
68
+ };
69
+ let queueRerun = (reason) => {
70
+ if (!config.watch || hasExited)
71
+ return;
72
+ clearTimeout(rerunTimer);
73
+ rerunTimer = setTimeout(() => {
74
+ rerunTimer = undefined;
75
+ if (running) {
76
+ queued = true;
77
+ }
78
+ else {
79
+ console.log(`\n↻ Change detected (${reason}), re-running tests...\n`);
80
+ void executeRun();
81
+ }
82
+ }, 100);
83
+ };
84
+ let executeRun = async () => {
85
+ if (hasExited)
86
+ return;
87
+ running = true;
88
+ let globalTeardown;
89
+ try {
90
+ if (config.setup) {
91
+ let mod = await importModule(path.resolve(cwd, config.setup), import.meta);
92
+ let globalSetup = mod.globalSetup;
93
+ globalTeardown = mod.globalTeardown;
94
+ await globalSetup?.();
95
+ }
96
+ let discoveredTests = await discoverTests(config, cwd);
97
+ if (discoveredTests == null) {
98
+ latestExitCode = 1;
99
+ cleanupAndExit(latestExitCode);
100
+ return;
101
+ }
102
+ let { files, serverFiles, browserFiles, e2eFiles } = discoveredTests;
103
+ if (config.watch) {
104
+ watcher ??= createWatcher((file) => queueRerun(file));
105
+ watcher.update(files);
106
+ }
107
+ let browserFilesKey = browserFiles.join('\0');
108
+ if (browserServer && browserFiles.length === 0) {
109
+ await closeBrowserServer();
110
+ }
111
+ else if (browserFiles.length > 0 &&
112
+ (!browserServer || browserServerFilesKey !== browserFilesKey)) {
113
+ await closeBrowserServer();
114
+ let { startServer } = IS_RUNNING_FROM_SRC
115
+ ? await importModule('./app/server.ts', import.meta)
116
+ : await import(`./app/server.js`);
117
+ let result = await startServer(browserFiles);
118
+ browserServer = result.server;
119
+ browserServerFilesKey = browserFilesKey;
120
+ browserPort = result.port;
121
+ }
122
+ let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
123
+ ? await loadPlaywrightConfig(config.playwrightConfig, cwd)
124
+ : config.playwrightConfig;
125
+ let reporter = createReporter(config.reporter);
126
+ let startTime = performance.now();
127
+ let counts = {
128
+ passed: 0,
129
+ failed: 0,
130
+ skipped: 0,
131
+ todo: 0,
132
+ };
133
+ let allCoverageMaps = [];
134
+ if (serverFiles.length > 0) {
135
+ reporter.onSectionStart('\nRunning server tests:');
136
+ let serverResult = await runServerTests(serverFiles, reporter, config.concurrency, 'server', {
137
+ coverage: config.coverage,
138
+ cwd,
139
+ });
140
+ counts.failed += serverResult.failed;
141
+ counts.passed += serverResult.passed;
142
+ counts.skipped += serverResult.skipped;
143
+ counts.todo += serverResult.todo;
144
+ allCoverageMaps.push(serverResult.coverageMap);
76
145
  }
77
- for (let project of projects) {
78
- reporter.onSectionStart(`\nRunning tests for project \`${project.name}\`:`);
79
- if (config.browser?.open) {
80
- if (project.playwrightUseOpts?.headless === true) {
81
- let label = project.name ? ` (project "${project.name}")` : '';
82
- console.warn(`Warning: browser.open is set but playwright headless is explicitly true${label} — ignoring browser.open`);
146
+ // Run browser/e2e tests for all browsers configured by the user
147
+ if (browserFiles.length > 0 || e2eFiles.length > 0) {
148
+ let projects = resolveProjects(playwrightConfig);
149
+ if (config.project) {
150
+ let projectNames = config.project.split(',').map((project) => project.trim());
151
+ projects = projects.filter((project) => project.name && projectNames.includes(project.name));
152
+ if (projects.length === 0) {
153
+ throw new Error(`No playwright projects found with name(s) "${config.project}"`);
83
154
  }
84
- else {
85
- project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false };
155
+ }
156
+ let lastBrowserResult = null;
157
+ for (let project of projects) {
158
+ reporter.onSectionStart(`\nRunning tests for project \`${project.name}\`:`);
159
+ if (config.browser?.open) {
160
+ if (project.playwrightUseOpts?.headless === true) {
161
+ let label = project.name ? ` (project "${project.name}")` : '';
162
+ console.warn(`Warning: browser.open is set but playwright headless is explicitly true${label} — ignoring browser.open`);
163
+ }
164
+ else {
165
+ project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false };
166
+ }
167
+ }
168
+ let [browserResult, e2eResult] = await Promise.all([
169
+ browserFiles.length > 0
170
+ ? runBrowserTests({
171
+ baseUrl: `http://localhost:${browserPort}`,
172
+ console: config.browser?.echo,
173
+ coverage: !!config.coverage,
174
+ open: config.browser?.open,
175
+ playwrightUseOpts: project.playwrightUseOpts,
176
+ projectName: project.name,
177
+ reporter,
178
+ testFiles: browserFiles,
179
+ })
180
+ : null,
181
+ e2eFiles.length > 0
182
+ ? runServerTests(e2eFiles, reporter, config.concurrency, 'e2e', {
183
+ open: config.browser?.open,
184
+ playwrightUseOpts: project.playwrightUseOpts,
185
+ projectName: project.name,
186
+ coverage: config.coverage,
187
+ cwd,
188
+ })
189
+ : null,
190
+ ]);
191
+ counts.passed += (browserResult?.results.passed ?? 0) + (e2eResult?.passed ?? 0);
192
+ counts.failed += (browserResult?.results.failed ?? 0) + (e2eResult?.failed ?? 0);
193
+ counts.skipped += (browserResult?.results.skipped ?? 0) + (e2eResult?.skipped ?? 0);
194
+ counts.todo += (browserResult?.results.todo ?? 0) + (e2eResult?.todo ?? 0);
195
+ allCoverageMaps.push(browserResult?.coverageMap);
196
+ allCoverageMaps.push(e2eResult?.coverageMap);
197
+ if (browserResult) {
198
+ lastBrowserResult = browserResult;
86
199
  }
87
200
  }
88
- let e2eResult = e2eFiles.length > 0
89
- ? await runServerTests(e2eFiles, reporter, config.concurrency, 'e2e', {
90
- open: config.browser?.open,
91
- playwrightUseOpts: project.playwrightUseOpts,
92
- projectName: project.name,
93
- })
94
- : null;
95
- counts.passed += e2eResult?.passed ?? 0;
96
- counts.failed += e2eResult?.failed ?? 0;
97
- counts.skipped += e2eResult?.skipped ?? 0;
98
- counts.todo += e2eResult?.todo ?? 0;
201
+ if (config.browser?.open && lastBrowserResult) {
202
+ console.log('\nBrowser is open. Press Ctrl+C to close.');
203
+ await Promise.race([
204
+ lastBrowserResult.disconnected,
205
+ new Promise((resolve) => {
206
+ process.once('SIGINT', resolve);
207
+ process.once('SIGTERM', resolve);
208
+ }),
209
+ ]);
210
+ await lastBrowserResult.close();
211
+ }
99
212
  }
213
+ reporter.onSummary(counts, performance.now() - startTime);
214
+ let thresholdsPassed = true;
215
+ if (config.coverage) {
216
+ thresholdsPassed = await generateCombinedCoverageReport(allCoverageMaps, cwd, config.coverage);
217
+ }
218
+ latestExitCode = counts.failed > 0 || !thresholdsPassed ? 1 : 0;
100
219
  }
101
- reporter.onSummary(counts, performance.now() - startTime);
102
- latestExitCode = counts.failed > 0 ? 1 : 0;
103
- }
104
- catch (error) {
105
- console.error('Error running tests:', error);
106
- latestExitCode = 1;
107
- }
108
- finally {
109
- await globalTeardown?.();
110
- running = false;
111
- if (queued) {
112
- queued = false;
113
- queueRerun('queued change');
220
+ catch (error) {
221
+ console.error('Error running tests:', error);
222
+ latestExitCode = 1;
223
+ }
224
+ finally {
225
+ await globalTeardown?.();
226
+ running = false;
227
+ if (queued) {
228
+ queued = false;
229
+ queueRerun('queued change');
230
+ }
231
+ else if (!config.watch) {
232
+ cleanupAndExit(latestExitCode);
233
+ }
114
234
  }
115
- else if (!config.watch) {
116
- cleanupAndExit(latestExitCode);
235
+ };
236
+ process.on('SIGINT', handleInterrupt);
237
+ process.on('SIGTERM', handleInterrupt);
238
+ try {
239
+ await executeRun();
240
+ if (config.watch && !hasExited) {
241
+ console.log('Watching for changes. Press Ctrl+C to stop.');
117
242
  }
118
243
  }
244
+ catch {
245
+ cleanupAndExit(1);
246
+ }
247
+ return await runPromise;
119
248
  }
120
- async function discoverTests(config) {
121
- async function findFiles(pattern) {
122
- let files = [];
123
- let exclude = ['node_modules/**', '.git/**'];
124
- for await (let file of fsp.glob(pattern, { cwd: process.cwd(), exclude })) {
125
- files.push(path.resolve(process.cwd(), file));
126
- }
127
- return files;
249
+ async function resolveCwd(cwd) {
250
+ try {
251
+ return await fsp.realpath(cwd);
252
+ }
253
+ catch {
254
+ return path.resolve(cwd);
128
255
  }
129
- let files = await findFiles(config.glob.test);
256
+ }
257
+ async function discoverTests(config, cwd) {
258
+ let files = await findFiles(config.glob.test, config.glob.exclude, cwd);
130
259
  if (files.length === 0) {
131
260
  console.log(`No test files found matching pattern: ${config.glob.test}`);
132
- process.exit(1);
261
+ return null;
133
262
  }
134
- let e2eSet = new Set(await findFiles(config.glob.e2e));
263
+ let browserSet = new Set(await findFiles(config.glob.browser, config.glob.exclude, cwd));
264
+ let e2eSet = new Set(await findFiles(config.glob.e2e, config.glob.exclude, cwd));
135
265
  let types = new Set(config.type.split(','));
136
- let e2eFiles = types.has('e2e') ? files.filter((f) => e2eSet.has(f)) : [];
137
- let serverFiles = types.has('server') ? files.filter((f) => !e2eSet.has(f)) : [];
138
- let totalFiles = serverFiles.length + e2eFiles.length;
266
+ let browserFiles = types.has('browser') ? files.filter((f) => browserSet.has(f)) : [];
267
+ let e2eFiles = types.has('e2e') ? files.filter((file) => e2eSet.has(file)) : [];
268
+ let serverFiles = types.has('server')
269
+ ? files.filter((file) => !browserSet.has(file) && !e2eSet.has(file))
270
+ : [];
271
+ let totalFiles = browserFiles.length + serverFiles.length + e2eFiles.length;
139
272
  if (totalFiles === 0) {
140
273
  console.log(`No test files remain after filtering for type ${config.type}`);
141
- process.exit(1);
274
+ return null;
142
275
  }
143
- console.log(`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${e2eFiles.length} e2e)`);
276
+ console.log(`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${browserFiles.length} browser, ${e2eFiles.length} e2e)`);
144
277
  return {
145
278
  files,
146
279
  serverFiles,
280
+ browserFiles,
147
281
  e2eFiles,
148
282
  };
149
283
  }
150
- function queueRerun(reason) {
151
- if (!config.watch || hasExited)
152
- return;
153
- clearTimeout(rerunTimer);
154
- rerunTimer = setTimeout(() => {
155
- rerunTimer = undefined;
156
- if (running) {
157
- queued = true;
158
- }
159
- else {
160
- console.log(`\n↻ Change detected (${reason}), re-running tests...\n`);
161
- void executeRun();
284
+ async function findFiles(pattern, excludePattern, cwd) {
285
+ let files = [];
286
+ if (IS_BUN) {
287
+ // Bun's `fs.promises.glob` follows symlinks and doesn't prune traversal
288
+ // via `exclude`, so it enters pnpm symlink cycles in `node_modules`.
289
+ // Use Bun's native Glob, which defaults to `followSymlinks: false`.
290
+ // @ts-expect-error — bun module is only resolvable under the Bun runtime
291
+ let { Glob } = await import('bun');
292
+ let glob = new Glob(pattern);
293
+ let excludeGlob = new Glob(excludePattern);
294
+ for await (let file of glob.scan({ cwd, absolute: true })) {
295
+ if (!excludeGlob.match(path.relative(cwd, file))) {
296
+ files.push(file);
297
+ }
162
298
  }
163
- }, 100);
164
- }
165
- function cleanupAndExit(code) {
166
- if (hasExited)
167
- return;
168
- hasExited = true;
169
- watcher?.close();
170
- process.exit(code);
299
+ return files;
300
+ }
301
+ for await (let file of fsp.glob(pattern, { cwd, exclude: [excludePattern] })) {
302
+ files.push(path.resolve(cwd, file));
303
+ }
304
+ return files;
171
305
  }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export type { RemixTestConfig } from './lib/config.ts';
2
2
  export { describe, it, suite, test, before, after, beforeEach, afterEach, beforeAll, afterAll, } from './lib/framework.ts';
3
3
  export { mock } from './lib/mock.ts';
4
4
  export type { TestContext } from './lib/context.ts';
5
+ export type { FakeTimers } from './lib/fake-timers.ts';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EACL,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,MAAM,EACN,KAAK,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EACL,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,MAAM,EACN,KAAK,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const colors: import("@remix-run/terminal").TerminalStyles;
2
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/lib/colors.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,8CAAiB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { createStyles } from '@remix-run/terminal';
2
+ export const colors = createStyles();
@@ -1,4 +1,6 @@
1
1
  import type { PlaywrightTestConfig } from 'playwright/test';
2
+ export declare const IS_RUNNING_FROM_SRC: boolean;
3
+ export declare function getBrowserTestRootDir(): string;
2
4
  export interface RemixTestConfig {
3
5
  /**
4
6
  * Options for controlling the playwright browser
@@ -12,14 +14,31 @@ export interface RemixTestConfig {
12
14
  /**
13
15
  * Glob patterns to identify test files
14
16
  * - `glob.test`: Glob pattern for all test files (--glob.test)
17
+ * - `glob.browser`: Glob pattern for the subset of browser test files (--glob.browser)
15
18
  * - `glob.e2e`: Glob pattern for the subset of e2e test files (--glob.e2e)
19
+ * - `glob.exclude`: Glob pattern for paths to exclude from discovery (--glob.exclude)
16
20
  */
17
21
  glob?: {
18
22
  test?: string;
23
+ browser?: string;
19
24
  e2e?: string;
25
+ exclude?: string;
20
26
  };
21
27
  /** Max number of concurrent test workers (--concurrency) */
22
28
  concurrency?: number | string;
29
+ /**
30
+ * Coverage configuration. `true` enables with defaults; an object enables with settings;
31
+ * `false` disables. CLI `--coverage` flag overrides the boolean aspect.
32
+ */
33
+ coverage?: boolean | {
34
+ dir?: string;
35
+ include?: string[];
36
+ exclude?: string[];
37
+ statements?: number | string;
38
+ lines?: number | string;
39
+ branches?: number | string;
40
+ functions?: number | string;
41
+ };
23
42
  /**
24
43
  * Path to a module that exports `globalSetup` and/or `globalTeardown` functions,
25
44
  * called once before and after the test run respectively. (--setup)
@@ -45,9 +64,20 @@ export interface ResolvedRemixTestConfig {
45
64
  open?: boolean;
46
65
  };
47
66
  concurrency: number;
67
+ coverage: {
68
+ dir: string;
69
+ include?: string[];
70
+ exclude?: string[];
71
+ statements?: number;
72
+ lines?: number;
73
+ branches?: number;
74
+ functions?: number;
75
+ } | undefined;
48
76
  glob: {
49
77
  test: string;
78
+ browser: string;
50
79
  e2e: string;
80
+ exclude: string;
51
81
  };
52
82
  playwrightConfig: string | PlaywrightTestConfig | undefined;
53
83
  project: string | undefined;
@@ -56,5 +86,6 @@ export interface ResolvedRemixTestConfig {
56
86
  type: string;
57
87
  watch: boolean;
58
88
  }
59
- export declare function loadConfig(): Promise<ResolvedRemixTestConfig>;
89
+ export declare function loadConfig(args?: string[], cwd?: string): Promise<ResolvedRemixTestConfig>;
90
+ export declare function getRemixTestHelpText(_target?: NodeJS.WriteStream): string;
60
91
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAgF3D,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;KACf,CAAA;IACD;;;;OAIG;IACH,IAAI,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,MAAM,CAAA;KACb,CAAA;IACD,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,oBAAoB,CAAA;IAChD,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;KACf,CAAA;IACD,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;KACZ,CAAA;IACD,gBAAgB,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAA;IAC3D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf;AAED,wBAAsB,UAAU,qCAU/B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAG3D,eAAO,MAAM,mBAAmB,SAA4D,CAAA;AAa5F,wBAAgB,qBAAqB,IAAI,MAAM,CAS9C;AAqID,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;KACf,CAAA;IACD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EACL,OAAO,GACP;QACE,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;QAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;QACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;QAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAC5B,CAAA;IACL;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,oBAAoB,CAAA;IAChD,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;KACf,CAAA;IACD,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EACJ;QACE,GAAG,EAAE,MAAM,CAAA;QACX,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,GACD,SAAS,CAAA;IACb,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,GAAG,EAAE,MAAM,CAAA;QACX,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,gBAAgB,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAA;IAC3D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAM,EAA0B,EAAE,GAAG,SAAgB,oCAK3F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAmBzF"}