@remix-run/test 0.0.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.
- package/LICENSE +21 -0
- package/README.md +430 -2
- package/dist/app/client/entry.d.ts +2 -0
- package/dist/app/client/entry.d.ts.map +1 -0
- package/dist/app/client/entry.js +324 -0
- package/dist/app/client/iframe.d.ts +2 -0
- package/dist/app/client/iframe.d.ts.map +1 -0
- package/dist/app/client/iframe.js +22 -0
- package/dist/app/server.d.ts +6 -0
- package/dist/app/server.d.ts.map +1 -0
- package/dist/app/server.js +303 -0
- package/dist/cli-entry.d.ts +3 -0
- package/dist/cli-entry.d.ts.map +1 -0
- package/dist/cli-entry.js +14 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +305 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/colors.d.ts +2 -0
- package/dist/lib/colors.d.ts.map +1 -0
- package/dist/lib/colors.js +2 -0
- package/dist/lib/config.d.ts +91 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +255 -0
- package/dist/lib/context.d.ts +93 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +65 -0
- package/dist/lib/coverage-loader.d.ts +16 -0
- package/dist/lib/coverage-loader.d.ts.map +1 -0
- package/dist/lib/coverage-loader.js +20 -0
- package/dist/lib/coverage.d.ts +28 -0
- package/dist/lib/coverage.d.ts.map +1 -0
- package/dist/lib/coverage.js +212 -0
- package/dist/lib/executor.d.ts +4 -0
- package/dist/lib/executor.d.ts.map +1 -0
- package/dist/lib/executor.js +128 -0
- package/dist/lib/fake-timers.d.ts +6 -0
- package/dist/lib/fake-timers.d.ts.map +1 -0
- package/dist/lib/fake-timers.js +45 -0
- package/dist/lib/framework.d.ts +107 -0
- package/dist/lib/framework.d.ts.map +1 -0
- package/dist/lib/framework.js +198 -0
- package/dist/lib/import-module.d.ts +2 -0
- package/dist/lib/import-module.d.ts.map +1 -0
- package/dist/lib/import-module.js +29 -0
- package/dist/lib/mock.d.ts +52 -0
- package/dist/lib/mock.d.ts.map +1 -0
- package/dist/lib/mock.js +61 -0
- package/dist/lib/normalize.d.ts +2 -0
- package/dist/lib/normalize.d.ts.map +1 -0
- package/dist/lib/normalize.js +18 -0
- package/dist/lib/playwright.d.ts +15 -0
- package/dist/lib/playwright.d.ts.map +1 -0
- package/dist/lib/playwright.js +81 -0
- package/dist/lib/reporters/dot.d.ts +9 -0
- package/dist/lib/reporters/dot.d.ts.map +1 -0
- package/dist/lib/reporters/dot.js +56 -0
- package/dist/lib/reporters/files.d.ts +9 -0
- package/dist/lib/reporters/files.d.ts.map +1 -0
- package/dist/lib/reporters/files.js +71 -0
- package/dist/lib/reporters/index.d.ts +13 -0
- package/dist/lib/reporters/index.d.ts.map +1 -0
- package/dist/lib/reporters/index.js +18 -0
- package/dist/lib/reporters/results.d.ts +30 -0
- package/dist/lib/reporters/results.d.ts.map +1 -0
- package/dist/lib/reporters/results.js +1 -0
- package/dist/lib/reporters/spec.d.ts +9 -0
- package/dist/lib/reporters/spec.d.ts.map +1 -0
- package/dist/lib/reporters/spec.js +153 -0
- package/dist/lib/reporters/tap.d.ts +9 -0
- package/dist/lib/reporters/tap.d.ts.map +1 -0
- package/dist/lib/reporters/tap.js +54 -0
- package/dist/lib/runner-browser.d.ts +21 -0
- package/dist/lib/runner-browser.d.ts.map +1 -0
- package/dist/lib/runner-browser.js +117 -0
- package/dist/lib/runner.d.ts +14 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +118 -0
- package/dist/lib/runtime.d.ts +2 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +2 -0
- package/dist/lib/ts-transform.d.ts +4 -0
- package/dist/lib/ts-transform.d.ts.map +1 -0
- package/dist/lib/ts-transform.js +29 -0
- package/dist/lib/watcher.d.ts +5 -0
- package/dist/lib/watcher.d.ts.map +1 -0
- package/dist/lib/watcher.js +39 -0
- package/dist/lib/worker-e2e.d.ts +2 -0
- package/dist/lib/worker-e2e.d.ts.map +1 -0
- package/dist/lib/worker-e2e.js +49 -0
- package/dist/lib/worker.d.ts +2 -0
- package/dist/lib/worker.d.ts.map +1 -0
- package/dist/lib/worker.js +57 -0
- package/dist/test/coverage/fixture.d.ts +5 -0
- package/dist/test/coverage/fixture.d.ts.map +1 -0
- package/dist/test/coverage/fixture.js +32 -0
- package/dist/test/coverage/test-browser.d.ts +2 -0
- package/dist/test/coverage/test-browser.d.ts.map +1 -0
- package/dist/test/coverage/test-browser.js +24 -0
- package/dist/test/coverage/test-e2e.d.ts +2 -0
- package/dist/test/coverage/test-e2e.d.ts.map +1 -0
- package/dist/test/coverage/test-e2e.js +60 -0
- package/dist/test/coverage/test-unit.d.ts +2 -0
- package/dist/test/coverage/test-unit.d.ts.map +1 -0
- package/dist/test/coverage/test-unit.js +27 -0
- package/dist/test/framework.test.browser.d.ts +2 -0
- package/dist/test/framework.test.browser.d.ts.map +1 -0
- package/dist/test/framework.test.browser.js +107 -0
- package/dist/test/framework.test.e2e.d.ts +2 -0
- package/dist/test/framework.test.e2e.d.ts.map +1 -0
- package/dist/test/framework.test.e2e.js +34 -0
- package/package.json +79 -5
- package/src/app/client/entry.ts +353 -0
- package/src/app/client/iframe.ts +18 -0
- package/src/app/server.ts +336 -0
- package/src/cli-entry.ts +15 -0
- package/src/cli.ts +384 -0
- package/src/index.ts +16 -0
- package/src/lib/colors.ts +3 -0
- package/src/lib/config.ts +377 -0
- package/src/lib/context.ts +168 -0
- package/src/lib/coverage-loader.ts +31 -0
- package/src/lib/coverage.ts +320 -0
- package/src/lib/executor.ts +145 -0
- package/src/lib/fake-timers.ts +64 -0
- package/src/lib/framework.ts +251 -0
- package/src/lib/import-module.ts +29 -0
- package/src/lib/mock.ts +89 -0
- package/src/lib/normalize.ts +22 -0
- package/src/lib/playwright.ts +100 -0
- package/src/lib/reporters/dot.ts +58 -0
- package/src/lib/reporters/files.ts +77 -0
- package/src/lib/reporters/index.ts +27 -0
- package/src/lib/reporters/results.ts +29 -0
- package/src/lib/reporters/spec.ts +174 -0
- package/src/lib/reporters/tap.ts +58 -0
- package/src/lib/runner-browser.ts +165 -0
- package/src/lib/runner.ts +189 -0
- package/src/lib/runtime.ts +2 -0
- package/src/lib/ts-transform.ts +36 -0
- package/src/lib/watcher.ts +46 -0
- package/src/lib/worker-e2e.ts +54 -0
- package/src/lib/worker.ts +50 -0
- package/src/test/coverage/fixture.ts +34 -0
- package/src/test/coverage/test-browser.ts +29 -0
- package/src/test/coverage/test-e2e.ts +70 -0
- package/src/test/coverage/test-unit.ts +32 -0
- package/tsconfig.json +16 -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,431 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `test`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A test framework for JavaScript and TypeScript projects.
|
|
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
|
+
- In-browser component testing (pair with `render` from `remix/ui/test`)
|
|
11
|
+
- Mock functions and method spies via `t.mock.fn` / `t.mock.method`
|
|
12
|
+
- Unified code coverage reporting across unit and E2E tests
|
|
13
|
+
- Watch mode
|
|
14
|
+
- Config file support (`remix-test.config.ts`)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm i remix
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Write test files that import from `remix/test`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import * as assert from 'remix/assert'
|
|
28
|
+
import { describe, it } from 'remix/test'
|
|
29
|
+
|
|
30
|
+
describe('My Test Suite', () => {
|
|
31
|
+
it('tests a function', () => {
|
|
32
|
+
let result = something()
|
|
33
|
+
assert.equal(result, 42)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run tests with the CLI:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
remix test
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
By default, `remix test` discovers all files matching `**/*.test{,.e2e}.{ts,tsx}`. Pass a glob as the first positional argument to override:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
remix test "src/**/*.test.ts"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or, you may control via the `glob.test` config field/CLI arg.
|
|
51
|
+
|
|
52
|
+
If you install `@remix-run/test` directly instead of the umbrella `remix` package, the same runner is available as `remix-test`:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npm i @remix-run/test
|
|
56
|
+
remix-test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Config File
|
|
60
|
+
|
|
61
|
+
Create a `remix-test.config.ts` (or `.js`) file at the root of your project (shown with default values):
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import type { RemixTestConfig } from 'remix/test'
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
// Browser options for E2E tests
|
|
68
|
+
browser: {
|
|
69
|
+
// Echo browser console output to the terminal
|
|
70
|
+
echo: false,
|
|
71
|
+
// Open browser (via playwright `headless:false`) and keep it open after tests
|
|
72
|
+
// complete (useful for debugging)
|
|
73
|
+
open: false,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Max number of concurrent test workers (default `os.availableParallelism()`)
|
|
77
|
+
concurrency: 2,
|
|
78
|
+
|
|
79
|
+
// Code coverage options
|
|
80
|
+
coverage: {
|
|
81
|
+
// Enable coverage reporting
|
|
82
|
+
enabled: true,
|
|
83
|
+
// Output directory (default: ".coverage")
|
|
84
|
+
dir: '.coverage',
|
|
85
|
+
// Glob patterns to include/exclude
|
|
86
|
+
include: ['src/**'],
|
|
87
|
+
exclude: ['src/**/*.test.ts'],
|
|
88
|
+
// Minimum thresholds (%)
|
|
89
|
+
statements: 80,
|
|
90
|
+
lines: 80,
|
|
91
|
+
branches: 80,
|
|
92
|
+
functions: 80,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
glob: {
|
|
96
|
+
// Glob pattern identifying all test files (default: "**/*.test{,.browser,.e2e}.{ts,tsx}")
|
|
97
|
+
test: '**/*.test{,.browser,.e2e}.ts',
|
|
98
|
+
// Glob pattern identifying browser test files (default: "**/*.test.browser.{ts,tsx}")
|
|
99
|
+
browser: '**/*.test.browser.ts',
|
|
100
|
+
// Glob pattern identifying E2E test files (default: "**/*.test.e2e.{ts,tsx}")
|
|
101
|
+
e2e: '**/*.test.e2e.ts',
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Playwright configuration for E2E tests, or string path to an existing
|
|
105
|
+
// config file on disk
|
|
106
|
+
playwrightConfig: {
|
|
107
|
+
projects: [
|
|
108
|
+
{ name: 'chromium', use: { browserName: 'chromium' } },
|
|
109
|
+
{ name: 'firefox', use: { browserName: 'firefox' } },
|
|
110
|
+
],
|
|
111
|
+
use: {
|
|
112
|
+
navigationTimeout: 5_000,
|
|
113
|
+
actionTimeout: 5_000,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Comma-separated list of playwright projects to run E2E tests for
|
|
118
|
+
project: 'chromium',
|
|
119
|
+
|
|
120
|
+
// Test reporter ("spec", "files", "tap", "dot")
|
|
121
|
+
reporter: 'spec',
|
|
122
|
+
|
|
123
|
+
// Path to a setup module (see Setup section below)
|
|
124
|
+
setup: './test/setup.ts',
|
|
125
|
+
|
|
126
|
+
// Comma-separated list of test types to run ("server", "browser", "e2e")
|
|
127
|
+
type: 'server,browser,e2e',
|
|
128
|
+
|
|
129
|
+
// Watch for file changes and re-run
|
|
130
|
+
watch: false,
|
|
131
|
+
} satisfies RemixTestConfig
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### CLI Options
|
|
135
|
+
|
|
136
|
+
You can point to a different config file location with the `--config` flag:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
remix test --config ./tests/config.ts
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
You may also specify any config field as a CLI flag which will take precedence over config file values:
|
|
143
|
+
|
|
144
|
+
| Flag | Short |
|
|
145
|
+
| --------------------------- | ----- |
|
|
146
|
+
| `--browser.echo` | |
|
|
147
|
+
| `--browser.open` | |
|
|
148
|
+
| `--concurrency <n>` | `-c` |
|
|
149
|
+
| `--coverage` | |
|
|
150
|
+
| `--coverage.dir <path>` | |
|
|
151
|
+
| `--coverage.include` | |
|
|
152
|
+
| `--coverage.exclude` | |
|
|
153
|
+
| `--coverage.statements` | |
|
|
154
|
+
| `--coverage.lines` | |
|
|
155
|
+
| `--coverage.branches` | |
|
|
156
|
+
| `--coverage.functions` | |
|
|
157
|
+
| `--glob.test` | |
|
|
158
|
+
| `--glob.browser` | |
|
|
159
|
+
| `--glob.e2e` | |
|
|
160
|
+
| `--playwrightConfig <path>` | |
|
|
161
|
+
| `--project <name>` | `-p` |
|
|
162
|
+
| `--reporter <name>` | `-r` |
|
|
163
|
+
| `--setup <path>` | `-s` |
|
|
164
|
+
| `--type <name>` | `-t` |
|
|
165
|
+
| `--watch` | `-w` |
|
|
166
|
+
|
|
167
|
+
### Setup
|
|
168
|
+
|
|
169
|
+
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:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// ./test/setup.ts
|
|
173
|
+
export async function globalSetup() {
|
|
174
|
+
await db.migrate()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function globalTeardown() {
|
|
178
|
+
await db.close()
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## API
|
|
183
|
+
|
|
184
|
+
### Test framework
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { beforeAll, afterAll, beforeEach, afterEach, describe, it } from 'remix/test'
|
|
188
|
+
|
|
189
|
+
beforeAll(() => {})
|
|
190
|
+
afterAll(() => {})
|
|
191
|
+
|
|
192
|
+
describe('My Test Suite', () => {
|
|
193
|
+
beforeEach(() => {})
|
|
194
|
+
afterEach(() => {})
|
|
195
|
+
|
|
196
|
+
it('tests something', () => {})
|
|
197
|
+
it('tests something else', () => {})
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`suite` and `test` are aliases for `describe` and `it`.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { suite, test } from 'remix/test'
|
|
205
|
+
|
|
206
|
+
suite('My Test Suite', () => {
|
|
207
|
+
test('tests something', () => {})
|
|
208
|
+
})
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Programmatic runner
|
|
212
|
+
|
|
213
|
+
`@remix-run/test/cli` exports `runRemixTest()` for tools that want to run the test runner without
|
|
214
|
+
exiting the current process:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { runRemixTest } from '@remix-run/test/cli'
|
|
218
|
+
|
|
219
|
+
let exitCode = await runRemixTest({
|
|
220
|
+
argv: ['--type', 'server'],
|
|
221
|
+
cwd: process.cwd(),
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`runRemixTest()` returns the runner exit code. The `remix test` and `remix-test` bin wrappers call
|
|
226
|
+
`process.exit()` with that code when the run finishes so open workers, browsers, or project handles
|
|
227
|
+
cannot keep the CLI alive.
|
|
228
|
+
|
|
229
|
+
### Test Context
|
|
230
|
+
|
|
231
|
+
Each test callback receives a `TestContext` (`t`) as its first argument with helpful test utilities.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// from 'remix/test'
|
|
235
|
+
interface TestContext {
|
|
236
|
+
// Register a cleanup function to run after the test completes
|
|
237
|
+
after(fn: () => void): void
|
|
238
|
+
|
|
239
|
+
// Mock tracker, mirroring the shape of Node's `t.mock` from `node:test`
|
|
240
|
+
mock: {
|
|
241
|
+
// Create a mock function with an optional implementation
|
|
242
|
+
fn<T extends (...args: any[]) => any>(impl?: T): MockFunction<T>
|
|
243
|
+
|
|
244
|
+
// Mock an object method with an optional implementation override
|
|
245
|
+
method<T extends object, K extends keyof T>(
|
|
246
|
+
obj: T,
|
|
247
|
+
methodName: K,
|
|
248
|
+
impl?: Function,
|
|
249
|
+
): MockFunction
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Replace global timer functions with controllable fakes
|
|
253
|
+
useFakeTimers(): FakeTimers
|
|
254
|
+
|
|
255
|
+
// E2E only: connect a running test server to a Playwright Page
|
|
256
|
+
serve(server: { baseUrl: string; close(): Promise<void> }): Promise<Page>
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### Mocks and Spies
|
|
261
|
+
|
|
262
|
+
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.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
it('mocks and spies', (t) => {
|
|
266
|
+
// Create a mock function
|
|
267
|
+
let fn = t.mock.fn((x: number) => x * 2)
|
|
268
|
+
fn(3)
|
|
269
|
+
fn.mock.calls[0].result // 6
|
|
270
|
+
|
|
271
|
+
// Mock an existing method
|
|
272
|
+
let spy = t.mock.method(console, 'warn')
|
|
273
|
+
console.warn('test')
|
|
274
|
+
spy.mock.calls.length // 1
|
|
275
|
+
// spy is restored automatically when the test ends
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Cleanup
|
|
280
|
+
|
|
281
|
+
You can register local test cleanup logic with `t.after()`:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
it('cleanup', (t) => {
|
|
285
|
+
let conn = db.connect()
|
|
286
|
+
t.after(() => conn.close())
|
|
287
|
+
// ...
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### Fake Timers
|
|
292
|
+
|
|
293
|
+
`t.useFakeTimers()` replaces the global timer functions (`setTimeout`, `setInterval`, etc.) with controllable fakes that are automatically restored after the test. It works in any test environment — server unit tests, browser tests, or E2E setup code.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
it('debounces a callback', (t) => {
|
|
297
|
+
let timers = t.useFakeTimers()
|
|
298
|
+
let calls = 0
|
|
299
|
+
let debounced = debounce(() => calls++, 300)
|
|
300
|
+
|
|
301
|
+
debounced()
|
|
302
|
+
timers.advance(299)
|
|
303
|
+
assert.equal(calls, 0)
|
|
304
|
+
timers.advance(1)
|
|
305
|
+
assert.equal(calls, 1)
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Method | Description |
|
|
310
|
+
| ------------- | --------------------------------------------------------------------------- |
|
|
311
|
+
| `advance(ms)` | Advance the clock by `ms` milliseconds, firing any elapsed timers |
|
|
312
|
+
| `restore()` | Restore the original timer functions (called automatically after each test) |
|
|
313
|
+
|
|
314
|
+
#### E2E
|
|
315
|
+
|
|
316
|
+
In E2E test files, `t.serve()` connects a running test server to a Playwright `Page`. See [E2E Testing](#e2e-testing) for details.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { createTestServer } from 'remix/node-fetch-server/test'
|
|
320
|
+
|
|
321
|
+
it('navigates to home', async (t) => {
|
|
322
|
+
let router = createRouter()
|
|
323
|
+
let server = await createTestServer(router.fetch)
|
|
324
|
+
let page = await t.serve(server)
|
|
325
|
+
await page.goto('/')
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Standalone mocks (module scope)
|
|
330
|
+
|
|
331
|
+
When you need a mock outside of a test body, import `mock` directly and call `restore()` manually:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { mock } from 'remix/test'
|
|
335
|
+
|
|
336
|
+
let spy = mock.method(console, 'log')
|
|
337
|
+
// ...
|
|
338
|
+
spy.mock.restore?.()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Browser Testing
|
|
342
|
+
|
|
343
|
+
Browser tests run components in an actual browser environment via Playwright and are discovered by the `**/*.test.browser.{ts,tsx}` glob pattern (configurable via `glob.browser`). They use the same `describe`/`it` API as unit tests. Each in-browser test suite runs in an isolated `iframe` so it has access to its own `document` instance.
|
|
344
|
+
|
|
345
|
+
#### `render()`
|
|
346
|
+
|
|
347
|
+
`render`, exported from `remix/ui/test`, mounts a component into the DOM and returns a `RenderResult`:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import * as assert from 'remix/assert'
|
|
351
|
+
import { describe, it } from 'remix/test'
|
|
352
|
+
import { render } from 'remix/ui/test'
|
|
353
|
+
import { Counter } from './counter.tsx'
|
|
354
|
+
|
|
355
|
+
describe('Counter', () => {
|
|
356
|
+
it('increments on click', async (t) => {
|
|
357
|
+
let { $, act, cleanup } = render(<Counter />)
|
|
358
|
+
t.after(cleanup)
|
|
359
|
+
|
|
360
|
+
assert.equal($('[data-count]')?.textContent, '0')
|
|
361
|
+
await act(() => $('[data-action="increment"]')?.click())
|
|
362
|
+
assert.equal($('[data-count]')?.textContent, '1')
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`RenderResult` provides:
|
|
368
|
+
|
|
369
|
+
| Property/Method | Description |
|
|
370
|
+
| --------------- | ----------------------------------------------------------------------- |
|
|
371
|
+
| `container` | The `HTMLElement` the component is mounted into |
|
|
372
|
+
| `root` | The Remix `VirtualRoot` the component is rendered in |
|
|
373
|
+
| `$(selector)` | Alias for `container.querySelector()` |
|
|
374
|
+
| `$$(selector)` | Alias for `container.querySelectorAll()` |
|
|
375
|
+
| `act(fn)` | Runs `fn` and flushes pending component updates |
|
|
376
|
+
| `cleanup()` | Unmounts and removes the container (pass to `t.after` for auto-cleanup) |
|
|
377
|
+
|
|
378
|
+
### E2E Testing
|
|
379
|
+
|
|
380
|
+
End-to-end (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.
|
|
381
|
+
|
|
382
|
+
E2E tests receive `t.serve()` on the test context, which accepts a running test server and returns a Playwright [`Page`](https://playwright.dev/docs/api/class-page) whose `baseURL` points at that server. The server and page are automatically closed after each test.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import * as assert from 'remix/assert'
|
|
386
|
+
import { createTestServer } from 'remix/node-fetch-server/test'
|
|
387
|
+
import { describe, it } from 'remix/test'
|
|
388
|
+
import { createRouter } from './router.ts'
|
|
389
|
+
|
|
390
|
+
describe('checkout', () => {
|
|
391
|
+
it('adds an item to the cart', async (t) => {
|
|
392
|
+
let router = createRouter()
|
|
393
|
+
let server = await createTestServer(router.fetch)
|
|
394
|
+
let page = await t.serve(server)
|
|
395
|
+
|
|
396
|
+
await page.goto('/')
|
|
397
|
+
await page.getByRole('button', { name: 'Add to Cart' }).click()
|
|
398
|
+
await page.getByRole('link', { name: 'Cart' }).click()
|
|
399
|
+
await page.getByRole('heading', { name: 'Shopping Cart' }).waitFor()
|
|
400
|
+
|
|
401
|
+
assert.equal(await page.locator('[data-test-cart-quantity]').innerText(), 1)
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Configure Playwright (browsers, timeouts, viewport, etc.) via `playwrightConfig` in your config file:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
export default {
|
|
410
|
+
playwrightConfig: {
|
|
411
|
+
projects: [
|
|
412
|
+
{ name: 'chromium', use: { browserName: 'chromium' } },
|
|
413
|
+
{ name: 'firefox', use: { browserName: 'firefox' } },
|
|
414
|
+
{ name: 'webkit', use: { browserName: 'webkit' } },
|
|
415
|
+
],
|
|
416
|
+
use: {
|
|
417
|
+
navigationTimeout: 5_000,
|
|
418
|
+
actionTimeout: 5_000,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Or, point to an existing playwright config file
|
|
423
|
+
// playwrightConfig: './playwright.config.ts'
|
|
424
|
+
} satisfies RemixTestConfig
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Set `browser.open: true` to keep the browser open after tests finish — useful for debugging failures.
|
|
428
|
+
|
|
429
|
+
## License
|
|
430
|
+
|
|
431
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../../src/app/client/entry.ts"],"names":[],"mappings":""}
|