@variance-authority/storybook-collector 0.1.1 → 0.3.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/CHANGELOG.md +60 -24
- package/README.md +344 -116
- package/dist/contract.d.ts +34 -1
- package/dist/contract.js.map +1 -1
- package/dist/execution.d.ts +50 -3
- package/dist/execution.js +32 -6
- package/dist/execution.js.map +1 -1
- package/dist/index.d.ts +2 -132
- package/dist/index.js +142 -155
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +146 -0
- package/dist/options.js +2 -0
- package/dist/options.js.map +1 -0
- package/dist/page-agent-entry.js +2 -1
- package/dist/page-agent-entry.js.map +1 -1
- package/dist/page-agent.bundle.js +952 -545
- package/dist/page-agent.d.ts +27 -1
- package/dist/page-agent.js +16 -3
- package/dist/page-agent.js.map +1 -1
- package/dist/read.d.ts +44 -0
- package/dist/read.js +164 -0
- package/dist/read.js.map +1 -0
- package/dist/serve.js +34 -7
- package/dist/serve.js.map +1 -1
- package/dist/source.d.ts +1 -1
- package/dist/source.js +1 -1
- package/dist/source.js.map +1 -1
- package/dist/world.d.ts +59 -0
- package/dist/world.js +33 -0
- package/dist/world.js.map +1 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,69 @@
|
|
|
1
1
|
# @variance-authority/storybook-collector
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
6
|
+
|
|
7
|
+
## 0.2.0
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- 8efba76: A run that changed ten files stops rewriting the whole selection index
|
|
12
|
+
|
|
13
|
+
Every run after the first reads the selection index, lays its own recording over
|
|
14
|
+
it, and writes it back. At a repository's scale almost all of that was spent
|
|
15
|
+
making objects nobody reads: a run that re-records ten modules of twenty thousand
|
|
16
|
+
decoded six hundred thousand regions into a model, merged ten of them, and
|
|
17
|
+
encoded the model back — the other 99.95% of the index materialized and
|
|
18
|
+
re-serialized to arrive at the bytes it was read from.
|
|
19
|
+
|
|
20
|
+
`layerTestCoverage` does the merge and the encode as one pass over the columns
|
|
21
|
+
the previous snapshot is already stored in. A module the run did not touch is
|
|
22
|
+
never made an object: its rows are copied column to column as integers, its
|
|
23
|
+
strings blob to blob as bytes, and the only thing that happens to either is the
|
|
24
|
+
renumbering the new dictionary implies. Objects are made for what the merge has
|
|
25
|
+
to reason about — the tests, the modules this run re-recorded, and the carried
|
|
26
|
+
modules whose text moved on disk. Over twenty thousand modules that is 1855 ms to
|
|
27
|
+
616, and it is byte for byte the same file, which a gate asserts across every
|
|
28
|
+
case the merge distinguishes.
|
|
29
|
+
|
|
30
|
+
The columns are now zstd rather than brotli, at two levels, because the runs are
|
|
31
|
+
two kinds of data. A varint run is a dense stream of small integers and answers
|
|
32
|
+
to a long search; a run of the string blob is file paths and hex digests, which
|
|
33
|
+
zstd finds most of at level 1 and nothing more of above it. Against the brotli
|
|
34
|
+
quality 4 it replaces, over the same snapshot: 319 ms to compress became 120, and
|
|
35
|
+
the file got 86 KB smaller.
|
|
36
|
+
|
|
37
|
+
`zlib.zstdCompressSync` arrived in Node 22.15, so that is the floor these
|
|
38
|
+
packages declare. The snapshot's layout version moved with the codec, which means
|
|
39
|
+
an index written by an earlier build is refused at its header and rebuilt — one
|
|
40
|
+
full run, and nothing a reader has to think about.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- e546e21: Where the JSX settings live under Vite 8
|
|
45
|
+
|
|
46
|
+
Vite 8 transforms with oxc, so `esbuild: { jsx, jsxDev, jsxImportSource }` becomes
|
|
47
|
+
`oxc: { jsx: { runtime, development, importSource } }`, and `esbuild.keepNames`
|
|
48
|
+
becomes `build.rolldownOptions.output.keepNames`. A config keeps whichever keys it
|
|
49
|
+
is given and reads only the ones its own major knows, so the wrong block is not an
|
|
50
|
+
error, not a warning and not a log line: the plugin installs, the bundle runs,
|
|
51
|
+
every subject renders, and every report names the line a component is declared on
|
|
52
|
+
instead of the line that wrote the element. It fails in the direction that looks
|
|
53
|
+
like it worked.
|
|
54
|
+
|
|
55
|
+
The `jsx-source` README carries a table of where the two settings live per
|
|
56
|
+
transform, both spellings on every copyable snippet, and the reminder to read
|
|
57
|
+
`provenanceOf`'s result rather than the config — the config cannot tell you.
|
|
58
|
+
`storybook-collector` gets the same for `keepNames`, on the symptom it produces: a
|
|
59
|
+
confident report naming a component that appears nowhere in your source.
|
|
60
|
+
|
|
3
61
|
## 0.1.1
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
|
6
64
|
|
|
7
65
|
- db08866: Keep the run when the execution journal cannot be recorded.
|
|
8
|
-
|
|
66
|
+
|
|
9
67
|
`close()` already held the position that a journal is not the artefact under
|
|
10
68
|
review: a recorder that declines to record is written to stderr and the run
|
|
11
69
|
continues, because failing the run over it would cost every subject in it the
|
|
@@ -14,29 +72,7 @@
|
|
|
14
72
|
practice — went straight up through `close()` and took the run down with it.
|
|
15
73
|
Both outcomes now end the same way, in a sentence on stderr and a run that keeps
|
|
16
74
|
its images.
|
|
17
|
-
- Updated dependencies [fe578c8]
|
|
18
|
-
- @variance-authority/sense@0.1.1
|
|
19
|
-
- @variance-authority/core@0.1.1
|
|
20
|
-
- @variance-authority/dom@0.1.1
|
|
21
|
-
- @variance-authority/playwright@0.1.1
|
|
22
|
-
- @variance-authority/react@0.1.1
|
|
23
|
-
- @variance-authority/storybook@0.1.1
|
|
24
75
|
|
|
25
76
|
## 0.1.0
|
|
26
77
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- Updated dependencies [9587133]
|
|
30
|
-
- Updated dependencies [f800342]
|
|
31
|
-
- Updated dependencies [26ae9ed]
|
|
32
|
-
- Updated dependencies [f09528d]
|
|
33
|
-
- Updated dependencies [cfb333d]
|
|
34
|
-
- Updated dependencies [e8fee66]
|
|
35
|
-
- Updated dependencies [48d32eb]
|
|
36
|
-
- Updated dependencies [5c34e6d]
|
|
37
|
-
- @variance-authority/sense@0.1.0
|
|
38
|
-
- @variance-authority/dom@0.1.0
|
|
39
|
-
- @variance-authority/react@0.1.0
|
|
40
|
-
- @variance-authority/core@0.1.0
|
|
41
|
-
- @variance-authority/playwright@0.1.0
|
|
42
|
-
- @variance-authority/storybook@0.1.0
|
|
78
|
+
First release.
|
package/README.md
CHANGED
|
@@ -1,85 +1,90 @@
|
|
|
1
|
-
<p align="center"><img src="
|
|
1
|
+
<p align="center"><img src="https://variance-authority.dev/mark.svg" alt="Variance Authority mark" width="72"></p>
|
|
2
2
|
|
|
3
3
|
# @variance-authority/storybook-collector
|
|
4
4
|
|
|
5
5
|
> Turn a built or already-served Storybook into Variance Authority subjects.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
observe — one subject per story, the unit a comparison runs against. Use this
|
|
9
|
-
package when Storybook already owns component mounting and you want
|
|
10
|
-
source-attributed visual, semantic, accessibility, and localization findings
|
|
11
|
-
without writing a browser harness.
|
|
7
|
+
Part of [Variance Authority](https://variance-authority.dev).
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
## What this is for
|
|
10
|
+
|
|
11
|
+
You already have a Storybook. Each story is a UI state somebody deliberately
|
|
12
|
+
named, mounted and made ready — which is the expensive half of visual regression
|
|
13
|
+
testing, and you have paid for it.
|
|
14
|
+
|
|
15
|
+
This package hands that catalogue to `npx variance run` without a browser harness of
|
|
16
|
+
your own. It reads the `index.json` your Storybook build writes, opens the
|
|
17
|
+
preview once, switches stories over Storybook's own channel, and produces one
|
|
18
|
+
**subject** per story — one named UI state you asked for and can ask for again,
|
|
19
|
+
and the unit a comparison runs against. A story's subject id is its Storybook
|
|
20
|
+
story id with a `story:` prefix: `story:checkout--empty`.
|
|
21
|
+
|
|
22
|
+
What you get back is not only a pixel diff. Each changed region resolves to the
|
|
23
|
+
React component that rendered it and to the `file:line` where that component is
|
|
24
|
+
declared, so a red run names `Button` in `src/ds.tsx:41` rather than a rectangle.
|
|
25
|
+
|
|
26
|
+
This is the Storybook adapter, not the `variance` binary. The binary lives in
|
|
27
|
+
`@variance-authority/cli`, and both are installed below.
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
| Requirement | Detail |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Node | 22.15 or newer. |
|
|
34
|
+
| Module format | ESM only. Every `@variance-authority/*` package sets `"type": "module"`; `require()` will not load them. |
|
|
35
|
+
| Browser | Chromium, installed through Playwright. The binaries do not arrive with an `npm install`. |
|
|
36
|
+
| Storybook | Exercised against Storybook 10 with `@storybook/react-vite`. Index versions `3`, `4` and `5` are read, and the story root is looked for at `#storybook-root` (Storybook 7 and later) and then `#root` (before it). Storybook 8.3 and later emit `storyFinished`, which this package waits for; it falls back to `storyRendered` when the event is absent. |
|
|
37
|
+
| Builder | Collection reads `index.json` and drives the preview channel, so it does not know or care whether Vite or Webpack 5 built the Storybook. Two optional precision features are Vite plugins with no Webpack equivalent, and are named as such where they appear below: `@variance-authority/jsx-source` and the `tests` option. |
|
|
38
|
+
| React | Required for component attribution, the `wiring` band and the Suspense wait. The reading is of the expando `react-dom` writes on host nodes — `__reactFiber$` on React 17 and later, `__reactInternalInstance$` on React 16 — so there is no React version to match and none is imported. The suite runs against React 19. |
|
|
39
|
+
|
|
40
|
+
A Storybook whose renderer is not React still collects: documents, pixels,
|
|
41
|
+
comparison and verdicts all work, and the fiber walk reports nothing rather than
|
|
42
|
+
failing. Set `wiring: false` so it does not visit every node to find that out.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
15
45
|
|
|
16
46
|
```bash
|
|
17
47
|
npm install --save-dev @variance-authority/cli @variance-authority/storybook-collector
|
|
18
48
|
npx playwright install chromium
|
|
19
49
|
```
|
|
20
50
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
51
|
+
## Run the first loop
|
|
52
|
+
|
|
53
|
+
Four steps. Nothing here reads your `.storybook` directory — the built
|
|
54
|
+
`index.json` is the input, whether you build the Storybook or already serve one.
|
|
24
55
|
|
|
25
|
-
|
|
56
|
+
### 1. Build the Storybook
|
|
26
57
|
|
|
27
|
-
|
|
58
|
+
```bash
|
|
59
|
+
npx storybook build -o storybook-static
|
|
60
|
+
```
|
|
28
61
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
server also works when you pass `baseUrl` below.
|
|
62
|
+
A development server works too; pass its origin as `baseUrl` in step 2 and skip
|
|
63
|
+
the build.
|
|
32
64
|
|
|
33
|
-
### 2.
|
|
65
|
+
### 2. Write the collector module
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
which stories need an explicit readiness marker.
|
|
67
|
+
The collector is the module that tells the run how to find and read your
|
|
68
|
+
stories. Create it anywhere in your project and default-export the factory.
|
|
38
69
|
|
|
39
70
|
```js
|
|
40
71
|
// variance/storybook.mjs
|
|
41
72
|
import { storybookCollector } from '@variance-authority/storybook-collector';
|
|
42
73
|
|
|
43
74
|
export default storybookCollector({
|
|
44
|
-
ready: { 'checkout--deferred': '[data-testid="checkout-ready"]' },
|
|
45
75
|
source: { dirs: ['src'] },
|
|
46
76
|
});
|
|
47
77
|
```
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Against a built Storybook those names arrive minified — a run reports the cause
|
|
58
|
-
as `Ce`, not `Button`, because the bundler renamed it and nothing in the browser
|
|
59
|
-
remembers otherwise. `file:line` is the identifier that survives minification,
|
|
60
|
-
which is what makes the plugin below load-bearing there rather than an
|
|
61
|
-
enhancement.
|
|
62
|
-
|
|
63
|
-
It names where a component is *declared* — one line however many times that
|
|
64
|
-
component is rendered.
|
|
65
|
-
|
|
66
|
-
For the line the changed element is actually written on: against a
|
|
67
|
-
**development** Storybook, source locations work automatically — nothing to
|
|
68
|
-
configure. Against a **built, minified** Storybook it needs the
|
|
69
|
-
`@variance-authority/jsx-source` plugin in your `viteFinal` with
|
|
70
|
-
`esbuild.jsxDev` on; a report prefers the exact location wherever it comes
|
|
71
|
-
from, and this collector makes it repository-relative. The plugin does not
|
|
72
|
-
take `jsxImportSource`, so a Storybook already compiling against Emotion or
|
|
73
|
-
theme-ui keeps doing exactly that.
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npm install --save-dev @variance-authority/jsx-source
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 3. Point the CLI at the Storybook index and collector
|
|
79
|
+
`source.dirs` is a list of directory paths — not globs — resolved against the
|
|
80
|
+
directory you run `variance` from. The scan reads `.tsx`, `.jsx`, `.ts` and
|
|
81
|
+
`.js`, skips `node_modules`, `dist`, `storybook-static` and any path containing
|
|
82
|
+
`.test.`, `.spec.` or `.stories.`, and indexes where each component is
|
|
83
|
+
*declared*. Without it the report still names components and resolves none of
|
|
84
|
+
them to a file. A `dirs` list that matches no source file at all is refused,
|
|
85
|
+
naming the directories it walked.
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
module you created:
|
|
87
|
+
### 3. Write the run config
|
|
83
88
|
|
|
84
89
|
```json
|
|
85
90
|
{
|
|
@@ -98,33 +103,101 @@ module you created:
|
|
|
98
103
|
}
|
|
99
104
|
```
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
Save it as `variance.config.json`. Paths resolve against this file's directory
|
|
107
|
+
rather than the working directory, and unknown keys are refused by name.
|
|
108
|
+
`profile` is what the run is capable of observing — `chromium` resolves computed
|
|
109
|
+
style, layout and pixels, and a Storybook loop paints, so it is `chromium`.
|
|
110
|
+
|
|
111
|
+
Track the approved images. `.variance/` also collects per-run junk, so exclude
|
|
112
|
+
the contents rather than the directory:
|
|
113
|
+
|
|
114
|
+
```gitignore
|
|
115
|
+
.variance/*
|
|
116
|
+
!.variance/baselines/
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. Check the machine, run, accept, run again
|
|
102
120
|
|
|
103
121
|
```bash
|
|
104
122
|
npx variance doctor --config variance.config.json
|
|
105
123
|
npx variance run --config variance.config.json
|
|
106
124
|
```
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
`doctor` opens a browser, measures the fonts your config asserts, and lists
|
|
127
|
+
which **identities** the baseline root stores — an identity being the digest of
|
|
128
|
+
renderer, engine, platform, device scale factor and fonts that an approved image
|
|
129
|
+
is stored under. It exits `2` when no browser opens or when the root has no
|
|
130
|
+
images this machine could compare against.
|
|
131
|
+
|
|
132
|
+
The first durable run exits `1` and reports every story `new`. An image nobody
|
|
133
|
+
approved is not a pass. Promote the candidates the run already produced:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx variance accept --config variance.config.json --all
|
|
137
|
+
npx variance run --config variance.config.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The second run exits `0` and reports every story `unchanged`.
|
|
141
|
+
|
|
142
|
+
`accept` never renders again — it promotes an image this run produced — and its
|
|
143
|
+
full form is:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
npx variance accept [--config <path>] <subject>... | --all | --shape <fingerprint>[,...] [--message-file <path> [--message <text>]]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Keep `--all` for the first run and for deliberate re-baselines; it cannot tell a
|
|
150
|
+
story nobody looked at from one that changed. After setup, name subject ids:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npx variance accept --config variance.config.json story:checkout--empty story:checkout--one-item
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`--shape` takes a **fingerprint** — an identifier the report gives every changed
|
|
157
|
+
region, naming *what* changed rather than which screenshots it landed in.
|
|
158
|
+
Accepting one covers every subject where that shape is the whole change, and
|
|
159
|
+
refuses by name any subject where something else changed too.
|
|
160
|
+
|
|
161
|
+
### What you get
|
|
162
|
+
|
|
163
|
+
`npx variance run` prints its summary to the terminal and writes the same facts to
|
|
164
|
+
`.variance/report.json`, which every other command reads. Abridged, a run after
|
|
165
|
+
one edit to `Button`:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
12 of 14 subject(s) observed, durable run at 2026-01-14T09:22:41.108Z
|
|
169
|
+
rendered by playwright-chromium (chromium@131, darwin/arm64, 1x)
|
|
170
|
+
7 unchanged, 5 changed, 2 not observed
|
|
171
|
+
|
|
172
|
+
[changed] story:checkout--empty — Button: 1356 pixel(s) differ across 2 region(s) in Button
|
|
173
|
+
[changed] story:checkout--one-item — Button: 1356 pixel(s) differ across 2 region(s) in Button
|
|
174
|
+
|
|
175
|
+
not observed: 2 subject(s) — 0 the run could not see, 2 excluded by configuration, 0 not reached by this change
|
|
176
|
+
[excluded] story:checkout--docs-only: excluded by tag `no-variance`
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The second list is the point of the first number. A run that planned fourteen
|
|
180
|
+
subjects, observed twelve and found five changes is a different fact from a run
|
|
181
|
+
of twelve, so every subject that was never observed is named with the reason.
|
|
182
|
+
|
|
183
|
+
For the before, diff and after images per changed story, grouped by cause rather
|
|
184
|
+
than by story:
|
|
114
185
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
and run again. The next unchanged run exits `0` without re-rendering
|
|
119
|
-
(*painting*) subjects whose stored document digest already proves they did
|
|
120
|
-
not move.
|
|
186
|
+
```bash
|
|
187
|
+
npx variance report --config variance.config.json --format html > .variance/report.html
|
|
188
|
+
```
|
|
121
189
|
|
|
122
|
-
The
|
|
123
|
-
|
|
124
|
-
|
|
190
|
+
The page references its images relatively, so write it beside the `report` path
|
|
191
|
+
the config declares. Written anywhere else it shows broken images.
|
|
192
|
+
|
|
193
|
+
An unchanged rerun does not repaint a story whose stored document digest already
|
|
194
|
+
proves it did not change.
|
|
125
195
|
|
|
126
196
|
## Options
|
|
127
197
|
|
|
198
|
+
Everything below is passed to `storybookCollector(...)` in the module from step
|
|
199
|
+
2.
|
|
200
|
+
|
|
128
201
|
| Option | Use it when | Default and boundary |
|
|
129
202
|
| --- | --- | --- |
|
|
130
203
|
| `ready` | A particular story finishes after Storybook's render signal. | No additional wait. Keys are Storybook story ids. |
|
|
@@ -132,52 +205,68 @@ complete cycle against a Storybook-built artifact: new → accept → unchanged
|
|
|
132
205
|
| `loading` | A story's *fallback* is the state you intend to review. | Omitted. Id globs, matched against the story id and the subject id. |
|
|
133
206
|
| `suspenseTimeoutMs` | A story legitimately needs longer than five seconds to arrive. | `5000`. `0` skips the wait and keeps the reading. |
|
|
134
207
|
| `source` | Reports should resolve component names to `file:line`. | Omitted; component names remain available. An empty or mistyped scan is refused. |
|
|
135
|
-
| `baseUrl` | Storybook is already running. | Omitted; the directory containing `subjects.index` is served on loopback for the run. |
|
|
136
|
-
| `headless` | You need to watch collection while debugging. | `true
|
|
208
|
+
| `baseUrl` | Storybook is already running, e.g. `http://localhost:6006`. | Omitted; the directory containing `subjects.index` is served on loopback for the run. |
|
|
209
|
+
| `headless` | You need to watch collection while debugging. | `true`. |
|
|
137
210
|
| `network` | Asset bytes at stable URLs must participate in render identity. | `true`; set `false` only when asset URLs are already content-addressed. |
|
|
211
|
+
| `hashAssets` | Your asset URLs already contain their own content hash. | `true`. Read only while `network` is on, so GIF freezing and blanking survive it — this, not `network: false`, is the setting for a content-addressed build. |
|
|
212
|
+
| `wiring` | Your preview's renderer is not React. | `true`. Reads props, context, hook cells and keys into a band of its own; turning it off changes no stored digest. |
|
|
213
|
+
| `holdings` | Application values behind the nodes are evidence you want read. | `false`. Changes `structureHash` — an inert wrapper survives the collapse — so both sides of a comparison must be read the same way. |
|
|
138
214
|
| `roots` | Your preview mounts somewhere other than the standard roots. | `['#storybook-root', '#root']`, tightest match first. |
|
|
139
|
-
| `tests` | The next run should be able to skip stories whose code nothing touched. | `false`. Requires a preview
|
|
215
|
+
| `tests` | The next run should be able to skip stories whose code nothing touched. | `false`. Requires a Vite-built preview that includes `testSelectionProbes()`; see below. |
|
|
216
|
+
|
|
217
|
+
A **band** is the severity category a change is filed under. Loudest first:
|
|
218
|
+
`a11y`, `geometry`, `token`, `content`, `texture`. A role or accessible name that
|
|
219
|
+
changed outranks a box that moved, which outranks a style value, which outranks
|
|
220
|
+
text, which outranks sub-pixel noise.
|
|
140
221
|
|
|
141
222
|
The package exports `StorybookCollectorOptions` plus the collector contract
|
|
142
|
-
types (`Collector`, `CollectorContext`, `Plan`, `PlannedSubject
|
|
223
|
+
types (`Collector`, `CollectorContext`, `Plan`, `PlannedSubject` and
|
|
143
224
|
`Collected`) for callers that need to wrap the factory without re-declaring its
|
|
144
|
-
boundary. `SourceScan`, `AcquireRequest
|
|
225
|
+
boundary. `SourceScan`, `AcquireRequest` and `Acquired` expose the corresponding
|
|
145
226
|
source and page-agent shapes.
|
|
146
227
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
the repository-keyed cache paths for the block inventory and the coverage index. A
|
|
151
|
-
story is its own owner in the recorded index — Storybook is an execution surface
|
|
152
|
-
this tool drives one subject at a time — and a story that did not render still
|
|
153
|
-
contributes its crossings while never justifying a later skip.
|
|
154
|
-
|
|
155
|
-
The collector reads a built Storybook index and switches stories over
|
|
156
|
-
Storybook's own preview channel, so a suite pays for one navigation rather than
|
|
157
|
-
one navigation per story. Addon chrome remains outside the selected story root
|
|
158
|
-
and does not enter the subject document.
|
|
228
|
+
One navigation serves the whole suite rather than one navigation per story, and
|
|
229
|
+
addon chrome stays outside the selected story root, so it never enters the
|
|
230
|
+
subject document.
|
|
159
231
|
|
|
160
232
|
## Readiness and loading
|
|
161
233
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
before anything is read — because content that arrives late brings its own
|
|
166
|
-
images and fonts.
|
|
234
|
+
Most stories are complete when Storybook emits `storyRendered`. Add a `ready`
|
|
235
|
+
entry only when a story continues mounting or fetching after that signal, keyed
|
|
236
|
+
by Storybook story id:
|
|
167
237
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
238
|
+
```js
|
|
239
|
+
// variance/storybook.mjs
|
|
240
|
+
import { storybookCollector } from '@variance-authority/storybook-collector';
|
|
241
|
+
|
|
242
|
+
export default storybookCollector({
|
|
243
|
+
ready: { 'checkout--deferred': '[data-testid="checkout-ready"]' },
|
|
244
|
+
source: { dirs: ['src'] },
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
If the selector never appears, collection fails naming the story and the
|
|
249
|
+
selector instead of silently capturing a spinner.
|
|
250
|
+
|
|
251
|
+
React Suspense is handled without a marker, and needs to be. Before each story is
|
|
252
|
+
read, every Suspense boundary under the story root is waited on — ahead of
|
|
253
|
+
stabilization, the step that pins in-flight CSS animations and transitions to a
|
|
254
|
+
settled frame, because content that arrives late brings its own images and fonts.
|
|
255
|
+
A marker cannot cover this case: a component that suspends renders no markup for
|
|
256
|
+
a selector to attach to, and `storyRendered` has already fired because the story
|
|
257
|
+
function returned.
|
|
258
|
+
|
|
259
|
+
A story still showing a fallback when the wait runs out is reported as **not
|
|
260
|
+
collected**, naming the open boundaries and the components that wrote them. A
|
|
261
|
+
skeleton on a slow machine and the component on a fast one is a difference no one
|
|
262
|
+
authored.
|
|
177
263
|
|
|
178
|
-
Declare the exception when the fallback is the subject:
|
|
264
|
+
Declare the exception when the fallback *is* the subject:
|
|
179
265
|
|
|
180
266
|
```js
|
|
267
|
+
// variance/storybook.mjs
|
|
268
|
+
import { storybookCollector } from '@variance-authority/storybook-collector';
|
|
269
|
+
|
|
181
270
|
export default storybookCollector({
|
|
182
271
|
loading: ['inbox--empty-state'],
|
|
183
272
|
source: { dirs: ['src'] },
|
|
@@ -188,6 +277,141 @@ A declared story waits for nothing, and is refused if it turns out to have
|
|
|
188
277
|
settled — a declaration that outlived its subject is the same nondeterminism from
|
|
189
278
|
the other side.
|
|
190
279
|
|
|
280
|
+
## Sharper source lines from a built Storybook
|
|
281
|
+
|
|
282
|
+
Two things get lost in a production Storybook build, and both are recovered in
|
|
283
|
+
`.storybook/main.js`. Skip this section entirely against a development
|
|
284
|
+
Storybook, where names and lines survive by default.
|
|
285
|
+
|
|
286
|
+
**Component names.** A bundler renames functions, React reads a component's
|
|
287
|
+
display name off the function, and a run then reports the cause as `Ce` rather
|
|
288
|
+
than `Button` — a complete, confident answer naming something that appears
|
|
289
|
+
nowhere in your source. Keep function names in whatever your builder spells that
|
|
290
|
+
setting.
|
|
291
|
+
|
|
292
|
+
**Element lines.** With names alone, a changed element resolves to the line its
|
|
293
|
+
component is *declared* on — one line however many times that component is
|
|
294
|
+
rendered. `@variance-authority/jsx-source` makes it resolve to the line the
|
|
295
|
+
element itself is written on. It is a Vite plugin.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm install --save-dev @variance-authority/jsx-source
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Added to an existing Vite config rather than replacing one. On Vite 8 the two
|
|
302
|
+
settings sit in two different sections — the JSX transform under `oxc`, keeping
|
|
303
|
+
names under `build.rolldownOptions.output`:
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
// .storybook/main.js
|
|
307
|
+
import { jsxSource } from '@variance-authority/jsx-source/vite';
|
|
308
|
+
|
|
309
|
+
export default {
|
|
310
|
+
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
|
311
|
+
framework: { name: '@storybook/react-vite', options: {} },
|
|
312
|
+
viteFinal: async (config) => ({
|
|
313
|
+
...config,
|
|
314
|
+
plugins: [...(config.plugins ?? []), jsxSource()],
|
|
315
|
+
oxc: { ...config.oxc, jsx: { runtime: 'automatic', development: true } },
|
|
316
|
+
build: {
|
|
317
|
+
...config.build,
|
|
318
|
+
rolldownOptions: {
|
|
319
|
+
...config.build?.rolldownOptions,
|
|
320
|
+
output: { ...config.build?.rolldownOptions?.output, keepNames: true },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
}),
|
|
324
|
+
};
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`development: true` is what makes the transform emit each element's file, line
|
|
328
|
+
and column at all; without it there is no location for the plugin to keep, and a
|
|
329
|
+
changed element reports the line its component is declared on. `keepNames` is a
|
|
330
|
+
Rolldown output option and does nothing inside the `oxc` block — it belongs in
|
|
331
|
+
the `build` section, not beside the JSX keys.
|
|
332
|
+
|
|
333
|
+
Vite 7 and below spell all three under `esbuild` — `jsx: 'automatic'`,
|
|
334
|
+
`jsxDev: true` and `keepNames: true` — in place of both sections above. A config
|
|
335
|
+
that uses the other major's key is read by nothing and warns about nothing: the
|
|
336
|
+
build succeeds, and the report names `Ce`. See
|
|
337
|
+
[`@variance-authority/jsx-source`](https://variance-authority.dev/reference/packages/jsx-source)
|
|
338
|
+
for builds that already use Emotion, theme-ui or another custom JSX runtime.
|
|
339
|
+
|
|
340
|
+
## Skipping stories nothing touched
|
|
341
|
+
|
|
342
|
+
With `tests`, a run records which blocks of your code each story crossed, so the
|
|
343
|
+
next run can skip a story whose code your diff did not reach. The recording is
|
|
344
|
+
done by a Vite plugin in the preview build:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
npm install --save-dev @variance-authority/sense
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
// .storybook/main.js
|
|
352
|
+
import { testSelectionProbes } from '@variance-authority/sense/journal';
|
|
353
|
+
|
|
354
|
+
export default {
|
|
355
|
+
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
|
356
|
+
framework: { name: '@storybook/react-vite', options: {} },
|
|
357
|
+
viteFinal: async (config) => ({
|
|
358
|
+
...config,
|
|
359
|
+
plugins: [...(config.plugins ?? []), testSelectionProbes({ label: 'storybook' })],
|
|
360
|
+
}),
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```js
|
|
365
|
+
// variance/storybook.mjs
|
|
366
|
+
import { storybookCollector } from '@variance-authority/storybook-collector';
|
|
367
|
+
|
|
368
|
+
export default storybookCollector({
|
|
369
|
+
source: { dirs: ['src'] },
|
|
370
|
+
tests: { label: 'storybook' },
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`tests` accepts `true` or an options object: `label` must match the one the
|
|
375
|
+
preview's `testSelectionProbes()` was given, `root` is the repository root the
|
|
376
|
+
recorded paths are relative to, and `cacheRoot` and `coverageFile` override the
|
|
377
|
+
repository-keyed cache paths for the block records and the coverage index.
|
|
378
|
+
Without a collector in the page, the run says so on stderr and records nothing.
|
|
379
|
+
|
|
380
|
+
`mode` is the probe recipe, and it has to be the one the preview was built
|
|
381
|
+
under: a snapshot names the recipe its ordinals were cut by, and a merge
|
|
382
|
+
discards a layer cut by another one, so a preview probing under `entries` and a
|
|
383
|
+
run folding under `presence` would each wipe the other. `preconditions` names
|
|
384
|
+
files whose contents are a precondition of every story's observation — a
|
|
385
|
+
`preview.js` that decides what renders belongs there, because a change to it
|
|
386
|
+
invalidates every reading and no crossing will ever say so. `heads` and
|
|
387
|
+
`commit` stamp the snapshot with the branch tips this recording stands on and
|
|
388
|
+
the commit it was taken at, which is what lets a later run tell a stale layer
|
|
389
|
+
from a current one.
|
|
390
|
+
|
|
391
|
+
A story is its own owner in the recorded index — Storybook is an execution
|
|
392
|
+
surface this tool drives one subject at a time — and a story that did not render
|
|
393
|
+
still contributes its crossings while never justifying a later skip.
|
|
394
|
+
|
|
395
|
+
Set `cases` to also write the execution index: one row per story per region,
|
|
396
|
+
beside the coverage index, or wherever `executionFile` points. That is the file
|
|
397
|
+
`variance covering` and `@variance-authority/distill` read to answer *which
|
|
398
|
+
stories walk this branch* — the question a component library is worth asking and
|
|
399
|
+
a preview of ten thousand stories usually is not, which is why it is off by
|
|
400
|
+
default.
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
export default storybookCollector({
|
|
404
|
+
source: { dirs: ['src'] },
|
|
405
|
+
tests: { label: 'storybook', cases: true },
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
A story is the case this costs nothing to name. The driver shows one at a time,
|
|
410
|
+
so the per-story grain a unit runner needs an async scope for is already here:
|
|
411
|
+
the index names each row by where the story is declared — its file, its
|
|
412
|
+
`title/name` — which is the coordinate a person reads in the sidebar and the one
|
|
413
|
+
a diff touches.
|
|
414
|
+
|
|
191
415
|
## When integration fails
|
|
192
416
|
|
|
193
417
|
- **The index cannot be read:** build Storybook first and check
|
|
@@ -199,26 +423,30 @@ the other side.
|
|
|
199
423
|
resolved. Fix what it awaits, or add the story to `loading` if the fallback is
|
|
200
424
|
what you intend to review.
|
|
201
425
|
- **Components have names but no source lines:** add or correct `source.dirs`.
|
|
202
|
-
|
|
203
|
-
|
|
426
|
+
- **The report names a component that is nowhere in your source:** minification
|
|
427
|
+
renamed it. See the `keepNames` setting above.
|
|
204
428
|
- **Elements report their component's declaration rather than their own line:**
|
|
205
|
-
|
|
206
|
-
`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
React 18 compiled with the classic transform, which records no location for
|
|
211
|
-
either mechanism to read.
|
|
429
|
+
this is the expected fallback for an uninstrumented built Storybook. Install
|
|
430
|
+
`jsx-source` only when the report must distinguish the exact element instance.
|
|
431
|
+
Against a development Storybook nothing normally needs installing; missing
|
|
432
|
+
lines there mean React is not its development build, the dev server emits no
|
|
433
|
+
source maps, or React 18 is still compiled to classic `createElement` calls.
|
|
212
434
|
- **Images change without a document change:** leave `network` enabled so asset
|
|
213
435
|
response bodies participate in the environment key. Disable it only when the
|
|
214
436
|
URL already identifies the bytes.
|
|
215
437
|
- **The browser is missing:** run `npx playwright install chromium`, then
|
|
216
|
-
`variance doctor` again.
|
|
438
|
+
`npx variance doctor` again.
|
|
439
|
+
- **A rerun reports `incomparable`:** the approved image was stored under a
|
|
440
|
+
different identity than this machine produces. `npx variance doctor` lists the
|
|
441
|
+
identities the baseline root stores.
|
|
217
442
|
|
|
218
443
|
## Boundaries
|
|
219
444
|
|
|
220
445
|
This package handles Storybook only. For pages your application already serves,
|
|
221
|
-
use `@variance-authority/route-collector`. For an existing
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
446
|
+
use `@variance-authority/route-collector`. For an existing Playwright test, use
|
|
447
|
+
`@variance-authority/playwright-test`, where the test body already performs
|
|
448
|
+
navigation, mounting and readiness.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
**[@variance-authority/storybook-collector](https://variance-authority.dev/reference/packages/storybook-collector)** is part of [Variance Authority](https://variance-authority.dev) — [documentation](https://variance-authority.dev/docs) · MIT
|