@variance-authority/core 0.14.0 → 0.17.1
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 +878 -0
- package/README.md +38 -17
- package/dist/attribute/component-hash.d.ts +14 -5
- package/dist/attribute/component-hash.js +14 -5
- package/dist/attribute/component-hash.js.map +1 -1
- package/dist/attribute/composition.d.ts +16 -0
- package/dist/attribute/composition.js +4 -6
- package/dist/attribute/composition.js.map +1 -1
- package/dist/attribute/index.d.ts +3 -1
- package/dist/attribute/index.js +2 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/movement.d.ts +1 -1
- package/dist/attribute/movement.js.map +1 -1
- package/dist/attribute/region.d.ts +47 -4
- package/dist/attribute/region.js +91 -22
- package/dist/attribute/region.js.map +1 -1
- package/dist/attribute/source.d.ts +10 -24
- package/dist/attribute/source.js +4 -38
- package/dist/attribute/source.js.map +1 -1
- package/dist/attribute/subject-pieces.d.ts +37 -0
- package/dist/attribute/subject-pieces.js +122 -0
- package/dist/attribute/subject-pieces.js.map +1 -0
- package/dist/compare/band.d.ts +8 -2
- package/dist/compare/band.js +7 -2
- package/dist/compare/band.js.map +1 -1
- package/dist/compare/diff/attribution.js +6 -4
- package/dist/compare/diff/attribution.js.map +1 -1
- package/dist/compare/diff/compare-nodes.js +9 -1
- package/dist/compare/diff/compare-nodes.js.map +1 -1
- package/dist/compare/diff/match.d.ts +2 -1
- package/dist/compare/diff/match.js +52 -0
- package/dist/compare/diff/match.js.map +1 -1
- package/dist/compare/parting.js +6 -4
- package/dist/compare/parting.js.map +1 -1
- package/dist/format/capture.d.ts +25 -0
- package/dist/format/capture.js +6 -0
- package/dist/format/capture.js.map +1 -1
- package/dist/format/document.d.ts +28 -5
- package/dist/format/document.js +8 -2
- package/dist/format/document.js.map +1 -1
- package/dist/format/drive.d.ts +191 -0
- package/dist/format/drive.js +343 -0
- package/dist/format/drive.js.map +1 -0
- package/dist/format/environment.d.ts +20 -2
- package/dist/format/environment.js +7 -0
- package/dist/format/environment.js.map +1 -1
- package/dist/format/index.d.ts +13 -3
- package/dist/format/index.js +8 -2
- package/dist/format/index.js.map +1 -1
- package/dist/format/intervention.d.ts +79 -0
- package/dist/format/intervention.js.map +1 -1
- package/dist/format/origin.d.ts +32 -0
- package/dist/format/origin.js +54 -0
- package/dist/format/origin.js.map +1 -0
- package/dist/format/packed.d.ts +32 -0
- package/dist/format/packed.js +23 -0
- package/dist/format/packed.js.map +1 -0
- package/dist/format/shape.d.ts +31 -0
- package/dist/format/shape.js +269 -0
- package/dist/format/shape.js.map +1 -0
- package/dist/format/snapshot.d.ts +2 -0
- package/dist/format/snapshot.js.map +1 -1
- package/dist/format/stabilize.d.ts +35 -6
- package/dist/format/stabilize.js +58 -103
- package/dist/format/stabilize.js.map +1 -1
- package/dist/format/steps.d.ts +44 -0
- package/dist/format/steps.js +67 -0
- package/dist/format/steps.js.map +1 -0
- package/dist/format/waits.d.ts +12 -0
- package/dist/format/waits.js +156 -0
- package/dist/format/waits.js.map +1 -0
- package/dist/format/width.d.ts +35 -0
- package/dist/format/width.js +38 -0
- package/dist/format/width.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/judge/docket.js +1 -0
- package/dist/judge/docket.js.map +1 -1
- package/dist/judge/index.d.ts +1 -0
- package/dist/judge/index.js +1 -0
- package/dist/judge/index.js.map +1 -1
- package/dist/judge/restamp.d.ts +20 -0
- package/dist/judge/restamp.js +20 -0
- package/dist/judge/restamp.js.map +1 -0
- package/dist/nearest.d.ts +46 -0
- package/dist/nearest.js +102 -0
- package/dist/nearest.js.map +1 -0
- package/dist/relate/before.d.ts +19 -24
- package/dist/relate/before.js +37 -39
- package/dist/relate/before.js.map +1 -1
- package/dist/relate/beyond.d.ts +59 -0
- package/dist/relate/beyond.js +98 -0
- package/dist/relate/beyond.js.map +1 -0
- package/dist/relate/carried.d.ts +122 -0
- package/dist/relate/carried.js +279 -0
- package/dist/relate/carried.js.map +1 -0
- package/dist/relate/graph.d.ts +14 -12
- package/dist/relate/graph.js +12 -11
- package/dist/relate/graph.js.map +1 -1
- package/dist/relate/index.d.ts +3 -1
- package/dist/relate/index.js +3 -1
- package/dist/relate/index.js.map +1 -1
- package/dist/relate/merkle.js +2 -11
- package/dist/relate/merkle.js.map +1 -1
- package/dist/relate/narrow.d.ts +2 -2
- package/dist/relate/narrow.js +26 -73
- package/dist/relate/narrow.js.map +1 -1
- package/dist/relate/reach.d.ts +6 -6
- package/dist/relate/reach.js +10 -53
- package/dist/relate/reach.js.map +1 -1
- package/dist/relate/records.d.ts +6 -5
- package/dist/relate/records.js +3 -2
- package/dist/relate/records.js.map +1 -1
- package/dist/rules/index.d.ts +3 -3
- package/dist/rules/index.js +3 -3
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/normalize/cascade.d.ts +12 -0
- package/dist/rules/normalize/cascade.js +34 -16
- package/dist/rules/normalize/cascade.js.map +1 -1
- package/dist/rules/normalize/fixture.d.ts +3 -0
- package/dist/rules/normalize/fixture.js +3 -1
- package/dist/rules/normalize/fixture.js.map +1 -1
- package/dist/rules/normalize/index.d.ts +2 -2
- package/dist/rules/normalize/index.js +9 -2
- package/dist/rules/normalize/index.js.map +1 -1
- package/dist/rules/normalize/value.d.ts +13 -2
- package/dist/rules/normalize/value.js +42 -8
- package/dist/rules/normalize/value.js.map +1 -1
- package/dist/rules/normalize/wrapper.js +5 -0
- package/dist/rules/normalize/wrapper.js.map +1 -1
- package/dist/rules/ruleset.d.ts +24 -4
- package/dist/rules/ruleset.js +24 -4
- package/dist/rules/ruleset.js.map +1 -1
- package/dist/segment/index.d.ts +20 -4
- package/dist/segment/index.js +28 -7
- package/dist/segment/index.js.map +1 -1
- package/dist/shard/index.d.ts +11 -0
- package/dist/shard/index.js +11 -0
- package/dist/shard/index.js.map +1 -0
- package/dist/shard/placement.d.ts +119 -0
- package/dist/shard/placement.js +156 -0
- package/dist/shard/placement.js.map +1 -0
- package/dist/share/index.d.ts +1 -1
- package/dist/share/index.js +1 -1
- package/dist/share/index.js.map +1 -1
- package/package.json +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,883 @@
|
|
|
1
1
|
# @variance-authority/core
|
|
2
2
|
|
|
3
|
+
## 0.17.1
|
|
4
|
+
|
|
5
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
6
|
+
|
|
7
|
+
## 0.17.0
|
|
8
|
+
|
|
9
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
10
|
+
|
|
11
|
+
## 0.16.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 40a19b9: A committed baseline is named for its story or test
|
|
16
|
+
|
|
17
|
+
Under `directory` and `lfs`, a baseline was written to
|
|
18
|
+
`<root>/<identity digest>/<percent-encoded subject id>.png`, so a pull request
|
|
19
|
+
that accepted one listed a 32-character hex directory nobody could check against
|
|
20
|
+
the change, and each machine that accepted wrote its own copy. `baselines.path`
|
|
21
|
+
is now a template rendered from what the subject is: `{dir}` and `{file}` (the
|
|
22
|
+
file that declares it), `{title}`, `{name}`, `{id}`, `{project}` (the
|
|
23
|
+
Playwright project's name, in the fixture's `varianceBaselinePath` only) and
|
|
24
|
+
`{engine}` (the engine's family). The default, `{title}/{name}-{engine}`,
|
|
25
|
+
writes `Ui/Button/Primary-chromium.png`;
|
|
26
|
+
`{dir}/__baselines__/{file}/{name}-{engine}` with `root: "."` writes
|
|
27
|
+
`src/ui/Button/__baselines__/Button.stories.tsx/Primary-chromium.png` beside the
|
|
28
|
+
code. A label is spelled `Primary__dark-chromium.png`.
|
|
29
|
+
|
|
30
|
+
The machine that painted a baseline is read from the record beside it. One file
|
|
31
|
+
holds each subject per engine family, the last accept writes it, and a run on
|
|
32
|
+
another platform, scale factor or engine version compares against it, reading
|
|
33
|
+
unchanged or changed as the pixels say, with the moved field named as the cause.
|
|
34
|
+
A laptop and CI that take turns accepting rewrite the record each time; set
|
|
35
|
+
`baselines.records` to keep the records out of version control. The tribunal's
|
|
36
|
+
bucket store answers the same way: a lookup reads the last accept on the
|
|
37
|
+
engine family, where it used to prefer this machine's own row.
|
|
38
|
+
|
|
39
|
+
Two subjects that render one path, compared case-folded, stop the run with an
|
|
40
|
+
error naming both subjects and the template. Nothing is renumbered. The record
|
|
41
|
+
names the subject it was accepted for, so a shard, a Playwright worker or a
|
|
42
|
+
later run that renders the same path for another subject stops too. Two
|
|
43
|
+
Playwright projects that differ only in scale factor, colour scheme or viewport
|
|
44
|
+
render one path; the error names both projects and `{project}`. A record left by
|
|
45
|
+
a project the config no longer runs is a renamed project's, and the first run
|
|
46
|
+
under the new name takes it over.
|
|
47
|
+
|
|
48
|
+
The Vitest plugin reads a test's `describe` path from the runner, so a title
|
|
49
|
+
that contains ` > ` stays one directory under `{title}`.
|
|
50
|
+
|
|
51
|
+
**Breaking:** `baselines.layout` is removed, and a config that sets it is
|
|
52
|
+
refused with an error naming `path`. Baselines under the identity directories
|
|
53
|
+
are still read: the subject is compared against them, unchanged or changed as
|
|
54
|
+
the pixels say, with the move from the old path to the new one given as a cause,
|
|
55
|
+
and the next accept writes the template's path and deletes the old pair, so the
|
|
56
|
+
pull request shows a rename. An accept deletes only a pair this machine's
|
|
57
|
+
identity or engine line painted; another machine's stays until that machine
|
|
58
|
+
accepts. `BaselineKey` carries `file`, `title`, `name` and
|
|
59
|
+
`project` in place of `path`; the Playwright fixture, the Vitest plugin and
|
|
60
|
+
Storybook's index supply them, and `varianceBaselinePath` and `baselinePath`
|
|
61
|
+
set the template for a suite run without the CLI.
|
|
62
|
+
|
|
63
|
+
The git-LFS store's `.gitattributes` pattern and the action's commit-back
|
|
64
|
+
pathspecs are the template's glob, so a collocated baseline is tracked and
|
|
65
|
+
committed where it is written. An `lfs` config whose root is `.` and whose
|
|
66
|
+
template has no directory of its own is refused, because its glob would track
|
|
67
|
+
every hyphenated image in the repository; give the template a directory such
|
|
68
|
+
as `__baselines__`, give the baselines a root of their own, or set
|
|
69
|
+
`baselines.pattern`. A `directory` config at root `.` with such a template is
|
|
70
|
+
refused for the same reason: the action would commit every hyphenated image
|
|
71
|
+
back. An existing LFS root gets the template's pattern as a new
|
|
72
|
+
`.gitattributes` line the next time a run opens it; commit it with the baselines.
|
|
73
|
+
- 09f60c8: A reach walks type-only imports unless you name the runtime edges
|
|
74
|
+
|
|
75
|
+
`dependentsOf`, `dependenciesOf`, `affectedBy` and `carried` in
|
|
76
|
+
`@variance-authority/core/relate` now walk every edge kind, `type` included,
|
|
77
|
+
when you pass no `through`. A type-only import is still a dependency: a changed
|
|
78
|
+
type can break the file that imports it, and the walk said nothing about that
|
|
79
|
+
file. Test selection asks what a run loads, so every selection walk in Sense and
|
|
80
|
+
the CLI passes `RUNTIME_EDGES`, and what it selects does not change.
|
|
81
|
+
|
|
82
|
+
If you called one of these and relied on `type` edges being left out, pass
|
|
83
|
+
`{ through: RUNTIME_EDGES }`.
|
|
84
|
+
- 0b6af81: A changed region is joined against the baseline's nodes as well as this render's
|
|
85
|
+
|
|
86
|
+
A change that replaces an element paints over the old element's box and the new
|
|
87
|
+
one's, and `attributeRegions` read only one snapshot. On todomvc's broken toggle,
|
|
88
|
+
71% of the changed region is inside the checkbox that left and 88.75% inside the
|
|
89
|
+
div that arrived. Neither reaches the 0.9 containment, so the region named the
|
|
90
|
+
row around both.
|
|
91
|
+
|
|
92
|
+
`AttributionOptions.before` is now required: the snapshot of the revision the
|
|
93
|
+
pixels are compared against, or `undefined`. Each node is joined against the
|
|
94
|
+
area its own box and the box of the node the differ's matcher pairs it with in
|
|
95
|
+
`before` cover together: the two places, not the rectangle that holds both.
|
|
96
|
+
A box that left is placed by where it was in its own image, whose corner is the
|
|
97
|
+
new `AttributionOptions.beforeOrigin`: by default the before snapshot's root, so
|
|
98
|
+
a subject the page moved between two clipped shots joins where its pixels are.
|
|
99
|
+
A call that states `origin` with a `before` snapshot and no `beforeOrigin`
|
|
100
|
+
throws, since a corner guessed from the other image is right only when the page
|
|
101
|
+
did not move.
|
|
102
|
+
|
|
103
|
+
A baseline keeps that snapshot. `Raster.snapshot` is a `PackedSnapshot`, the
|
|
104
|
+
tree gzipped and in base64, written by the Playwright fixture, its promotion,
|
|
105
|
+
the Vitest browser provider and the CLI's image sidecars. `packSnapshot` and
|
|
106
|
+
`unpackSnapshot` are exported from `@variance-authority/observe`. Every store
|
|
107
|
+
carries it as text: `sidecarFrom` keeps it, `variance push` forwards it, and
|
|
108
|
+
tribunal stores it in new `baselines.snapshot` and
|
|
109
|
+
`build_subjects.candidate_snapshot` columns (schema 21, migration
|
|
110
|
+
`0019_baseline-snapshot.sql`) and promotes it on approval. On
|
|
111
|
+
`cases/storybook-case` it adds 3.2–5.4 KB to each sidecar.
|
|
112
|
+
|
|
113
|
+
A baseline without a snapshot says so. When a changed verdict has regions and
|
|
114
|
+
its baseline kept no snapshot, or one that will not unpack, the reason ends
|
|
115
|
+
*the baseline records no snapshot of its nodes, so regions were joined against
|
|
116
|
+
this render's boxes alone: an accepting run records one on a revision where
|
|
117
|
+
this subject is unchanged*. An accepting run — `--update-snapshots`, Vitest's
|
|
118
|
+
`--update`, `variance accept` — now re-stamps an unchanged baseline that kept no
|
|
119
|
+
snapshot, as it re-stamps one painted under another identity, and the
|
|
120
|
+
observation marks it `signals.snapshot: 'unrecorded'`; `restamps` in
|
|
121
|
+
`@variance-authority/core/judge` is the one rule all three ask. Every other
|
|
122
|
+
verdict the mark is on ends *the baseline records no snapshot of its nodes, and
|
|
123
|
+
an accepting run records this one*. Two images
|
|
124
|
+
compared directly get the same clause, naming the before image, without the
|
|
125
|
+
remedy, and are never marked.
|
|
126
|
+
|
|
127
|
+
A subject whose document has not moved settles on its digest and builds no
|
|
128
|
+
observation to mark, so `settle` now renders a pictured baseline that kept no
|
|
129
|
+
snapshot when this run collected one, with the reason *the baseline records no
|
|
130
|
+
snapshot of its nodes, so this run renders it for an accepting run to record
|
|
131
|
+
one*. A suite of baselines accepted before this release renders them on every
|
|
132
|
+
run until an accepting run re-stamps them. Tribunal records one on ingest
|
|
133
|
+
instead: a pushed candidate's snapshot is written onto a baseline that has an
|
|
134
|
+
image and kept none, when it was painted from the same document under the same
|
|
135
|
+
identity, and never over one it kept, so such a subject renders once. The
|
|
136
|
+
snapshot's runtime fields (`provenance` and its `createdBy`, `wiring`, `holding`,
|
|
137
|
+
`styleProvenance`, `styleTokens`, `ignoredBy`) are then the recording run's: the
|
|
138
|
+
first push, from a build on any branch, that drew that document, as they are the
|
|
139
|
+
accepting run's on a re-stamp. `Described.keepsSnapshot`
|
|
140
|
+
is the new required field it reads:
|
|
141
|
+
the durable, LFS and tribunal stores derive it from the sidecar with
|
|
142
|
+
`keepsSnapshot` from `@variance-authority/core/format`, and the remote store
|
|
143
|
+
refuses a description without it. A packed snapshot now opens with a `v1:`
|
|
144
|
+
header naming its format version, `PACKED_SNAPSHOT_HEADER`, and a snapshot
|
|
145
|
+
without it counts as not kept. `unpackSnapshot` reads one as not recorded
|
|
146
|
+
unless every node in it is one, with `isSemanticSnapshot`, new in
|
|
147
|
+
`@variance-authority/core/format` beside `isRenderDocument` and
|
|
148
|
+
`isSubjectRef`: the checks `@variance-authority/unit-test` already made of a
|
|
149
|
+
capture file's snapshot, document and subject, which it now asks. Tribunal serves API 6, which describes a
|
|
150
|
+
baseline with `keepsSnapshot`; `variance push` against an older deployment names
|
|
151
|
+
the refused descriptions when the project's `remote` baselines are stored there,
|
|
152
|
+
and the remote store's refusal names tribunal before API 6 and a
|
|
153
|
+
`serveRasterStore` from an earlier `@variance-authority/remote`: upgrade the
|
|
154
|
+
server, tribunal or your own, before the client. `settle` takes this run's snapshot as a
|
|
155
|
+
fifth argument. `variance accept` and the MCP changelog call these subjects
|
|
156
|
+
*unchanged and re-stamped* rather than re-stamped under this run's identity,
|
|
157
|
+
which was untrue of them. The render cache no longer hands on a snapshot an
|
|
158
|
+
entry kept.
|
|
159
|
+
- 18c70d3: A run collects in parallel by default, and the report says where each subject's time went
|
|
160
|
+
|
|
161
|
+
Without `workers` in the config, a run collected in one browser world, so a
|
|
162
|
+
route suite read its pages one after another on any machine. It now collects in
|
|
163
|
+
half the machine's logical cores, at least one and never more worlds than there
|
|
164
|
+
are files: two on a 4-vCPU CI runner, eight on a 16-core laptop. A `concurrency`
|
|
165
|
+
you set holds it lower, since every world keeps a page and a subject in flight.
|
|
166
|
+
A `workers` you set still wins over both. Set `workers: 1` for a server under
|
|
167
|
+
test that cannot take concurrent pages. A collector with no `openWorker` still
|
|
168
|
+
collects in one world, and the run warns about it only when you set `workers`. Verdicts and the order
|
|
169
|
+
of observations do not depend on the count, and the report's new `workers` field
|
|
170
|
+
records it. `variance collect` takes the same width.
|
|
171
|
+
|
|
172
|
+
A changed subject read in a second world is now run alone like one read in the
|
|
173
|
+
first. A worker opens no clean world of its own, so the run asked the worker and
|
|
174
|
+
reported "not re-run alone: collector has no `collectAlone`"; it now asks the
|
|
175
|
+
collector that can open one.
|
|
176
|
+
|
|
177
|
+
The `alone.limit` budget now goes to the first subjects to change in the order
|
|
178
|
+
the run takes them. It went to whichever changed subjects reached it first, so
|
|
179
|
+
with more changed subjects than the limit, which ones carried `again` and
|
|
180
|
+
`alone` evidence depended on the number of worlds and on timing.
|
|
181
|
+
|
|
182
|
+
Each observation in the report also carries `steps` beside `costMs`: every
|
|
183
|
+
phase the subject went through, in the order it ran, with its wall time. The
|
|
184
|
+
collector's phases are named `collect.*` (`collect.navigate`,
|
|
185
|
+
`collect.wait-for-fonts`, `collect.capture`), the run's own are `compose`,
|
|
186
|
+
`inspect`, `settle`, `baseline`, `cache.get`, `cache.put`, `decode`, `compare`,
|
|
187
|
+
`write`, `again.collect` and `alone.collect`, and a painted subject adds the
|
|
188
|
+
renderer's under `render.*`. The report's `steps` sums each phase over the
|
|
189
|
+
subjects that ran it and counts them. A phase a subject skipped is absent, not
|
|
190
|
+
zero. Timings are part of no digest, so images, records and baselines do not
|
|
191
|
+
move when only timing does.
|
|
192
|
+
|
|
193
|
+
`settleRecipe`, `stabilizeForObservation`, `compareRasters` and the raster
|
|
194
|
+
`Renderer.render` take an optional step clock and report the steps they ran on
|
|
195
|
+
it. The route page agent and `readRoute` always return the steps they took.
|
|
196
|
+
`stepClock`, `timeOn` and `totalsOf` are exported from
|
|
197
|
+
`@variance-authority/core/format`.
|
|
198
|
+
|
|
199
|
+
A run that is not sharded now reads the mainline's subject costs at every
|
|
200
|
+
width, as a sharded run always did, and takes its slowest files first. One
|
|
201
|
+
world takes its subjects in the same order as several, so the budget above
|
|
202
|
+
reaches the same subjects.
|
|
203
|
+
|
|
204
|
+
`concurrency` is split evenly across the worlds and rounded up, at least one
|
|
205
|
+
per world, so by default the raster tier is as wide as the number of worlds.
|
|
206
|
+
|
|
207
|
+
The frame above a subject carries its classes in code-unit order. Storybook
|
|
208
|
+
swaps its layout class on a page it reuses, so their order said which story the
|
|
209
|
+
page showed before, and a run in parallel lanes gave the same story a different
|
|
210
|
+
document, and a render-cache miss, with the same pixels.
|
|
211
|
+
- 9b95dc1: A shard takes whole files in every runner, and is balanced by file count without times
|
|
212
|
+
|
|
213
|
+
Under `--shard k/n`, a Playwright config wrapped by `withTestSelection` now
|
|
214
|
+
places each spec file whole in one shard, by the time it took on the last
|
|
215
|
+
recorded run, as Vitest and Jest shards are placed. Playwright's own split,
|
|
216
|
+
which under `fullyParallel` divides a file's tests between shards, does not run.
|
|
217
|
+
The reporter places the shard in `preprocess`, after `--project`, `--grep` and
|
|
218
|
+
`.only`, and prints one line on stderr:
|
|
219
|
+
`variance-authority: shard 1/2 by file count: 2 of 3 files; no times at …`.
|
|
220
|
+
A file several projects run, or `repeatEach` repeats, goes with all its copies
|
|
221
|
+
to one shard; setup and teardown projects are not placed, and a project with no
|
|
222
|
+
file in the shard is left out whole. Playwright runs a setup project in every
|
|
223
|
+
shard that needs it, so combining the shards' records refuses its files as run
|
|
224
|
+
by two shards. `PWTEST_SHARD_WEIGHTS` is ignored for placement, though
|
|
225
|
+
Playwright still validates it.
|
|
226
|
+
|
|
227
|
+
This needs Playwright 1.62 or newer. `withTestSelection` refuses a run that asks
|
|
228
|
+
for a shard, by `--shard` or by `shard` in the configuration, when the
|
|
229
|
+
configuration is evaluated, before any test starts: on an older Playwright with
|
|
230
|
+
`variance-authority: refusing shard k/n: Playwright <version> would split it
|
|
231
|
+
test by test, …; placing whole files needs Playwright 1.62 or newer`, and when
|
|
232
|
+
a `--reporter` on the command line replaces the configuration's reporters
|
|
233
|
+
without naming `@variance-authority/playwright-test/reporter`, by that
|
|
234
|
+
specifier or by a path that resolves to it. The version is the one the
|
|
235
|
+
configuration's directory resolves, from `-c`/`--config` or the working
|
|
236
|
+
directory. A configuration that names the reporter without `withTestSelection`
|
|
237
|
+
is not refused. A run with no shard runs as before.
|
|
238
|
+
|
|
239
|
+
When the record has no times, or none for the files collected, a Vitest, Jest
|
|
240
|
+
or Playwright shard prices every file the same and places them by the same
|
|
241
|
+
rule, so the shards are balanced by file count and none is left empty while
|
|
242
|
+
there are as many files as shards. The line reads
|
|
243
|
+
`shard k/n by file count: <m> of <n> files; <why>`, in place of
|
|
244
|
+
`split by the runner, by count: <why>`. A file the runner hands in more than
|
|
245
|
+
once, for each project that runs it, is placed once at its recorded time, which
|
|
246
|
+
already sums every project, with every copy in one shard.
|
|
247
|
+
|
|
248
|
+
`place` in `@variance-authority/core/shard` is where that rule lives: with
|
|
249
|
+
nothing recorded, every group weighs one and is placed longest first on the
|
|
250
|
+
least-loaded shard like a timed one, ties broken by key in code-unit order and
|
|
251
|
+
then by the lowest shard. Its `by` reads `count` rather than `checksum`, and
|
|
252
|
+
its `cost` and `load` are always present, one per group when placed by count.
|
|
253
|
+
`variance run --shard` and `variance collect --shard` place stories and routes
|
|
254
|
+
by the same rule, and a subject another shard owns reads
|
|
255
|
+
`assigned to shard k/n by count, …`.
|
|
256
|
+
|
|
257
|
+
A Jest or Vitest configuration wrapped by `withTestSelection` and evaluated
|
|
258
|
+
under `--shard` gets `passWithNoTests: true` unless it sets `passWithNoTests`
|
|
259
|
+
itself, so a shard left no file, because there are fewer files than shards,
|
|
260
|
+
passes as it does under Playwright. Vitest 4 refused such a run before any file
|
|
261
|
+
was placed. An unsharded run that finds no file still fails.
|
|
262
|
+
|
|
263
|
+
`shardOf(files, shard, times)`, `landingTimes(record, options)` and
|
|
264
|
+
`shardArgument(argv)`, which reads the `--shard k/n` a command line asks for, are exported
|
|
265
|
+
from `@variance-authority/sense/test-selection`, for a seam that places a shard
|
|
266
|
+
by the same rule.
|
|
267
|
+
|
|
268
|
+
A Vitest, Jest or Playwright shard that cannot read the record its times come
|
|
269
|
+
from now fails before any test runs, with
|
|
270
|
+
`variance-authority: the times this shard is placed by could not be read, …`
|
|
271
|
+
and the reason: a share that did not answer or answered with what does not
|
|
272
|
+
read, a record that is there and does not decode, or a CLI that throws as it
|
|
273
|
+
loads. It used to place by file count, beside shards that may have read the
|
|
274
|
+
times. A record that is not there, a share that holds none or is not
|
|
275
|
+
configured, and a CLI that is not installed still place by count. An unsharded
|
|
276
|
+
run reads no times and runs as before. `variance shards` fails the same way.
|
|
277
|
+
|
|
278
|
+
**Breaking:**
|
|
279
|
+
|
|
280
|
+
- A Vitest or Jest shard with no times is placed by file count, in code-unit
|
|
281
|
+
order of path, not by your sequencer's `shard`. Your sequencer still sorts
|
|
282
|
+
each shard.
|
|
283
|
+
- A Playwright run below 1.62 that asks for a shard is refused. Upgrade
|
|
284
|
+
Playwright, or run without `--shard`.
|
|
285
|
+
- A sharded Playwright run whose `--reporter` leaves out
|
|
286
|
+
`@variance-authority/playwright-test/reporter` is refused. Name it in
|
|
287
|
+
`--reporter` too.
|
|
288
|
+
- `variance run --shard` and `variance collect --shard` with no recorded costs
|
|
289
|
+
place each file by count, not by a checksum of the file and the shard. The
|
|
290
|
+
shards are balanced, but adding a file can move others to another shard.
|
|
291
|
+
A collection part's `assignment`, and the `assignment` in the provenance of
|
|
292
|
+
the suite index merged from it, read `count` where they read `checksum`. An
|
|
293
|
+
index that reads `checksum` still opens. `variance collect merge` refuses
|
|
294
|
+
sharded parts an earlier version cut by checksum; collect every shard again.
|
|
295
|
+
- A sharded run whose share does not answer fails rather than placing by
|
|
296
|
+
file count. Run the job again, or hand every shard the record fetched once.
|
|
297
|
+
- 402ce9f: A story's parameters are read when the story is reached, so no story file is imported before its story comes up
|
|
298
|
+
|
|
299
|
+
Your Storybook run used to read every story's `parameters.variance` before the
|
|
300
|
+
first story, through the preview's `extract()`. That imported every story file
|
|
301
|
+
up front: on a large Storybook you paid for the whole build before seeing one
|
|
302
|
+
story, and each file's first story then timed its import as 0 in `collect`.
|
|
303
|
+
|
|
304
|
+
The run now plans from `index.json` alone. When it reaches a story, it asks the
|
|
305
|
+
preview for that story's merged parameters, which imports only that story's
|
|
306
|
+
file, the import showing the story would have made anyway. That import is
|
|
307
|
+
charged to the story's `collect`, so the first story of each file carries what
|
|
308
|
+
its file costs. A story that excludes itself is still skipped and listed in the
|
|
309
|
+
report with its reason, and a story whose parameters cannot be read is read at
|
|
310
|
+
your run's `viewport` with a warning naming it. A preview too old to read one
|
|
311
|
+
story's parameters says so once per run, not once per story.
|
|
312
|
+
|
|
313
|
+
A story that declares `widths` is still read as `<story id>@<width>` at each
|
|
314
|
+
width, with its own baseline, and all its widths are read on the same page in
|
|
315
|
+
one visit: nothing is opened, booted or loaded again for a second width. A
|
|
316
|
+
run that does not read such a story, because `--subjects`, `--since` or a shard
|
|
317
|
+
left it out, because its visit failed, or because its parameters could not be read, still counts its width baselines as
|
|
318
|
+
planned rather than naming them as baselines your plan no longer holds. A
|
|
319
|
+
story that excludes itself when it is reached holds no subject of the run, so
|
|
320
|
+
every baseline it left, under its own id and at each width, is named. A
|
|
321
|
+
shard prices the story at the sum of the widths its last visit read.
|
|
322
|
+
|
|
323
|
+
If you write a collector, `collect` may now return a `Visit` instead of a
|
|
324
|
+
`Collected`: every subject the visit to the planned one produced, each with its own
|
|
325
|
+
result, each id the planned id or the planned id at a width (`<id>@<width>`),
|
|
326
|
+
and any warnings the visit raised. A visit that could not learn every subject
|
|
327
|
+
it realizes, such as a story read without its parameters, says `incomplete: true`.
|
|
328
|
+
A visit that produced no subject is
|
|
329
|
+
reported as a failed one, and so is a visit that produced any other id. The run and `variance collect` report each subject
|
|
330
|
+
at the position the plan gave the planned subject, and evidence parts record
|
|
331
|
+
the planned id as `of`; a merge refuses a part whose subject is neither. A
|
|
332
|
+
`--subjects` glob that names one width, such as `*@375`, now reaches the story
|
|
333
|
+
it comes from, and only a width suffix extends a story id: `button--primary`
|
|
334
|
+
no longer reaches `button--primary-large`.
|
|
335
|
+
|
|
336
|
+
`@variance-authority/core/format` exports `atWidth`, which writes a subject's
|
|
337
|
+
id and name at a width, and `widenedFrom`, which reads the planned id back
|
|
338
|
+
from a width id, and `realizes`, which says whether a visit to one id may
|
|
339
|
+
produce another.
|
|
340
|
+
|
|
341
|
+
`readParameters(page)` is now `readParameters(page, storyId)` and returns one
|
|
342
|
+
story's parameters; the up-front read of every story is gone.
|
|
343
|
+
- f18c00c: The differ pairs the one element its author replaced, and says what it became
|
|
344
|
+
|
|
345
|
+
When, under one matched parent, exactly one element left and exactly one
|
|
346
|
+
arrived, and the same component's `createdBy` wrote both, `matchTrees` pairs
|
|
347
|
+
them. The pair is compared like any other and leads with a new `tag-changed`
|
|
348
|
+
delta, in the geometry band and counted as structural, followed by whatever else
|
|
349
|
+
differs: role, name, state, attributes, styles and box. Before, the same change
|
|
350
|
+
was a `node-removed` and a `node-added`. Two or more replaced by one author under
|
|
351
|
+
one parent stay unpaired, because either could be either, and so do two that
|
|
352
|
+
each carry an alias or a role and name of their own: every `<Button>` is created
|
|
353
|
+
by `Button`, and a Save button replaced by an Export link is two elements.
|
|
354
|
+
|
|
355
|
+
`tag-changed` is reported on every pair across two tags, so a pair that a
|
|
356
|
+
declared alias or a shared role and name joined — a `<button>` and a
|
|
357
|
+
`<div role="button">` with one name — now says its tag changed, where before it
|
|
358
|
+
reported nothing for it.
|
|
359
|
+
|
|
360
|
+
A `style-changed` or `token-changed` delta on a pair across two tags is banded
|
|
361
|
+
`geometry`, with the `tag-changed` it follows, rather than `token`: the style is
|
|
362
|
+
the new element's, and the structure a `token` delta says held did not. A
|
|
363
|
+
`bands: ['token']` ignore rule no longer reads it. `bandOf` owns the rule, as
|
|
364
|
+
`bandOf(kind, { retagged })`, and a parting row's bands now read each delta's
|
|
365
|
+
own band, so the row and the docket agree.
|
|
366
|
+
- 4aa7b16: Content inserted into a page is the inserted rows, not everything under them
|
|
367
|
+
|
|
368
|
+
Two images of the same width are compared row by row after their rows are
|
|
369
|
+
aligned: each row is hashed, runs of equal rows are one token, and Myers finds
|
|
370
|
+
the shortest edit between the two sequences. Each after row is then compared
|
|
371
|
+
with the before row that shows the same content, and `pixelmatch` still judges
|
|
372
|
+
every pairing; a row that moved, came or went is a change in itself, so no
|
|
373
|
+
pairing makes a page read as unchanged. A section added to a long page marks that
|
|
374
|
+
section, and the content it pushed down matches. A pairing is kept only when it
|
|
375
|
+
leaves fewer pixels differing than position does, a row it leaves unpaired
|
|
376
|
+
charged in full, so a line that changed in place is not shifted against its
|
|
377
|
+
neighbours. The rows a gap grew by are inserted, as the rows it lost are
|
|
378
|
+
removed, so a button whose padding grew has a region where it grew. Images of
|
|
379
|
+
different widths, or an edit longer than 2,000 runs of rows, are compared
|
|
380
|
+
position by position on the union box, as before.
|
|
381
|
+
|
|
382
|
+
An inserted row is marked edge to edge, since nothing says where its content
|
|
383
|
+
begins, so a region inside one is joined by its rows rather than its area: to
|
|
384
|
+
the tightest box whose rows span it. `attributeRegions` takes the inserted rows
|
|
385
|
+
as `inserted`. Joined by area, every such band went to the subject's root, and
|
|
386
|
+
a card whose button grew named the card rather than the button.
|
|
387
|
+
|
|
388
|
+
`RasterComparison.alignment` carries the pairing as `RowAlignment`: the after
|
|
389
|
+
rows `inserted`, the before rows `removed` with the after row they left from,
|
|
390
|
+
and the rows `shifted` with how far. A comparison without it was not aligned.
|
|
391
|
+
|
|
392
|
+
Content that moved is a change. A gap that grew in one place and shrank in
|
|
393
|
+
another moves what lies between them and leaves the page as tall as it was, and
|
|
394
|
+
every moved row matches the row it came from; such a subject reports `changed`,
|
|
395
|
+
never `unchanged` or `ignored`. The reason names the edit:
|
|
396
|
+
*120 pixels differ across 1 region; 3 pixel rows inserted in 1 place, moving
|
|
397
|
+
the content under them by up to 3 rows, and the subject resized from 40×5 to
|
|
398
|
+
40×8*. The Playwright fixture's evidence now writes a diff for a subject that
|
|
399
|
+
resized, drawn on the after image.
|
|
400
|
+
- 16e7c76: `dominatorsOf` is removed from `@variance-authority/core/relate`
|
|
401
|
+
|
|
402
|
+
Nothing ran it once distill read its records in the sense addon, which finds
|
|
403
|
+
the import every path from a test file runs through with its own dominator
|
|
404
|
+
pass. A caller that needs immediate dominators of a file graph computes them
|
|
405
|
+
over `Relations` directly.
|
|
406
|
+
|
|
407
|
+
### Patch Changes
|
|
408
|
+
|
|
409
|
+
- 8d1bcc1: A link in a doc comment opens from `node_modules`
|
|
410
|
+
|
|
411
|
+
Doc comments linked to documentation by repository paths such as
|
|
412
|
+
`../../../../docs/flakiness.md`, which resolve to nothing in an installed
|
|
413
|
+
package. They now link to the page on variance-authority.dev, or to the file on
|
|
414
|
+
GitHub when the site does not publish it, so the link works on hover in your
|
|
415
|
+
editor and in what `variance ask symbol` prints.
|
|
416
|
+
|
|
417
|
+
The MCP server's instructions and its observability answer linked to
|
|
418
|
+
`/start-cli` and `/presentation`, which the site does not serve. They now link
|
|
419
|
+
to `/start/cli` and `/docs/presentation`, and the `@variance-authority/cli`,
|
|
420
|
+
`@variance-authority/help`, `@variance-authority/mcp` and
|
|
421
|
+
`@variance-authority/vitest-browser` READMEs link to the guides where the site
|
|
422
|
+
publishes them.
|
|
423
|
+
|
|
424
|
+
`variancePrecondition`, `wiringOf`, `tapCommits` and `provenanceOf` carry an
|
|
425
|
+
`@see` link to the section of the guide that documents them.
|
|
426
|
+
|
|
427
|
+
## 0.15.0
|
|
428
|
+
|
|
429
|
+
### Minor Changes
|
|
430
|
+
|
|
431
|
+
- 8dbc834: The components a module declares are read from its code. A declaration written
|
|
432
|
+
in a block comment, a JSDoc example or a template literal no longer names a
|
|
433
|
+
component, so a commented-out `function Retired()` no longer makes its file
|
|
434
|
+
the one that declares `Retired`. A name is declared by a statement of the
|
|
435
|
+
module itself: a function, class or binding inside a function body belongs to
|
|
436
|
+
that function and no longer counts. An `export async function Page()`, an
|
|
437
|
+
abstract class, a generator, a second name in `const A = 1, B = 2`, and a name
|
|
438
|
+
that starts with a non-ASCII capital or carries a `$` are now declared too.
|
|
439
|
+
|
|
440
|
+
`variance run`, `variance collect`, the Storybook collector and the route
|
|
441
|
+
collector resolve a component to its `file:line` from the same reading, through
|
|
442
|
+
`indexDeclarations`, which `sense` now exports. What that changes in the index
|
|
443
|
+
they build from `source.dirs`:
|
|
444
|
+
|
|
445
|
+
- The line is the first line of the declaring statement. A decorated class
|
|
446
|
+
sits at its first decorator, or at its `export` when the decorator is written
|
|
447
|
+
above the `export`, and a second name in a multi-line `const` at the line of
|
|
448
|
+
the `const`.
|
|
449
|
+
- `.d.ts` files, and test, spec and story files your own `exclude` lets
|
|
450
|
+
through, declare nothing.
|
|
451
|
+
- A file the parser cannot read declares nothing, where the text scan found
|
|
452
|
+
the components in it. That covers a Flow-annotated `.js` file, and a `.vue`,
|
|
453
|
+
`.svelte` or `.mdx` file you add to a collector's `extensions`, which is
|
|
454
|
+
parsed as TSX and fails. The index built from `source.dirs` or a collector's
|
|
455
|
+
`source` does not say so: the file's components are missing, and nothing
|
|
456
|
+
names the file. Only `sense`'s source index, for a file it records, names the
|
|
457
|
+
parse error in that file's record.
|
|
458
|
+
- Building the index needs `sense`'s native addon, which ships for macOS on
|
|
459
|
+
arm64, Linux on x64 and arm64 with glibc, and Windows on x64. On any other
|
|
460
|
+
machine, including an Intel Mac, an Alpine image and Windows on arm64, a
|
|
461
|
+
configured `source.dirs` fails at its first file and names why the addon did
|
|
462
|
+
not load, where the text scan ran anywhere. The route collector depends on
|
|
463
|
+
`sense` for it.
|
|
464
|
+
|
|
465
|
+
`indexSource` is removed from `@variance-authority/core/attribute`. Build a
|
|
466
|
+
`SourceIndex` with `indexDeclarations` from `@variance-authority/sense`, or
|
|
467
|
+
write one as plain data: a map from component name to `{ file, line, via }`.
|
|
468
|
+
`SourceRef['via']` loses `'declared'`, which nothing produced, and
|
|
469
|
+
`readCapture` in `@variance-authority/unit-test` refuses a capture whose source
|
|
470
|
+
index carries it.
|
|
471
|
+
|
|
472
|
+
The source index format moves to version 18, so an index written before this
|
|
473
|
+
release is rebuilt once instead of keeping the names it read from comments.
|
|
474
|
+
- 6755276: `variance distill --file <path>` lists each module no case entered under the
|
|
475
|
+
import that made the test file load it: the topmost import every path from the
|
|
476
|
+
test file to the module runs through, with nothing behind it that a case
|
|
477
|
+
entered, often in a module the test used rather than in the test file. A module
|
|
478
|
+
two imports reach is listed as shared, with the nearest file every path to it
|
|
479
|
+
runs through; one no static import reaches, such as a dynamic import's, is
|
|
480
|
+
listed apart. The file graph is read from the checkout only when the reading
|
|
481
|
+
has a module no case entered. A module the file mocked with a factory was never
|
|
482
|
+
evaluated, and its import is not walked.
|
|
483
|
+
|
|
484
|
+
`@variance-authority/core/relate` exports `dominatorsOf`, each node's immediate
|
|
485
|
+
dominator from a root. `distillFile` takes an optional `imports` lookup and
|
|
486
|
+
sets `cause` on each module no case entered; `@variance-authority/distill`
|
|
487
|
+
exports `LoadCause`.
|
|
488
|
+
- b065364: `::before` and `::after` are read as children of the element they hang from
|
|
489
|
+
|
|
490
|
+
Restyling or rewording a `::before` or `::after` box used to repaint the page
|
|
491
|
+
and move no hash: the rules written for it were matched against the element,
|
|
492
|
+
failed, and were dropped from both the snapshot and the render document. A
|
|
493
|
+
generated box now enters the capture as a child node tagged `::before` (first)
|
|
494
|
+
or `::after` (last), carrying the rules written for it. In a browser it carries
|
|
495
|
+
its computed style, projected onto the allowlist, and the text its `content`
|
|
496
|
+
says as a `#text` child; a box whose `content` is `none` or `normal`, or whose
|
|
497
|
+
`display` is `none`, is not in the tree. Strings are joined and `attr()` reads
|
|
498
|
+
the element's attribute; a part such as `counter()` or `open-quote` is carried
|
|
499
|
+
as written.
|
|
500
|
+
|
|
501
|
+
Under JSDOM, which computes no generated content, the declared rules answer,
|
|
502
|
+
picked by the same cascade order normalization uses: a box is in the tree when
|
|
503
|
+
the winning `content` gives it one and the winning `display` is not `none`, with the rules that style it and no computed style.
|
|
504
|
+
When that `content` is strings and `attr()`, the box says its words as a
|
|
505
|
+
`#text` child, as in a browser, so rewording it moves the snapshot. When it
|
|
506
|
+
holds a `counter()`, a quote, a `var()` or a `url()`, the box has no text and
|
|
507
|
+
carries `unread` on `RawNode` and `SemanticNode`, naming the parts it could not
|
|
508
|
+
resolve; `unread` is outside every hash. JSDOM's own parser drops a `content`
|
|
509
|
+
that is a single `attr()` or `counter()` and nothing else, so no box is read
|
|
510
|
+
for one.
|
|
511
|
+
|
|
512
|
+
This is part of `RULESET_VERSION` `r2`, the same `r2` that keeps the served
|
|
513
|
+
port out of a render. A subject with a `::before` or `::after` box has a new
|
|
514
|
+
render document, so after this upgrade its image shows the box and reads
|
|
515
|
+
`changed`; `variance accept --all` and Playwright's
|
|
516
|
+
`--update-snapshots=changed` adopt it.
|
|
517
|
+
- 1a0b845: A subject is read with nothing hovered, nothing focused, motion reduced and `Date` fixed
|
|
518
|
+
|
|
519
|
+
Every layout and raster baseline made before this release is compared under a
|
|
520
|
+
moved recipe once; a semantic baseline, read in jsdom, does not move. The
|
|
521
|
+
default recipes gained tricks, so the stabilization digest every reading records
|
|
522
|
+
has changed, and each subject names that recipe as the cause of whatever it
|
|
523
|
+
reports. Review the changes and adopt them with `variance accept --all`, as for
|
|
524
|
+
any recipe change.
|
|
525
|
+
|
|
526
|
+
The standard collection recipe now also holds:
|
|
527
|
+
|
|
528
|
+
- `no-hover`: the pointer is moved to `(-1, -1)`, off the page, once per page
|
|
529
|
+
before it loads. On the `variance` fixture's page it is moved again before
|
|
530
|
+
every observation, a frame each, and not put back: a menu a script opened on
|
|
531
|
+
hover is closed for your test's next step.
|
|
532
|
+
- `no-focus`: whatever holds focus is blurred before the subject is read,
|
|
533
|
+
including an element inside a shadow root. On a page your test or agent goes
|
|
534
|
+
on using, focus goes back to that element once the reading and any screenshot
|
|
535
|
+
of it are taken, so a test types where it left off. The blur still ran the
|
|
536
|
+
page's blur handlers, and what they did, such as a validate-on-blur error,
|
|
537
|
+
stays.
|
|
538
|
+
- `reduced-motion`: the browser reports `prefers-reduced-motion: reduce`, which
|
|
539
|
+
stops the script animations of a page that honours it, and the reading
|
|
540
|
+
declares the same preference among the conditions its `@media` rules are
|
|
541
|
+
resolved against. A `prefers-reduced-motion` condition you declare through
|
|
542
|
+
`createHarness`'s `features` wins, and the browser is asked for that value;
|
|
543
|
+
a declared `no-preference` is not recorded as `reduced-motion`. Rules under
|
|
544
|
+
`@media (prefers-reduced-motion: no-preference)` are not read by default;
|
|
545
|
+
leave `reduced-motion` out to read them.
|
|
546
|
+
- `freeze-clock`: `Date` reads one instant. A script added before the page's
|
|
547
|
+
own replaces `new Date()`, `Date()` and `Date.now()`, and gives the same
|
|
548
|
+
instant to `Intl.DateTimeFormat`'s `format()` and `formatToParts()` called
|
|
549
|
+
without a date. Timers, animation frames and `performance.now()` are the
|
|
550
|
+
browser's own, so a page that settles on a timer still settles. No Playwright
|
|
551
|
+
clock is installed, so `page.clock` stays yours, and a clock your test
|
|
552
|
+
installs replaces the fixed `Date`; the reading still records `freeze-clock`,
|
|
553
|
+
so leave it out of the list when your test owns the clock. `Temporal.Now` and
|
|
554
|
+
`Date` inside a worker are not fixed. The default instant is in the past, and
|
|
555
|
+
the browser's cookie store keeps real time: a cookie your page's script sets
|
|
556
|
+
with an expiry counted from `Date.now()` is already expired and is not
|
|
557
|
+
stored, so a dismissed consent banner can come back after the next
|
|
558
|
+
navigation. Set `varianceNow` or `now` near today, or leave `freeze-clock`
|
|
559
|
+
out.
|
|
560
|
+
|
|
561
|
+
The render and layout recipes gain `no-focus` alone, because a painted document
|
|
562
|
+
still honours a collected `autofocus`. The other three have nothing to act on
|
|
563
|
+
there: the date the page printed is already text in the document, and its
|
|
564
|
+
`@media` rules were resolved when it was read. A `<script>` inside a subject
|
|
565
|
+
root is carried into the render document and runs there under the real clock.
|
|
566
|
+
Every document is painted under the render recipe, so a reading taken without
|
|
567
|
+
`no-focus` is still painted blurred.
|
|
568
|
+
|
|
569
|
+
The instant defaults to `DEFAULT_NOW`, `2024-06-15T12:00:00.000Z`, exported
|
|
570
|
+
from `@variance-authority/core/format`. `routeCollector` and
|
|
571
|
+
`storybookCollector` take a `now` option to choose another. It must be an ISO
|
|
572
|
+
8601 instant with its zone; anything else is refused before a browser opens.
|
|
573
|
+
An instant other than the default is spelled in the clock's id,
|
|
574
|
+
`freeze-clock@<instant>`, so it is part of the environment key, and a baseline taken at one instant is compared
|
|
575
|
+
with a run at another with the moved instant named as the cause.
|
|
576
|
+
|
|
577
|
+
To read a subject hovered, focused, animated or at the real time, pass your own
|
|
578
|
+
`stabilize` list without that trick: `DEFAULT_STABILIZE`, the ids of the
|
|
579
|
+
default recipe, filtered, as in
|
|
580
|
+
`DEFAULT_STABILIZE.filter((id) => id !== 'freeze-clock')`. It is exported from
|
|
581
|
+
`@variance-authority/core/format`, `@variance-authority/route-collector`,
|
|
582
|
+
`@variance-authority/storybook-collector` and
|
|
583
|
+
`@variance-authority/playwright-test`. `storybookCollector` and `observe` in
|
|
584
|
+
`@variance-authority/playwright-test` now take `stabilize`, as `routeCollector`
|
|
585
|
+
already did.
|
|
586
|
+
|
|
587
|
+
A trick needs a browser driver to apply it, and is recorded only where one did.
|
|
588
|
+
The collectors apply all four. So does the `variance` fixture in
|
|
589
|
+
`@variance-authority/playwright-test`: it sets `reduced-motion` and
|
|
590
|
+
`freeze-clock` on the `page` it hands your test, before the test body runs, and
|
|
591
|
+
takes the pointer off the page and releases focus before every observation,
|
|
592
|
+
giving focus back after it. Two new fixture options, `varianceStabilize` and
|
|
593
|
+
`varianceNow`, choose what it sets and the instant, and are set with
|
|
594
|
+
`test.use`; `varianceDriven` holds the ids it set. A fixed `Date` stops code
|
|
595
|
+
that times itself with `Date.now()`: a debounce or a throttle, such as lodash's
|
|
596
|
+
`debounce`, sees no time pass and never fires, so a test that types into a
|
|
597
|
+
debounced search box can stop getting results after this upgrade. A loop that
|
|
598
|
+
waits for `Date.now()` to pass a mark hangs the page, an animation timed by
|
|
599
|
+
`Date`, such as jQuery's `animate`, never calls its `complete`, and a client
|
|
600
|
+
that checks a token's `nbf` or `iat` against `Date.now()` can refuse a token
|
|
601
|
+
issued today. None of these failures names the clock. Leave
|
|
602
|
+
`freeze-clock` out for such a test, and for one that sets `page.clock` itself:
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
import { DEFAULT_STABILIZE } from '@variance-authority/playwright-test';
|
|
606
|
+
|
|
607
|
+
test.use({ varianceStabilize: DEFAULT_STABILIZE.filter((id) => id !== 'freeze-clock') });
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
On a page the fixture never handed your test,
|
|
611
|
+
such as one opened from your own browser context, call the new
|
|
612
|
+
`driveBeforeTest(page)` before `page.goto()` to set the same two; `observe` and
|
|
613
|
+
`createVariance` there apply `no-hover` and `no-focus`.
|
|
614
|
+
`@variance-authority/vitest-browser` reads from inside your page and has no
|
|
615
|
+
driver; `@variance-authority/presentation` reads through the Playwright `Page`
|
|
616
|
+
you hand it, a test's or an agent's live browser, and does not drive it. Both
|
|
617
|
+
apply `no-focus` only, giving focus back once the reading is taken. A trick left out is absent from the
|
|
618
|
+
reading's ids and digest, as a trick a tier cannot reach already is.
|
|
619
|
+
`stabilizeForObservation` in `@variance-authority/dom` takes `driven`, the ids
|
|
620
|
+
your driver applied, and returns `refocus()`, which gives focus back to the
|
|
621
|
+
element the reading released. `stabilizeAsRequested` takes the recipe by id, as
|
|
622
|
+
a page agent receives it. `createHarness` in `@variance-authority/playwright`
|
|
623
|
+
takes a `stabilize` recipe to drive its page before the first navigation, and
|
|
624
|
+
reports the ids it applied as `driven`. `createObservedHarness` opens one with
|
|
625
|
+
`observeNetwork` started before its first navigation, as both collectors do.
|
|
626
|
+
|
|
627
|
+
`createPlaywrightRenderer` no longer couples `no-focus` to `waitForFonts`:
|
|
628
|
+
`waitForFonts: false` turns off the waits alone.
|
|
629
|
+
|
|
630
|
+
`@variance-authority/core/format` exports `DEFAULT_STABILIZE`, `DEFAULT_NOW`,
|
|
631
|
+
`driveBeforeLoad` and `driveBeforeRead`, which apply the driver tricks to a
|
|
632
|
+
Playwright `Page`, `forDriver`, which drops the ones a host did not apply,
|
|
633
|
+
`featuresOf`, which declares the motion preference a recipe drove, and
|
|
634
|
+
`recipeToDrive`, which resolves a `stabilize` list and checks `now` before a
|
|
635
|
+
browser opens.
|
|
636
|
+
- d432339: A page reads its clock in UTC and formats for en-US, not for the host
|
|
637
|
+
|
|
638
|
+
The harness the route and Storybook collectors open, and the pages the renderer
|
|
639
|
+
paints, took their timezone and locale from the machine. A date the page
|
|
640
|
+
formatted read one way on a laptop in Sydney and another on a runner in
|
|
641
|
+
Virginia, from one build, and the two compared against one baseline as
|
|
642
|
+
`changed`.
|
|
643
|
+
|
|
644
|
+
Both are pinned: `timezoneId` to `UTC` and `locale` to `en-US`. Declare either
|
|
645
|
+
in `variance.config.json` (`"timezoneId": "Europe/Berlin"`, `"locale":
|
|
646
|
+
"de-DE"`) and the collectors and the renderer pin that instead; an unknown
|
|
647
|
+
timezone or a malformed language tag is refused by its field.
|
|
648
|
+
`createHarness` and `createPlaywrightRenderer` take the same two options.
|
|
649
|
+
`@variance-authority/core/format` exports their type, `PageRegion`, and
|
|
650
|
+
`@variance-authority/playwright` exports `pinnedRegion`, which fills in `UTC`
|
|
651
|
+
and `en-US` for a field left out.
|
|
652
|
+
|
|
653
|
+
Both are part of the environment key, and the renderer's pair is part of
|
|
654
|
+
`RenderIdentity.rasterization`. A capture read in another timezone or language
|
|
655
|
+
is another environment, and an image painted in one is compared with a
|
|
656
|
+
baseline painted in another, with `rasterization` named as the cause of
|
|
657
|
+
whatever moved.
|
|
658
|
+
|
|
659
|
+
`@variance-authority/playwright-test` paints each test in the timezone and
|
|
660
|
+
locale its page runs in: whatever the Playwright project or `test.use` sets.
|
|
661
|
+
When neither sets a `timezoneId`, `varianceFixtures` opens the page in `UTC`
|
|
662
|
+
rather than the host's, so a laptop and a runner in UTC read it under one key;
|
|
663
|
+
Playwright Test already opens it in `en-US` when nothing sets a `locale`. That
|
|
664
|
+
reaches every test of a suite extended with `varianceFixtures`, including tests
|
|
665
|
+
that never call `variance`, so a test that asserts a date formatted in your
|
|
666
|
+
machine's timezone reads it in `UTC` after this upgrade; set `timezoneId` in
|
|
667
|
+
`use` to keep your own. A page
|
|
668
|
+
you hand to `createVariance` is read and painted in whatever its own context
|
|
669
|
+
opened it in, which is the host's timezone when that context set none: open the
|
|
670
|
+
context with `timezoneId: 'UTC'` to match. `varianceRenderer` is now a test fixture, chosen
|
|
671
|
+
by the page, and `varianceRenderers` is the worker's set of renderers, one per
|
|
672
|
+
timezone and locale; a project that overrode `varianceRenderer` at worker scope
|
|
673
|
+
overrides it at test scope instead.
|
|
674
|
+
|
|
675
|
+
**Every image painted by the Playwright renderer is compared under a moved
|
|
676
|
+
recipe once after this upgrade**, because its recipe now carries the timezone
|
|
677
|
+
and locale. Each subject names `rasterization` as the cause of what it reports,
|
|
678
|
+
and one whose pixels moved is `changed`:
|
|
679
|
+
|
|
680
|
+
- With `variance run`: the run exits `1` for each `changed` subject. Review
|
|
681
|
+
them in `variance report --format html` and adopt them with
|
|
682
|
+
`variance accept --all`. A rerun reports `unchanged`.
|
|
683
|
+
- With `@variance-authority/playwright-test`: the suite fails each `changed`
|
|
684
|
+
test, and `--update-snapshots=changed` adopts them.
|
|
685
|
+
- With `@variance-authority/vitest-browser`: `vitest --update` adopts them, as
|
|
686
|
+
`variance accept --all` does.
|
|
687
|
+
|
|
688
|
+
`@variance-authority/vitest-browser` reads no `variance.config.json` and takes no
|
|
689
|
+
timezone or locale, so it paints in `UTC` and `en-US` whatever its tab ran in,
|
|
690
|
+
and neither it nor `@variance-authority/unit-test` writes a timezone or locale
|
|
691
|
+
to the environment key.
|
|
692
|
+
- 14aefbf: Ask what one test, or one story, checks that the narrower ones inside it do
|
|
693
|
+
not.
|
|
694
|
+
|
|
695
|
+
- `variance ask test-composition --file <test> --name <words>`, and
|
|
696
|
+
`docs_test_composition`, read one recorded test as the smaller tests whose
|
|
697
|
+
regions sit inside its own, the larger tests holding it, and the regions no
|
|
698
|
+
smaller test entered: modules only it enters, and paths through a smaller
|
|
699
|
+
test's module only it takes.
|
|
700
|
+
- `variance_composition {subject}` closes with the same reading for a story:
|
|
701
|
+
the smaller stories inside it, the larger ones holding it, and what no
|
|
702
|
+
smaller story renders — components only it mounts, and components a smaller
|
|
703
|
+
story renders another way. The run carries it per subject in the report's
|
|
704
|
+
structure section; `piecesOf` in `@variance-authority/core/attribute`
|
|
705
|
+
computes it.
|
|
706
|
+
- A component mounted by more than half the suite's subjects is structure. A
|
|
707
|
+
subject that mounts a component many times now counts once, where every
|
|
708
|
+
mount used to count: a chip story is an example of the chip again when the
|
|
709
|
+
pages around it mount chips many times over.
|
|
710
|
+
- f1c1b64: A bumped package and a moved manifest are read as the repository files they
|
|
711
|
+
change, once, before anything else is asked. `beyondReach` in
|
|
712
|
+
`@variance-authority/core/relate` walks a bump through the install to the first
|
|
713
|
+
files whose runtime imports load it, and stops there: a test that entered
|
|
714
|
+
anything further along evaluated that file on the way. A moved manifest becomes
|
|
715
|
+
every file beside it. `variance select`, `variance reach` and `variance run
|
|
716
|
+
--since` hand those files on as changed whole, so the suite's `before` and the
|
|
717
|
+
record answer for them as they do for an edited file.
|
|
718
|
+
|
|
719
|
+
`before` now holds files alone. A bump of a package your setup imports runs the
|
|
720
|
+
whole suite and names the setup file, not the package. `changedBefore` takes the
|
|
721
|
+
changed files only, and `BeforeReach.packages` is gone.
|
|
722
|
+
|
|
723
|
+
The `packages` option of `narrowByJourneys`, `narrowByExecution` and
|
|
724
|
+
`selectJourneyFile` is removed: pass the files `beyondReach` returns, each with
|
|
725
|
+
no line ranges.
|
|
726
|
+
- a4f55c1: One walk carries a change to every file that loads it
|
|
727
|
+
|
|
728
|
+
`@variance-authority/core/relate` exports `carried`, the walk against the
|
|
729
|
+
arrows that `dependentsOf`, its narrowed form and test selection's two importer
|
|
730
|
+
walks now all run. You pass seeds, each with what its change carries — the
|
|
731
|
+
whole file, or the names that moved — and a step that says what one importer
|
|
732
|
+
does with what reached it. The walk owns the worklist, enters each file once
|
|
733
|
+
per name, ends on any cycle, and treats a step with no answer as the whole
|
|
734
|
+
file. A walk you write over the graph gets the same answers on cycles,
|
|
735
|
+
self-imports and re-exports as the ones the selector runs, with no loop of your
|
|
736
|
+
own.
|
|
737
|
+
- bd98c3a: `--shard` in Vitest and Jest is placed by recorded time, and `variance shards` says how many to start
|
|
738
|
+
|
|
739
|
+
A Vitest or Jest config wrapped by `withTestSelection` now places the files of
|
|
740
|
+
`--shard k/n` by the time each took on the last recorded run, rather than
|
|
741
|
+
leaving the runner to divide them by count. Each shard computes the whole
|
|
742
|
+
placement from the same record and keeps its own part: files go longest first
|
|
743
|
+
to the shard with the least time so far, and a file the record has not seen is
|
|
744
|
+
priced at the median of the ones it has. The run says on stderr which record
|
|
745
|
+
placed it and what each shard was given:
|
|
746
|
+
|
|
747
|
+
```text
|
|
748
|
+
variance-authority: shard 1/2 by the times recorded at 8e3b0f0e9028: 1 of 5 files, 9.0 s (shards 400 ms to 9.0 s)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
When the times cannot be read, the runner's own split runs instead, and the
|
|
752
|
+
line says why. A failed read never fails the run. A Jest config that names no
|
|
753
|
+
`testSequencer` finds Jest's own through Jest, so a layout that does not hoist
|
|
754
|
+
`@jest/test-sequencer`, as pnpm does not, runs as before. Under
|
|
755
|
+
`VARIANCE_AUTHORITY_SINCE`, only the files the selection keeps are placed.
|
|
756
|
+
|
|
757
|
+
`variance shards --setup <seconds>` prints how many shards a recorded suite is
|
|
758
|
+
worth, and with `--format json` a `matrix` of `k/n` strings for a CI job to
|
|
759
|
+
start. Every shard is charged its setup, so a shard is added only when it
|
|
760
|
+
shortens the wait by more than it spends, and the count stops at the slowest
|
|
761
|
+
test file, which the answer names with its slowest case. `--budget` asks for
|
|
762
|
+
the fewest shards that finish within it, `--workers` divides a shard's share
|
|
763
|
+
among its runner's workers, and `--since` leaves out what the change lets the
|
|
764
|
+
run skip, so a change that reaches no test answers `0 shards`. `--collected
|
|
765
|
+
<file>`, the runner's list of test files, counts a test file the record has not
|
|
766
|
+
seen, priced at the median.
|
|
767
|
+
|
|
768
|
+
It always answers, and names what it could not weigh, in `notes` under
|
|
769
|
+
`--format json`. Without `--setup` the count weighs no setup. A budget no
|
|
770
|
+
longer than the setup is out of reach at any count, and the answer is the count
|
|
771
|
+
that finishes soonest, with `why` set to `setup over budget`. `--since` without
|
|
772
|
+
`--collected` counts only recorded files and never answers fewer than one
|
|
773
|
+
shard, and `--at-distance` without `--since` counts the whole suite. With
|
|
774
|
+
nothing recorded it answers one shard until `--unrecorded <n>` names the count
|
|
775
|
+
to start.
|
|
776
|
+
|
|
777
|
+
`@variance-authority/core/shard` is the placement and the count, which
|
|
778
|
+
`variance run --shard` places stories and routes by too.
|
|
779
|
+
`@variance-authority/sense` exports `recordedTimes` and `fileCases`, and
|
|
780
|
+
`@variance-authority/cli` exports `suiteTimes`.
|
|
781
|
+
- af6b022: `before` moves out of `source` and is declared where the suite is. The top-level
|
|
782
|
+
`before` is what every suite rests on — a CI workflow, a `.nvmrc` — and a
|
|
783
|
+
suite's own `before`, beside its `kind`, is its runner config and its setup
|
|
784
|
+
files, each listed, since a config names its setup as a string and a string is
|
|
785
|
+
not an import. `variance select` now reads both for the suite it reads: each
|
|
786
|
+
entry is walked down the file graph, and a change to any file it reaches, a
|
|
787
|
+
`package.json` whose `exports`, `main` or `type` moved over one of those files,
|
|
788
|
+
or a bump of a package those files import, runs the whole suite and names what
|
|
789
|
+
moved. A directory in either list is walked from every file under it. `select
|
|
790
|
+
--execution` reads the same lists for the suite `--suite` names, the only one
|
|
791
|
+
declared, or every declared suite when none is named; beside a snapshot
|
|
792
|
+
`--execution`, `--suite` is refused, since both name the record. A manifest move
|
|
793
|
+
is read when the diff leaves every lockfile alone. A
|
|
794
|
+
config below the repository root inherits the root's `before`, as it inherits
|
|
795
|
+
its suites. A suite that declares nothing has nothing before its reach, and
|
|
796
|
+
`select` says so.
|
|
797
|
+
`variance run --since` reads the top-level list as it read `source.before`.
|
|
798
|
+
|
|
799
|
+
`source.before` is refused: move it to the top level.
|
|
800
|
+
- 754d04c: The port a build is served on is no part of a render
|
|
801
|
+
|
|
802
|
+
The route and Storybook collectors serve a build on a free port, and that port
|
|
803
|
+
reached three identities. An asset the page loaded was keyed by its absolute
|
|
804
|
+
URL, a `url()` in a computed style was the absolute URL the engine resolved,
|
|
805
|
+
and the document digest read both. One build served twice therefore read as two
|
|
806
|
+
environment keys and two documents: each run missed the render cache, painted
|
|
807
|
+
again, and read its document as changed against its own baseline.
|
|
808
|
+
|
|
809
|
+
An asset the page's own origin serves is keyed by its path, and the page's
|
|
810
|
+
origin is dropped from a `url()` before it is hashed. An asset another origin
|
|
811
|
+
serves keeps its absolute URL. A capture carries the page's `baseUrl` so core
|
|
812
|
+
knows which origin is the page's, and the renderer resolves a path-keyed asset
|
|
813
|
+
against the document's `baseUrl`. `documentDigest` reads the page's own base
|
|
814
|
+
and resources as paths, so one build served on two ports addresses one render
|
|
815
|
+
and the render cache hits; the document keeps them absolute for the renderer,
|
|
816
|
+
and a resource another origin serves stays absolute in the digest too.
|
|
817
|
+
|
|
818
|
+
A `@variance-authority/unit-test` capture keys its assets the same way, so a
|
|
819
|
+
`@variance-authority/vitest-browser` observation, which captures through it on
|
|
820
|
+
the port the runner had free, keeps the port out of its environment key.
|
|
821
|
+
`readCapture` reads back a capture whose asset is keyed by path, resolving the
|
|
822
|
+
key against the document's `baseUrl` as the renderer does; `assetUrl` in
|
|
823
|
+
`@variance-authority/core/format` is that one resolution.
|
|
824
|
+
|
|
825
|
+
**The document of every page served over `http` moves once after this
|
|
826
|
+
upgrade.** `documentDigest` now reads a document's base and resources without
|
|
827
|
+
the page's origin, so the digest of every document served over `http` or
|
|
828
|
+
`https` changes: every route, every story, every Vitest browser page, and every
|
|
829
|
+
Playwright page opened by URL. A moved document misses the render cache once,
|
|
830
|
+
and its image is still compared with the baseline: a page whose pixels did not
|
|
831
|
+
move reads `unchanged`, and one whose pixels did reads `changed`, which
|
|
832
|
+
`variance accept --all` and Playwright's `--update-snapshots=changed` adopt.
|
|
833
|
+
`RULESET_VERSION` moves from `r1` to `r2`. The ruleset is part of a snapshot's
|
|
834
|
+
environment key, not of the render recipe, so no image names it as a moved
|
|
835
|
+
identity; a snapshot diffed against one recorded under `r1` names the ruleset as its
|
|
836
|
+
environment root.
|
|
837
|
+
- 8187fb1: `didYouMean` and `nearest` are exported from `@variance-authority/core` and no
|
|
838
|
+
longer from `@variance-authority/mcp/tools`. Import them from
|
|
839
|
+
`@variance-authority/core`. A mistyped command or flag is refused, with the
|
|
840
|
+
nearest right one, without loading the MCP server's tools.
|
|
841
|
+
- ca7813e: `intersectionArea` is exported from `@variance-authority/core/format`
|
|
842
|
+
|
|
843
|
+
The area two `Rect`s share, zero when they only touch or do not meet. It sits
|
|
844
|
+
beside `Rect`, where the attribution already used it, so the review service can
|
|
845
|
+
ask whether two builds changed the same place.
|
|
846
|
+
|
|
847
|
+
### Patch Changes
|
|
848
|
+
|
|
849
|
+
- 85e1a40: `wait-for-fonts` gives up after 15 seconds and names the font
|
|
850
|
+
|
|
851
|
+
A web font whose request never answered kept `document.fonts.ready` pending, and
|
|
852
|
+
the capture waited forever. `wait-for-fonts` now waits at most 15 seconds, the
|
|
853
|
+
same bound as `wait-for-images`, then fails naming every font still loading:
|
|
854
|
+
its family, and the `src` that requested it when the stylesheet declaring it is
|
|
855
|
+
same-origin. A face declared in a cross-origin stylesheet is named by family
|
|
856
|
+
alone, because the page cannot read that sheet's rules.
|
|
857
|
+
- cb8a52f: A source index another release wrote is no longer reported as damaged
|
|
858
|
+
|
|
859
|
+
After an upgrade, the first command that read the source index said it was
|
|
860
|
+
damaged and read only up to its first bad segment, and in CI it refused with
|
|
861
|
+
the same words. The segments were whole: an earlier release had written them in
|
|
862
|
+
another format version. Now the reader names both versions, for example that
|
|
863
|
+
the index was written in format version 15 and this release reads version 18.
|
|
864
|
+
On a workstation it rebuilds the index and says so, and `variance index` prints
|
|
865
|
+
`source index rebuilt over one written in format version 15`. In CI the refusal
|
|
866
|
+
names both versions and asks you to run `variance index` with this release
|
|
867
|
+
before the command that reads it. `ask orient --files` over such an index says
|
|
868
|
+
which version wrote it and that `variance index` rebuilds it.
|
|
869
|
+
- ef496ad: READMEs name what the program does
|
|
870
|
+
|
|
871
|
+
The package READMEs, and the `@variance-authority/vantage` and
|
|
872
|
+
`@variance-authority/playwright-test` descriptions, no longer write a test, a
|
|
873
|
+
record or a run as something that says, asks or knows. Each sentence names what
|
|
874
|
+
the program does: a command prints, a record holds a field, a test runs or
|
|
875
|
+
covers. Where a value is filled in from configuration or a default rather than
|
|
876
|
+
recorded, the README says so. Four renamed headings change their anchors:
|
|
877
|
+
`help`'s "Where the declaration is undocumented", `playwright`'s "Where a
|
|
878
|
+
component is declared, read from the engine", `storybook`'s "What a pass sends
|
|
879
|
+
the preview" and `sense`'s "Correct what a file's text declares it imports".
|
|
880
|
+
|
|
3
881
|
## 0.14.0
|
|
4
882
|
|
|
5
883
|
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|