@remix-run/test 0.0.0 → 0.1.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -2
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +171 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2 -0
  9. package/dist/lib/config.d.ts +60 -0
  10. package/dist/lib/config.d.ts.map +1 -0
  11. package/dist/lib/config.js +152 -0
  12. package/dist/lib/context.d.ts +69 -0
  13. package/dist/lib/context.d.ts.map +1 -0
  14. package/dist/lib/context.js +49 -0
  15. package/dist/lib/e2e-server.d.ts +11 -0
  16. package/dist/lib/e2e-server.d.ts.map +1 -0
  17. package/dist/lib/e2e-server.js +15 -0
  18. package/dist/lib/executor.d.ts +27 -0
  19. package/dist/lib/executor.d.ts.map +1 -0
  20. package/dist/lib/executor.js +123 -0
  21. package/dist/lib/framework.d.ts +107 -0
  22. package/dist/lib/framework.d.ts.map +1 -0
  23. package/dist/lib/framework.js +198 -0
  24. package/dist/lib/framework.test.d.ts +2 -0
  25. package/dist/lib/framework.test.d.ts.map +1 -0
  26. package/dist/lib/framework.test.e2e.d.ts +2 -0
  27. package/dist/lib/framework.test.e2e.d.ts.map +1 -0
  28. package/dist/lib/framework.test.e2e.js +29 -0
  29. package/dist/lib/framework.test.js +283 -0
  30. package/dist/lib/mock.d.ts +52 -0
  31. package/dist/lib/mock.d.ts.map +1 -0
  32. package/dist/lib/mock.js +61 -0
  33. package/dist/lib/playwright.d.ts +15 -0
  34. package/dist/lib/playwright.d.ts.map +1 -0
  35. package/dist/lib/playwright.js +84 -0
  36. package/dist/lib/reporters/dot.d.ts +10 -0
  37. package/dist/lib/reporters/dot.d.ts.map +1 -0
  38. package/dist/lib/reporters/dot.js +55 -0
  39. package/dist/lib/reporters/files.d.ts +10 -0
  40. package/dist/lib/reporters/files.d.ts.map +1 -0
  41. package/dist/lib/reporters/files.js +70 -0
  42. package/dist/lib/reporters/index.d.ts +14 -0
  43. package/dist/lib/reporters/index.d.ts.map +1 -0
  44. package/dist/lib/reporters/index.js +18 -0
  45. package/dist/lib/reporters/spec.d.ts +10 -0
  46. package/dist/lib/reporters/spec.d.ts.map +1 -0
  47. package/dist/lib/reporters/spec.js +152 -0
  48. package/dist/lib/reporters/tap.d.ts +10 -0
  49. package/dist/lib/reporters/tap.d.ts.map +1 -0
  50. package/dist/lib/reporters/tap.js +54 -0
  51. package/dist/lib/runner.d.ts +9 -0
  52. package/dist/lib/runner.d.ts.map +1 -0
  53. package/dist/lib/runner.js +89 -0
  54. package/dist/lib/utils.d.ts +16 -0
  55. package/dist/lib/utils.d.ts.map +1 -0
  56. package/dist/lib/utils.js +27 -0
  57. package/dist/lib/watcher.d.ts +5 -0
  58. package/dist/lib/watcher.d.ts.map +1 -0
  59. package/dist/lib/watcher.js +39 -0
  60. package/dist/lib/worker-e2e.d.ts +2 -0
  61. package/dist/lib/worker-e2e.d.ts.map +1 -0
  62. package/dist/lib/worker-e2e.js +48 -0
  63. package/dist/lib/worker.d.ts +2 -0
  64. package/dist/lib/worker.d.ts.map +1 -0
  65. package/dist/lib/worker.js +29 -0
  66. package/package.json +58 -5
  67. package/src/cli.ts +210 -0
  68. package/src/index.ts +15 -0
  69. package/src/lib/config.ts +231 -0
  70. package/src/lib/context.ts +126 -0
  71. package/src/lib/e2e-server.ts +28 -0
  72. package/src/lib/executor.ts +162 -0
  73. package/src/lib/framework.ts +251 -0
  74. package/src/lib/mock.ts +89 -0
  75. package/src/lib/playwright.ts +102 -0
  76. package/src/lib/reporters/dot.ts +57 -0
  77. package/src/lib/reporters/files.ts +76 -0
  78. package/src/lib/reporters/index.ts +28 -0
  79. package/src/lib/reporters/spec.ts +173 -0
  80. package/src/lib/reporters/tap.ts +58 -0
  81. package/src/lib/runner.ts +137 -0
  82. package/src/lib/utils.ts +40 -0
  83. package/src/lib/watcher.ts +46 -0
  84. package/src/lib/worker-e2e.ts +52 -0
  85. package/src/lib/worker.ts +30 -0
  86. package/tsconfig.json +14 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shopify Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,326 @@
1
- # Placeholder Package
1
+ # `test`
2
2
 
3
- This package is a placeholder published at `0.0.0` to reserve the npm name and configure CI publish permissions.
3
+ A test framework for Remix applications
4
+
5
+ ## Features
6
+
7
+ - `describe`/`it` test structure with `before`/`after`/`beforeEach`/`afterEach` hooks
8
+ - Server-side unit testing
9
+ - Playwright E2E testing via `t.serve`
10
+ - Mock functions and method spies via `t.mock.fn` / `t.mock.method`
11
+ - Watch mode
12
+ - Config file support (`remix-test.config.ts`)
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm i remix
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Write test files that import from `remix/test`:
23
+
24
+ ```ts
25
+ import * as assert from 'remix/assert'
26
+ import { describe, it } from 'remix/test'
27
+
28
+ describe('My Test Suite', () => {
29
+ it('tests a function', () => {
30
+ let result = something()
31
+ assert.equal(result, 42)
32
+ })
33
+ })
34
+ ```
35
+
36
+ Run tests with the CLI:
37
+
38
+ ```sh
39
+ remix-test
40
+ ```
41
+
42
+ By default, `remix-test` discovers all files matching `**/*.test.{ts,tsx}`. Pass a glob as the first positional argument to override:
43
+
44
+ ```sh
45
+ remix-test "src/**/*.test.ts"
46
+ ```
47
+
48
+ Or, you may control via the `glob.test` config field/CLI arg.
49
+
50
+ ### Config File
51
+
52
+ Create a `remix-test.config.ts` (or `.js`) file at the root of your project (shown with default values):
53
+
54
+ ```ts
55
+ import type { RemixTestConfig } from 'remix/test'
56
+
57
+ export default {
58
+ // Browser options for E2E tests
59
+ browser: {
60
+ // Echo browser console output to the terminal
61
+ echo: false,
62
+ // Open browser (via playwright `headless:false`) and keep it open after tests
63
+ // complete (useful for debugging)
64
+ open: false,
65
+ },
66
+
67
+ // Max number of concurrent test workers (default `os.availableParallelism()`)
68
+ concurrency: 2,
69
+
70
+ glob: {
71
+ // Glob pattern identifying all test files (default: "**/*.test?(.e2e).{ts,tsx}")
72
+ test: '**/*.test?(.e2e).ts',
73
+ // Global pattern identifying the subset of E2E test files{ts,tsx}")
74
+ e2e: '**/*.test.e2e.ts',
75
+ },
76
+
77
+ // Playwright configuration for E2E tests, or string path to an existing
78
+ // config file on disk
79
+ playwrightConfig: {
80
+ projects: [
81
+ { name: 'chromium', use: { browserName: 'chromium' } },
82
+ { name: 'firefox', use: { browserName: 'firefox' } },
83
+ ],
84
+ use: {
85
+ navigationTimeout: 5_000,
86
+ actionTimeout: 5_000,
87
+ },
88
+ },
89
+
90
+ // Comma-separated list of playwright projects to run E2E tests for
91
+ project: 'chromium',
92
+
93
+ // Test reporter ("spec", "files", "tap", "dot")
94
+ reporter: 'spec',
95
+
96
+ // Path to a setup module (see Setup section below)
97
+ setup: './test/setup.ts',
98
+
99
+ // Comma-separated list of test types to run ("server", "e2e")
100
+ type: 'server,e2e',
101
+
102
+ // Watch for file changes and re-run
103
+ watch: false,
104
+ } satisfies RemixTestConfig
105
+ ```
106
+
107
+ ### CLI Options
108
+
109
+ You can point to a different config file location with the `--config` flag:
110
+
111
+ ```sh
112
+ remix-test --config ./tests/config.ts
113
+ ```
114
+
115
+ You may also specify any config field as a CLI flag which will take precedence over config file values:
116
+
117
+ | Flag | Short |
118
+ | --------------------------- | ----- |
119
+ | `--browser.echo` | |
120
+ | `--browser.open` | |
121
+ | `--concurrency <n>` | `-c` |
122
+ | `--glob.test` | |
123
+ | `--glob.e2e` | |
124
+ | `--playwrightConfig <path>` | |
125
+ | `--project <name>` | `-p` |
126
+ | `--reporter <name>` | `-r` |
127
+ | `--setup <path>` | `-s` |
128
+ | `--type <name>` | `-t` |
129
+ | `--watch` | `-w` |
130
+
131
+ ### Setup
132
+
133
+ The `setup` option points to a module that can export `globalSetup` and/or `globalTeardown` functions, called once before and after the entire test run respectively:
134
+
135
+ ```ts
136
+ // ./test/setup.ts
137
+ export async function globalSetup() {
138
+ await db.migrate()
139
+ }
140
+
141
+ export async function globalTeardown() {
142
+ await db.close()
143
+ }
144
+ ```
145
+
146
+ ## API
147
+
148
+ ### Test framework
149
+
150
+ ```ts
151
+ import { beforeAll, afterAll, beforeEach, afterEach, describe, it } from 'remix/test'
152
+
153
+ beforeAll(() => {})
154
+ afterAll(() => {})
155
+
156
+ describe('My Test Suite', () => {
157
+ beforeEach(() => {})
158
+ afterEach(() => {})
159
+
160
+ it('tests something', () => {})
161
+ it('tests something else', () => {})
162
+ })
163
+ ```
164
+
165
+ `suite` and `test` are aliases for `describe` and `it`.
166
+
167
+ ```ts
168
+ import { suite, test } from 'remix/test'
169
+
170
+ suite('My Test Suite', () => {
171
+ test('tests something', () => {})
172
+ })
173
+ ```
174
+
175
+ ### Test Context
176
+
177
+ Each test callback receives a `TestContext` (`t`) as its first argument with helpful test utilities.
178
+
179
+ ```ts
180
+ interface TestContext {
181
+ // Register a cleanup function to run after the test completes
182
+ after(fn: () => void): void
183
+
184
+ // Mock tracker, mirroring the shape of Node's `t.mock` from `node:test`
185
+ mock: {
186
+ // Create a mock function with an optional implementation
187
+ fn<T extends (...args: any[]) => any>(impl?: T): MockFunction<T>
188
+
189
+ // Mock an object method with an optional implementation override
190
+ method<T extends object, K extends keyof T>(
191
+ obj: T,
192
+ methodName: K,
193
+ impl?: Function,
194
+ ): MockFunction
195
+ }
196
+
197
+ // E2E only: start a server with the given request handler, returns a Playwright Page
198
+ serve(handler: (req: Request) => Promise<Response>): Promise<Page>
199
+ }
200
+ ```
201
+
202
+ #### Mocks and Spies
203
+
204
+ Use `t.mock.fn()`/`t.mock.method()` to set up mocks and method spies. This is preferred over the standalone `mock` import because TestContext method mocks are automatically restored after the test runs.
205
+
206
+ ```ts
207
+ it('mocks and spies', (t) => {
208
+ // Create a mock function
209
+ let fn = t.mock.fn((x: number) => x * 2)
210
+ fn(3)
211
+ fn.mock.calls[0].result // 6
212
+
213
+ // Mock an existing method
214
+ let spy = t.mock.method(console, 'warn')
215
+ console.warn('test')
216
+ spy.mock.calls.length // 1
217
+ // spy is restored automatically when the test ends
218
+ })
219
+ ```
220
+
221
+ #### Cleanup
222
+
223
+ You can register local test cleanup logic with `t.after()`:
224
+
225
+ ```ts
226
+ it('cleanup', (t) => {
227
+ let conn = db.connect()
228
+ t.after(() => conn.close())
229
+ // ...
230
+ })
231
+ ```
232
+
233
+ #### E2E
234
+
235
+ In E2E test files, `t.serve()` starts an HTTP server and returns a Playwright `Page`. See [E2E Testing](#e2e-testing) for details.
236
+
237
+ ```ts
238
+ it('navigates to home', async (t) => {
239
+ let router = createRouter()
240
+ let page = await t.serve(router.fetch)
241
+ await page.goto('/')
242
+ })
243
+ ```
244
+
245
+ ### Standalone mocks (module scope)
246
+
247
+ When you need a mock outside of a test body, import `mock` directly and call `restore()` manually:
248
+
249
+ ```ts
250
+ import { mock } from 'remix/test'
251
+
252
+ let spy = mock.method(console, 'log')
253
+ // ...
254
+ spy.mock.restore?.()
255
+ ```
256
+
257
+ ### E2E Testing
258
+
259
+ E2E tests use [Playwright](https://playwright.dev) and are discovered by the `**/*.test.e2e.{ts,tsx}` glob pattern (configurable via `glob.e2e`). They use the same `describe`/`it` API as unit tests.
260
+
261
+ E2E tests receive `t.serve()` on the test context, which starts an HTTP server with the given request handler and returns a Playwright [`Page`](https://playwright.dev/docs/api/class-page). The server and page are automatically closed after each test.
262
+
263
+ ```ts
264
+ import * as assert from 'remix/assert'
265
+ import { describe, it } from 'remix/test'
266
+ import { createRouter } from './router.ts'
267
+
268
+ describe('checkout', () => {
269
+ it('adds an item to the cart', async (t) => {
270
+ let router = createRouter()
271
+ let page = await t.serve(router.fetch)
272
+
273
+ await page.goto('/')
274
+ await page.getByRole('button', { name: 'Add to Cart' }).click()
275
+ await page.getByRole('link', { name: 'Cart' }).click()
276
+ await page.getByRole('heading', { name: 'Shopping Cart' }).waitFor()
277
+
278
+ assert.equal(await page.locator('[data-test-cart-quantity]').innerText(), 1)
279
+ })
280
+ })
281
+ ```
282
+
283
+ Configure Playwright (browsers, timeouts, viewport, etc.) via `playwrightConfig` in your config file:
284
+
285
+ ```ts
286
+ export default {
287
+ playwrightConfig: {
288
+ projects: [
289
+ { name: 'chromium', use: { browserName: 'chromium' } },
290
+ { name: 'firefox', use: { browserName: 'firefox' } },
291
+ { name: 'webkit', use: { browserName: 'webkit' } },
292
+ ],
293
+ use: {
294
+ navigationTimeout: 5_000,
295
+ actionTimeout: 5_000,
296
+ },
297
+ },
298
+
299
+ // Or, point to an existing playwright config file
300
+ // playwrightConfig: './playwright.config.ts'
301
+ } satisfies RemixTestConfig
302
+ ```
303
+
304
+ Set `browser.open: true` to keep the browser open after tests finish — useful for debugging failures.
305
+
306
+ ### Assertions
307
+
308
+ `remix/test` re-exports `remix/assert`. See the [`@remix-run/assert` README](../assert/README.md) for full API documentation.
309
+
310
+ ```ts
311
+ import * as assert from 'remix/assert'
312
+
313
+ assert.ok(value)
314
+ assert.equal(actual, expected)
315
+ assert.notEqual(actual, expected)
316
+ assert.deepEqual(actual, expected)
317
+ assert.notDeepEqual(actual, expected)
318
+ assert.match(string, regexp)
319
+ assert.throws(fn)
320
+ await assert.rejects(asyncFn)
321
+ assert.fail('message')
322
+ ```
323
+
324
+ ## License
325
+
326
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ import * as fsp from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { tsImport } from 'tsx/esm/api';
5
+ import { runServerTests } from "./lib/runner.js";
6
+ import { createReporter } from "./lib/reporters/index.js";
7
+ 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.');
23
+ }
24
+ }
25
+ catch {
26
+ cleanupAndExit(1);
27
+ }
28
+ async function executeRun() {
29
+ if (hasExited)
30
+ return;
31
+ running = true;
32
+ let globalTeardown;
33
+ 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
+ }
76
+ }
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`);
83
+ }
84
+ else {
85
+ project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false };
86
+ }
87
+ }
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;
99
+ }
100
+ }
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');
114
+ }
115
+ else if (!config.watch) {
116
+ cleanupAndExit(latestExitCode);
117
+ }
118
+ }
119
+ }
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;
128
+ }
129
+ let files = await findFiles(config.glob.test);
130
+ if (files.length === 0) {
131
+ console.log(`No test files found matching pattern: ${config.glob.test}`);
132
+ process.exit(1);
133
+ }
134
+ let e2eSet = new Set(await findFiles(config.glob.e2e));
135
+ 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;
139
+ if (totalFiles === 0) {
140
+ console.log(`No test files remain after filtering for type ${config.type}`);
141
+ process.exit(1);
142
+ }
143
+ console.log(`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${e2eFiles.length} e2e)`);
144
+ return {
145
+ files,
146
+ serverFiles,
147
+ e2eFiles,
148
+ };
149
+ }
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();
162
+ }
163
+ }, 100);
164
+ }
165
+ function cleanupAndExit(code) {
166
+ if (hasExited)
167
+ return;
168
+ hasExited = true;
169
+ watcher?.close();
170
+ process.exit(code);
171
+ }
@@ -0,0 +1,5 @@
1
+ export type { RemixTestConfig } from './lib/config.ts';
2
+ export { describe, it, suite, test, before, after, beforeEach, afterEach, beforeAll, afterAll, } from './lib/framework.ts';
3
+ export { mock } from './lib/mock.ts';
4
+ export type { TestContext } from './lib/context.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { describe, it, suite, test, before, after, beforeEach, afterEach, beforeAll, afterAll, } from "./lib/framework.js";
2
+ export { mock } from "./lib/mock.js";
@@ -0,0 +1,60 @@
1
+ import type { PlaywrightTestConfig } from 'playwright/test';
2
+ export interface RemixTestConfig {
3
+ /**
4
+ * Options for controlling the playwright browser
5
+ * - `browser.echo`: Echo browser console output to stdout (--browser.echo)
6
+ * - `browser.open`: Open browser window and keep open after test finish (--browser.open)
7
+ */
8
+ browser?: {
9
+ echo?: boolean;
10
+ open?: boolean;
11
+ };
12
+ /**
13
+ * Glob patterns to identify test files
14
+ * - `glob.test`: Glob pattern for all test files (--glob.test)
15
+ * - `glob.e2e`: Glob pattern for the subset of e2e test files (--glob.e2e)
16
+ */
17
+ glob?: {
18
+ test?: string;
19
+ e2e?: string;
20
+ };
21
+ /** Max number of concurrent test workers (--concurrency) */
22
+ concurrency?: number | string;
23
+ /**
24
+ * Path to a module that exports `globalSetup` and/or `globalTeardown` functions,
25
+ * called once before and after the test run respectively. (--setup)
26
+ */
27
+ setup?: string;
28
+ /**
29
+ * Playwright configuration — either a path to a playwright config file or an inline
30
+ * PlaywrightTestConfig object. CLI `--playwrightConfig` only accepts a file path.
31
+ */
32
+ playwrightConfig?: string | PlaywrightTestConfig;
33
+ /** Filter tests to a specific playwright project or comma-separated list of projects (--project) */
34
+ project?: string;
35
+ /** Test reporter (--reporter) */
36
+ reporter?: string;
37
+ /** Comma-separated list of test types to run (--type) */
38
+ type?: string;
39
+ /** Watch mode — re-run tests on file changes (--watch) */
40
+ watch?: boolean;
41
+ }
42
+ export interface ResolvedRemixTestConfig {
43
+ browser: {
44
+ echo?: boolean;
45
+ open?: boolean;
46
+ };
47
+ concurrency: number;
48
+ glob: {
49
+ test: string;
50
+ e2e: string;
51
+ };
52
+ playwrightConfig: string | PlaywrightTestConfig | undefined;
53
+ project: string | undefined;
54
+ reporter: string;
55
+ setup: string | undefined;
56
+ type: string;
57
+ watch: boolean;
58
+ }
59
+ export declare function loadConfig(): Promise<ResolvedRemixTestConfig>;
60
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +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"}