Testing
Récit is tested on four levels. Each one answers a different question, and a change is tested at the lowest level that can see it.
| Level | What it checks | Where | Command |
|---|---|---|---|
| Unit tests | Logic without a DOM: parsers, schemas, config, SSR | *.test.ts, *.spec.ts, __tests__/ next to the code | pnpm test:related <files…> |
Stories and play | A component rendered in Chromium: states, interactions, axe | X.stories.tsx next to X.tsx | pnpm test:stories [files…] |
| Behavioural end-to-end | A built site or deck, navigated as a reader does | e2e/*-behaviour.spec.ts, e2e/site-*.spec.ts | pnpm test:e2e:host <spec> |
| Screenshots | What every page and every story looks like, light and dark | e2e/__screenshots__/…-linux.png, in the Docker container | pnpm test:e2e:linux [specs] |
Everything at once, as CI runs it: pnpm verify (lint, build, typecheck, unit tests and stories),
then pnpm test:e2e:linux.
Unit tests
Vitest runs one project per package (vitest.config.ts at the root lists them) plus scripts and
the storybook project. A unit test stays for what a story cannot show: a function without a DOM, a
schema, a server render through renderToString. A test that needs a mounted component reuses its
story with composeStories instead of building another harness.
Bash
The coverage thresholds sit in the root vitest.config.ts, one entry per package, at the level last
measured (rounded down) with autoUpdate: coverage can only go up.
Stories
Every component of the framework (packages/site), of the slides (packages/components), of the
deck, of the motion blocks, of the site kit and of the sites has a story next to it, and every
interactive component a play function. node scripts/check-stories.js counts what is missing and
fails pnpm check on the first finding. The workshop lives in sites/storybook; the title of a
story picks its decorators:
| Title | Source | What it gets |
|---|---|---|
Site/UI/<Component> | packages/site/src/lib/ui | Mantine, the color mode |
Site/Components/… | packages/site/src/lib/components, plugins | plus a site: router, query client, site context, slots, head |
Slides/<Component> | packages/components/src | a slide frame |
Deck/<Component> | packages/formats/deck/src | a compiled deck |
Motion/Blocks/<Block> | packages/formats/motion-blocks/src | a still film frame and a motion theme |
Kit/<Component> | packages/tools/site-kit/src/components | the site, as for Site/ |
Writing a story
packages/site/src/lib/components/Banner.stories.tsx
- One file per component, one export per state,
argscomplete onmeta. - Text in Lorem Ipsum, titles and comments in English.
- No class and no
stylein a story: lay variants out with Mantine'sStackorGroup, or withparameters.grid("stack"or"row"). - A site story sets its route with
parameters.site.pathand overrides the fixture config withparameters.site.options.
Writing a play function
A play function asserts what a reader sees and does, by role and name:
getByRole("button", { name: /copy/i }), never a class, a CSS selector or adata-testid(allowed only on an element without role and text, listed inscripts/check-stories.js).- After an interaction,
findBy*orwaitFor: the built Storybook renders withoutact(). - A portal (dialog, menu, popover) is outside the canvas:
within(canvasElement.ownerDocument.body). - A callback is a
fn()ofargs, asserted withtoHaveBeenCalledWith. - No snapshot of HTML: the screenshot is the picture, the
playis the behaviour. - Name a step by the behaviour:
opens on click and closes on Escape.
Axe runs on every story in error. A violation is fixed, or marked
parameters: { a11y: { test: "todo" } } with the reason on the line above.
Running the stories
Bash
A missing Chromium stops the run with the command that installs it:
pnpm exec playwright install chromium.
End-to-end tests
The e2e suite serves the built sites, decks and Storybook on the ports of e2e/ports.ts: build
before running it. Pass the specs you need, and only their projects are built and served.
Bash
The behavioural specs (site-behaviour, deck-behaviour, deck-features, accessibility)
navigate, type and click; they run anywhere. The screenshots are compared only in the Linux
container: its -linux.png files are the reference, and only pnpm test:e2e:update:linux writes
them.
The stories are screenshotted in light and dark by one spec per surface, so a change to one surface replays only its own:
| Spec | Stories |
|---|---|
e2e/storybook-site-ui.spec.ts | Site/UI/ |
e2e/storybook-site.spec.ts | Site/Components/, Site/Navigation/, Site/Plugins/ |
e2e/storybook-slides.spec.ts | Slides/ |
e2e/storybook-deck.spec.ts | Deck/ |
e2e/storybook-motion.spec.ts | Motion/ |
e2e/storybook-kit.spec.ts | Kit/, Sites/ |
A new story gets its two screenshots without touching a spec; its references are written in the container at the next gate.
Reading the gallery
After a container run, node scripts/screenshot-gallery.js builds
test-results/gallery/index.html: one row per screenshot that changed, with the reference, the new
image and their difference, a filter by spec, and an "accepted" button. Add --serve <port> to open
it from another machine. A change is accepted only when the page changed on purpose; the references
are then regenerated with pnpm test:e2e:update:linux <specs>.