mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-05 20:17:42 +00:00
shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
1 parent
6776366ce6
commit
6406208b35
637 files changed
+100297
-497
No files matched your search
@@ -0,0 +1,153 @@
|
||||
# Testing
|
||||
|
||||
## What This Covers
|
||||
|
||||
How to test the two layers most Remix code lives in: HTTP behavior and DOM behavior. Read this when the task involves:
|
||||
|
||||
- Driving the router with `router.fetch(new Request(...))` and asserting on the returned `Response`
|
||||
- Building a fresh router per test for session, storage, or database isolation
|
||||
- Rendering components into a real DOM with `render(...)` or `createRoot(...)`
|
||||
- Configuring `remix test` discovery, excludes, and coverage
|
||||
- Using adjacent CLI checks such as `remix routes`, `remix doctor`, and `remix version`
|
||||
- Choosing which layer to test for a given behavior
|
||||
|
||||
For session and auth test setup, see `auth-and-sessions.md`. For component lifecycle, see `component-model.md`.
|
||||
|
||||
## Two Shapes
|
||||
|
||||
Remix tests run with `remix test`, use `remix/test` for the test framework, and use `remix/assert` for assertions. Two main shapes:
|
||||
|
||||
- **Server / router tests** — drive the router with `router.fetch(new Request(...))` and assert on the returned `Response`. No DOM, no browser harness.
|
||||
- **Component tests** — render a component into a real DOM `Element` with `render(...)`, or use `createRoot(...)` directly when you need lower-level root control.
|
||||
|
||||
## Server / Router Tests
|
||||
|
||||
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.
|
||||
|
||||
```ts
|
||||
import * as assert from 'remix/assert'
|
||||
import { describe, it } from 'remix/test'
|
||||
|
||||
import { createBookstoreRouter } from '../app/router.ts'
|
||||
import { routes } from '../app/routes.ts'
|
||||
|
||||
describe('home', () => {
|
||||
it('responds 200 with the home page', async () => {
|
||||
let router = createBookstoreRouter()
|
||||
let response = await router.fetch(new Request('http://localhost' + routes.home.href()))
|
||||
|
||||
assert.equal(response.status, 200)
|
||||
assert.match(await response.text(), /Welcome to the Bookstore/)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```ts
|
||||
import { createMemorySessionStorage } from 'remix/session-storage/memory'
|
||||
import { createCookie } from 'remix/cookie'
|
||||
|
||||
let router = createBookstoreRouter({
|
||||
sessionCookie: createCookie('session', { secrets: ['test'] }),
|
||||
sessionStorage: createMemorySessionStorage(),
|
||||
})
|
||||
```
|
||||
|
||||
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`:
|
||||
|
||||
```ts
|
||||
import { createTestServer } from 'remix/node-fetch-server/test'
|
||||
|
||||
let server = await createTestServer((request) => router.fetch(request))
|
||||
try {
|
||||
let response = await fetch(new URL(routes.home.href(), server.baseUrl))
|
||||
assert.equal(response.status, 200)
|
||||
} finally {
|
||||
await server.close()
|
||||
}
|
||||
```
|
||||
|
||||
## Test Runner Config
|
||||
|
||||
Configure discovery and coverage in `remix-test.config.ts` or with CLI flags:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
glob: {
|
||||
test: '**/*.test{,.e2e}.{ts,tsx}',
|
||||
e2e: '**/*.test.e2e.{ts,tsx}',
|
||||
exclude: 'node_modules/**',
|
||||
},
|
||||
coverage: {
|
||||
dir: '.coverage',
|
||||
include: ['app/**/*.{ts,tsx}'],
|
||||
exclude: ['app/**/*.test.{ts,tsx}'],
|
||||
statements: 80,
|
||||
lines: 80,
|
||||
branches: 70,
|
||||
functions: 80,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Component Tests
|
||||
|
||||
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.
|
||||
|
||||
### Basic pattern
|
||||
|
||||
```tsx
|
||||
import * as assert from 'remix/assert'
|
||||
import { render } from 'remix/ui/test'
|
||||
|
||||
let result = render(<Counter />)
|
||||
|
||||
let button = result.$('button')!
|
||||
await result.act(() => button.click())
|
||||
|
||||
assert.match(result.container.textContent ?? '', /1/)
|
||||
result.cleanup()
|
||||
```
|
||||
|
||||
### Why act / flush
|
||||
|
||||
- **After initial render** — ensures event listeners are attached and the DOM is ready for interaction.
|
||||
- **After interactions** — applies updates from `handle.update()` calls triggered by events.
|
||||
- **After async work resolves** — applies updates from resolved `queueTask(...)` callbacks.
|
||||
|
||||
### Async operations
|
||||
|
||||
For components with async operations in `queueTask`, use `act(...)` after each async step:
|
||||
|
||||
```tsx
|
||||
let result = render(<AsyncLoader />)
|
||||
|
||||
assert.equal(result.container.textContent, 'Loading...')
|
||||
|
||||
await waitForFetch()
|
||||
await result.act(() => {})
|
||||
|
||||
assert.equal(result.container.textContent, 'Expected data')
|
||||
```
|
||||
|
||||
### Component removal
|
||||
|
||||
Use `result.cleanup()` or `root.dispose()` to remove the component tree and verify cleanup behavior:
|
||||
|
||||
```tsx
|
||||
let result = render(<MyComponent />)
|
||||
|
||||
assert.ok(result.$('.content'))
|
||||
|
||||
result.cleanup()
|
||||
assert.throws(() => result.$('.content'), /cleaned up/)
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Prefer real DOM interactions over mocking framework behavior.
|
||||
- Avoid testing implementation-only markers unless they are the only stable synchronization point.
|
||||
- One representative flow proving a behavior is better than repeating the same assertion across many paths.
|
||||
Reference in new issue
Block a user