@remix-run/cli 0.3.1 → 0.3.2

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.
@@ -2,8 +2,7 @@
2
2
 
3
3
  ## What This Covers
4
4
 
5
- How to test the two layers most Remix code lives in: HTTP behavior and DOM behavior. Read this when
6
- the task involves:
5
+ How to test the two layers most Remix code lives in: HTTP behavior and DOM behavior. Read this when the task involves:
7
6
 
8
7
  - Driving the router with `router.fetch(new Request(...))` and asserting on the returned `Response`
9
8
  - Building a fresh router per test for session, storage, or database isolation
@@ -12,24 +11,18 @@ the task involves:
12
11
  - Using adjacent CLI checks such as `remix routes`, `remix doctor`, and `remix version`
13
12
  - Choosing which layer to test for a given behavior
14
13
 
15
- For session and auth test setup, see `auth-and-sessions.md`. For component lifecycle, see
16
- `component-model.md`.
14
+ For session and auth test setup, see `auth-and-sessions.md`. For component lifecycle, see `component-model.md`.
17
15
 
18
16
  ## Two Shapes
19
17
 
20
- Remix tests run with `remix test`, use `remix/test` for the test framework, and use
21
- `remix/assert` for assertions. Two main shapes:
18
+ Remix tests run with `remix test`, use `remix/test` for the test framework, and use `remix/assert` for assertions. Two main shapes:
22
19
 
23
- - **Server / router tests** — drive the router with `router.fetch(new Request(...))` and assert
24
- on the returned `Response`. No DOM, no browser harness.
25
- - **Component tests** — render a component into a real DOM `Element` with `render(...)`, or use
26
- `createRoot(...)` directly when you need lower-level root control.
20
+ - **Server / router tests** — drive the router with `router.fetch(new Request(...))` and assert on the returned `Response`. No DOM, no browser harness.
21
+ - **Component tests** render a component into a real DOM `Element` with `render(...)`, or use `createRoot(...)` directly when you need lower-level root control.
27
22
 
28
23
  ## Server / Router Tests
29
24
 
30
- Treat the router as a pure `(Request) => Promise<Response>` function. Build a fresh app router
31
- per test (or per suite) so middleware state — sessions, in-memory storage, the database — stays
32
- isolated.
25
+ Treat the router as a pure `(Request) => Promise<Response>` function. Build a fresh app router per test (or per suite) so middleware state — sessions, in-memory storage, the database — stays isolated.
33
26
 
34
27
  ```ts
35
28
  import * as assert from 'remix/assert'
@@ -49,10 +42,7 @@ describe('home', () => {
49
42
  })
50
43
  ```
51
44
 
52
- Use `routes.<name>.href(...)` to build URLs in tests so they stay in sync with the route
53
- definition. For form-style POSTs, attach a `FormData` body to the `Request`. For tests that need
54
- a known session, swap in `createMemorySessionStorage()` and a test cookie when constructing the
55
- router.
45
+ Use `routes.<name>.href(...)` to build URLs in tests so they stay in sync with the route definition. For form-style POSTs, attach a `FormData` body to the `Request`. For tests that need a known session, swap in `createMemorySessionStorage()` and a test cookie when constructing the router.
56
46
 
57
47
  ```ts
58
48
  import { createMemorySessionStorage } from 'remix/session-storage/memory'
@@ -64,8 +54,7 @@ let router = createBookstoreRouter({
64
54
  })
65
55
  ```
66
56
 
67
- Use `createTestServer` from `remix/node-fetch-server/test` when the behavior depends on a real
68
- HTTP origin, redirects, streaming, cookies through a network boundary, or browser-style `fetch`:
57
+ Use `createTestServer` from `remix/node-fetch-server/test` when the behavior depends on a real HTTP origin, redirects, streaming, cookies through a network boundary, or browser-style `fetch`:
69
58
 
70
59
  ```ts
71
60
  import { createTestServer } from 'remix/node-fetch-server/test'
@@ -102,16 +91,11 @@ export default {
102
91
  }
103
92
  ```
104
93
 
105
- Use `remix test --coverage` to enable coverage with defaults. Use `glob.exclude` when discovery
106
- would otherwise enter generated output, symlinked workspaces, or other paths that should not
107
- produce tests.
94
+ Use `remix test --coverage` to enable coverage with defaults. Use `glob.exclude` when discovery would otherwise enter generated output, symlinked workspaces, or other paths that should not produce tests.
108
95
 
109
96
  ## Component Tests
110
97
 
111
- Use `render(...)` from `remix/ui/test` for most component tests. It creates a real DOM container,
112
- flushes the initial render, and returns `act(...)` so interactions can flush pending updates before
113
- assertions. Use `createRoot(container)` from `remix/ui` directly when a test needs explicit control
114
- over root rendering, flushing, or disposal.
98
+ Use `render(...)` from `remix/ui/test` for most component tests. It creates a real DOM container, flushes the initial render, and returns `act(...)` so interactions can flush pending updates before assertions. Use `createRoot(container)` from `remix/ui` directly when a test needs explicit control over root rendering, flushing, or disposal.
115
99
 
116
100
  ### Basic pattern
117
101
 
@@ -130,8 +114,7 @@ result.cleanup()
130
114
 
131
115
  ### Why act / flush
132
116
 
133
- - **After initial render** — ensures event listeners are attached and the DOM is ready for
134
- interaction.
117
+ - **After initial render** — ensures event listeners are attached and the DOM is ready for interaction.
135
118
  - **After interactions** — applies updates from `handle.update()` calls triggered by events.
136
119
  - **After async work resolves** — applies updates from resolved `queueTask(...)` callbacks.
137
120
 
@@ -152,8 +135,7 @@ assert.equal(result.container.textContent, 'Expected data')
152
135
 
153
136
  ### Component removal
154
137
 
155
- Use `result.cleanup()` or `root.dispose()` to remove the component tree and verify cleanup
156
- behavior:
138
+ Use `result.cleanup()` or `root.dispose()` to remove the component tree and verify cleanup behavior:
157
139
 
158
140
  ```tsx
159
141
  let result = render(<MyComponent />)
@@ -168,5 +150,4 @@ assert.throws(() => result.$('.content'), /cleaned up/)
168
150
 
169
151
  - Prefer real DOM interactions over mocking framework behavior.
170
152
  - Avoid testing implementation-only markers unless they are the only stable synchronization point.
171
- - One representative flow proving a behavior is better than repeating the same assertion across many
172
- paths.
153
+ - One representative flow proving a behavior is better than repeating the same assertion across many paths.
@@ -1,4 +1,5 @@
1
- import { css, type RemixNode } from 'remix/ui'
1
+ import type { Handle, RemixNode } from 'remix/ui'
2
+ import { css } from 'remix/ui'
2
3
 
3
4
  import { routes } from '../routes.ts'
4
5
 
@@ -10,22 +11,26 @@ export interface DocumentProps {
10
11
 
11
12
  const DEFAULT_TITLE = readAppDisplayName('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
12
13
 
13
- export function Document() {
14
- return ({ children, head, title = DEFAULT_TITLE }: DocumentProps) => (
15
- <html lang="en">
16
- <head>
17
- <meta charSet="utf-8" />
18
- <meta name="viewport" content="width=device-width, initial-scale=1" />
19
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
20
- <title>{title}</title>
21
- {head}
22
- </head>
23
- <body mix={css({ margin: 0 })}>
24
- {children}
25
- <script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
26
- </body>
27
- </html>
28
- )
14
+ export function Document(handle: Handle<DocumentProps>) {
15
+ return () => {
16
+ let { children, head, title = DEFAULT_TITLE } = handle.props
17
+
18
+ return (
19
+ <html lang="en">
20
+ <head>
21
+ <meta charSet="utf-8" />
22
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
23
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
24
+ <title>{title}</title>
25
+ {head}
26
+ </head>
27
+ <body mix={css({ margin: 0 })}>
28
+ {children}
29
+ <script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
30
+ </body>
31
+ </html>
32
+ )
33
+ }
29
34
  }
30
35
 
31
36
  function readAppDisplayName(value: string): string {
@@ -1,5 +1,6 @@
1
1
  // Delete this file and put your own home page in app/actions/controller.tsx
2
- import { css, type RemixNode } from 'remix/ui'
2
+ import type { Handle, RemixNode } from 'remix/ui'
3
+ import { css } from 'remix/ui'
3
4
 
4
5
  import { PromptButton } from '../assets/prompt-button.tsx'
5
6
  import { Document } from './document.tsx'
@@ -203,54 +204,62 @@ function CodingWithAiCard() {
203
204
  )
204
205
  }
205
206
 
206
- function CardLink() {
207
- return ({ href, icon, label }: { href: string; icon: RemixNode; label: string }) => (
208
- <a
209
- href={href}
210
- mix={css({
211
- display: 'flex',
212
- gap: '16px',
213
- alignItems: 'center',
214
- padding: '16px',
215
- borderRadius: '12px',
216
- color: 'var(--text-primary)',
217
- textDecoration: 'none',
218
- background: 'transparent',
219
- transition: 'background-color 150ms ease, color 150ms ease',
220
- '&:hover, &:focus-visible': {
221
- background: 'var(--surface-4)',
222
- color: 'var(--brand-blue)',
223
- outline: 'none',
224
- },
225
- })}
226
- >
227
- <IconSlot>{icon}</IconSlot>
228
- <span mix={css({ fontSize: '14px', lineHeight: 1.5, whiteSpace: 'nowrap' })}>{label}</span>
229
- </a>
230
- )
207
+ function CardLink(handle: Handle<{ href: string; icon: RemixNode; label: string }>) {
208
+ return () => {
209
+ let { href, icon, label } = handle.props
210
+
211
+ return (
212
+ <a
213
+ href={href}
214
+ mix={css({
215
+ display: 'flex',
216
+ gap: '16px',
217
+ alignItems: 'center',
218
+ padding: '16px',
219
+ borderRadius: '12px',
220
+ color: 'var(--text-primary)',
221
+ textDecoration: 'none',
222
+ background: 'transparent',
223
+ transition: 'background-color 150ms ease, color 150ms ease',
224
+ '&:hover, &:focus-visible': {
225
+ background: 'var(--surface-4)',
226
+ color: 'var(--brand-blue)',
227
+ outline: 'none',
228
+ },
229
+ })}
230
+ >
231
+ <IconSlot>{icon}</IconSlot>
232
+ <span mix={css({ fontSize: '14px', lineHeight: 1.5, whiteSpace: 'nowrap' })}>{label}</span>
233
+ </a>
234
+ )
235
+ }
231
236
  }
232
237
 
233
- function IconSlot() {
234
- return ({ children, rotated = false }: { children: RemixNode; rotated?: boolean }) => (
235
- <span
236
- aria-hidden="true"
237
- mix={css({
238
- flex: '0 0 24px',
239
- width: '24px',
240
- display: 'flex',
241
- alignItems: 'center',
242
- justifyContent: rotated ? 'center' : 'flex-start',
243
- '& svg': {
244
- width: '20px',
245
- height: '20px',
246
- display: 'block',
247
- ...(rotated ? { transform: 'rotate(180deg)' } : {}),
248
- },
249
- })}
250
- >
251
- {children}
252
- </span>
253
- )
238
+ function IconSlot(handle: Handle<{ children: RemixNode; rotated?: boolean }>) {
239
+ return () => {
240
+ let { children, rotated = false } = handle.props
241
+
242
+ return (
243
+ <span
244
+ aria-hidden="true"
245
+ mix={css({
246
+ flex: '0 0 24px',
247
+ width: '24px',
248
+ display: 'flex',
249
+ alignItems: 'center',
250
+ justifyContent: rotated ? 'center' : 'flex-start',
251
+ '& svg': {
252
+ width: '20px',
253
+ height: '20px',
254
+ display: 'block',
255
+ ...(rotated ? { transform: 'rotate(180deg)' } : {}),
256
+ },
257
+ })}
258
+ >
259
+ {children}
260
+ </span>
261
+ )
262
+ }
254
263
  }
255
264
 
256
265
  function Footer() {