@variance-authority/report 0.1.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 +29 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/dist/changelog-message.d.ts +51 -0
- package/dist/changelog-message.js +244 -0
- package/dist/changelog-message.js.map +1 -0
- package/dist/changelog.d.ts +231 -0
- package/dist/changelog.js +96 -0
- package/dist/changelog.js.map +1 -0
- package/dist/cluster.d.ts +112 -0
- package/dist/cluster.js +109 -0
- package/dist/cluster.js.map +1 -0
- package/dist/composition.d.ts +248 -0
- package/dist/composition.js +33 -0
- package/dist/composition.js.map +1 -0
- package/dist/declarations.d.ts +266 -0
- package/dist/declarations.js +212 -0
- package/dist/declarations.js.map +1 -0
- package/dist/file.d.ts +28 -0
- package/dist/file.js +151 -0
- package/dist/file.js.map +1 -0
- package/dist/finding-record.d.ts +64 -0
- package/dist/finding-record.js +14 -0
- package/dist/finding-record.js.map +1 -0
- package/dist/findings.d.ts +157 -0
- package/dist/findings.js +227 -0
- package/dist/findings.js.map +1 -0
- package/dist/format.d.ts +444 -0
- package/dist/format.js +2 -0
- package/dist/format.js.map +1 -0
- package/dist/history-records.d.ts +112 -0
- package/dist/history-records.js +16 -0
- package/dist/history-records.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +168 -0
- package/dist/intent.js +214 -0
- package/dist/intent.js.map +1 -0
- package/dist/presentation-record.d.ts +66 -0
- package/dist/presentation-record.js +9 -0
- package/dist/presentation-record.js.map +1 -0
- package/dist/promotion.d.ts +86 -0
- package/dist/promotion.js +104 -0
- package/dist/promotion.js.map +1 -0
- package/dist/reach.d.ts +154 -0
- package/dist/reach.js +47 -0
- package/dist/reach.js.map +1 -0
- package/dist/variation.d.ts +58 -0
- package/dist/variation.js +2 -0
- package/dist/variation.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +48 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The suite as one graph, in the artifact.
|
|
3
|
+
*
|
|
4
|
+
* Every other part of a run report is *one subject against its baseline* — two
|
|
5
|
+
* revisions, one thing. This is the other axis, and it is the one a suite of
|
|
6
|
+
* examples has always implied and never written down:
|
|
7
|
+
*
|
|
8
|
+
* > A visual-regression example is a component built from components. The example
|
|
9
|
+
* > *is* a component, at a boundary; the same component appears again, with the
|
|
10
|
+
* > same or different props, inside larger examples.
|
|
11
|
+
*
|
|
12
|
+
* Once the boundaries are addressable, the run can say which of its examples are
|
|
13
|
+
* watching literally the same bytes, which of them disagree at one commit, and —
|
|
14
|
+
* for anything that moved — what in the run explains it. None of that needs a
|
|
15
|
+
* second render, a second image, or a store: it is a fold over digests the
|
|
16
|
+
* collection already produced.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the shapes here are smaller than the ones that produced them
|
|
19
|
+
*
|
|
20
|
+
* `@variance-authority/core`'s `Composition` carries every site of every
|
|
21
|
+
* rendering, which is one entry per component boundary per subject — tens of
|
|
22
|
+
* thousands of objects on a real suite, and a report is a file people open. What
|
|
23
|
+
* survives into the artifact is what a *sentence* needs: the names, the counts,
|
|
24
|
+
* and the subject lists that let a reader go and look. A consumer that wants the
|
|
25
|
+
* full graph recomputes it from the snapshots, which is the same bargain the
|
|
26
|
+
* report already makes about masks and PNGs.
|
|
27
|
+
*
|
|
28
|
+
* The one thing kept in full is the movement list, because it is short by
|
|
29
|
+
* construction — only components that moved — and because it is the part somebody
|
|
30
|
+
* acts on.
|
|
31
|
+
*/
|
|
32
|
+
/** A component's census entry: where it is, what it is inside, what it reads. */
|
|
33
|
+
export interface ComponentRecord {
|
|
34
|
+
readonly component: string;
|
|
35
|
+
/** Subjects holding at least one boundary of it, in plan order. */
|
|
36
|
+
readonly subjects: readonly string[];
|
|
37
|
+
/** Boundaries summed across every subject. Distinct from `subjects.length`. */
|
|
38
|
+
readonly instances: number;
|
|
39
|
+
/**
|
|
40
|
+
* Subjects whose shallowest attributed boundary is this component.
|
|
41
|
+
*
|
|
42
|
+
* The narrow example — the subject that exists to *show* this thing rather than
|
|
43
|
+
* a page that happens to contain it. Empty is a real answer and a common one: a
|
|
44
|
+
* component that appears only inside pages has no example, which is the gap a
|
|
45
|
+
* reviewer is usually looking for.
|
|
46
|
+
*/
|
|
47
|
+
readonly examples: readonly string[];
|
|
48
|
+
/** Components that enclose it somewhere, sorted. The graph, upwards. */
|
|
49
|
+
readonly within: readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* Components that **mounted** it somewhere, sorted. Usually the useful edge.
|
|
52
|
+
*
|
|
53
|
+
* `within` is where the boundary sits and this is who wrote the element, and in
|
|
54
|
+
* a real application they are mostly different: measured on
|
|
55
|
+
* `examples/todomvc`, every `Chip` is `within: ["Stack"]` and
|
|
56
|
+
* `createdBy: ["TodoFooter"]`. `TodoFooter` renders nothing but other
|
|
57
|
+
* components, so it owns no DOM node, is a boundary nowhere, and would be
|
|
58
|
+
* absent from this graph entirely if only `within` were recorded — while being
|
|
59
|
+
* the file a reviewer has to open.
|
|
60
|
+
*
|
|
61
|
+
* Empty on a production build, where React's `_debugOwner` is gone. Empty is
|
|
62
|
+
* *not* "nothing mounted it".
|
|
63
|
+
*/
|
|
64
|
+
readonly createdBy: readonly string[];
|
|
65
|
+
/** Components it encloses somewhere, sorted. The graph, downwards. */
|
|
66
|
+
readonly renders: readonly string[];
|
|
67
|
+
/** Custom properties its own nodes resolve through, anywhere in the suite. */
|
|
68
|
+
readonly tokens: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Distinct props digests it was rendered with, and distinct renderings it
|
|
71
|
+
* produced.
|
|
72
|
+
*
|
|
73
|
+
* Two numbers rather than the classes themselves, because the classes are
|
|
74
|
+
* digests and a digest tells a human nothing. What the pair says is worth a
|
|
75
|
+
* line: `variants: 4, renderings: 4` is a component whose output is a function
|
|
76
|
+
* of its input, and `variants: 1, renderings: 3` is one whose output is not —
|
|
77
|
+
* which is what `divergences` is about.
|
|
78
|
+
*
|
|
79
|
+
* `variants` counts the unknown-props class as one when it occurred, because
|
|
80
|
+
* instances with no provenance are a group this report can describe and not one
|
|
81
|
+
* it may join on.
|
|
82
|
+
*/
|
|
83
|
+
readonly variants: number;
|
|
84
|
+
readonly renderings: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* One rendering, several subjects. The dots, connected.
|
|
88
|
+
*
|
|
89
|
+
* The finding is a rendering that survives being mounted somewhere else — three
|
|
90
|
+
* identical chips in one list say nothing, and the same chip in a chip story and
|
|
91
|
+
* in a page footer says that a reviewer looking at two diffs is looking at one.
|
|
92
|
+
*/
|
|
93
|
+
export interface EchoRecord {
|
|
94
|
+
readonly component: string;
|
|
95
|
+
/** The joining digest: the four content digests of the boundary, together. */
|
|
96
|
+
readonly rendering: string;
|
|
97
|
+
/** At least two, in plan order. */
|
|
98
|
+
readonly subjects: readonly string[];
|
|
99
|
+
/** Boundaries, which is at least `subjects.length`. */
|
|
100
|
+
readonly sites: number;
|
|
101
|
+
/** The narrow example among them, when the suite has one. */
|
|
102
|
+
readonly example?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The same component, the same **inputs**, more than one rendering. At one commit.
|
|
106
|
+
*
|
|
107
|
+
* Not a regression — there is no baseline anywhere in it. It says the component's
|
|
108
|
+
* own inputs do not determine its output, which is either a fact about the design
|
|
109
|
+
* (a token, a theme, an ancestor's cascade) or a reading that is not repeatable.
|
|
110
|
+
* `bands` says which kind, in the vocabulary a sensitivity absorbs, so a
|
|
111
|
+
* divergence entirely inside a relaxed band can be dismissed without opening it.
|
|
112
|
+
*
|
|
113
|
+
* *Inputs*, not *props*: a props digest excludes `children`, and `divergencesOf`
|
|
114
|
+
* in `core` refuses every pair the exclusion could explain. An empty list is a
|
|
115
|
+
* real and common answer — `examples/todomvc` produces exactly zero — and it
|
|
116
|
+
* means nothing in the suite rendered two ways from one input.
|
|
117
|
+
*/
|
|
118
|
+
export interface DivergenceRecord {
|
|
119
|
+
readonly component: string;
|
|
120
|
+
readonly bands: readonly string[];
|
|
121
|
+
/**
|
|
122
|
+
* The subjects behind each distinct rendering, widest group first. At least two.
|
|
123
|
+
*
|
|
124
|
+
* Grouped rather than counted, and that is the whole record. A divergence says
|
|
125
|
+
* one input produced more than one output, and the only sentence a reader can
|
|
126
|
+
* act on is *which* subjects sit on each side of it — `price` and `receipt`
|
|
127
|
+
* render one way, `promo` renders another, so `promo` is the one to open. A
|
|
128
|
+
* count with a flat subject list beside it, which is what this was, names every
|
|
129
|
+
* subject involved and withholds the only thing distinguishing them: the reader
|
|
130
|
+
* has three names, knows two of them agree, and cannot tell which two.
|
|
131
|
+
*
|
|
132
|
+
* Deduplicated per group, because a rendering reached twice in one subject is
|
|
133
|
+
* one subject to go and look at. `renderings.length` is the count this replaced.
|
|
134
|
+
*/
|
|
135
|
+
readonly renderings: readonly (readonly string[])[];
|
|
136
|
+
/**
|
|
137
|
+
* Why each rendering after the first parted from it.
|
|
138
|
+
*
|
|
139
|
+
* The sentence, under the split. `renderings` says *promo is the odd one out*;
|
|
140
|
+
* this says *`Price` inherited a different `color` — an ancestor declared it*,
|
|
141
|
+
* which is the difference between a reader knowing where to look and knowing
|
|
142
|
+
* what they will find when they get there.
|
|
143
|
+
*
|
|
144
|
+
* Absent when the run kept no documents to read. Empty is never written.
|
|
145
|
+
*/
|
|
146
|
+
readonly partings?: readonly PartingRecord[];
|
|
147
|
+
}
|
|
148
|
+
/** One rendering of a divergence, spoken. */
|
|
149
|
+
export interface PartingRecord {
|
|
150
|
+
/** Index into {@link DivergenceRecord.renderings}, always at least 1. */
|
|
151
|
+
readonly rendering: number;
|
|
152
|
+
/** Already phrased for a reader; the first line is the triage slice. */
|
|
153
|
+
readonly lines: readonly string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Why one component moved in one subject, and what the suite held against it.
|
|
157
|
+
*
|
|
158
|
+
* The ladder is `edited` → `token` → `upstream` → `contradicted` → `unexplained`,
|
|
159
|
+
* and the last one is the finding. See
|
|
160
|
+
* [`flakiness.md`](../../../docs/flakiness.md) for what it is and is not allowed
|
|
161
|
+
* to conclude.
|
|
162
|
+
*/
|
|
163
|
+
export interface MovementRecord {
|
|
164
|
+
readonly subject: string;
|
|
165
|
+
readonly component: string;
|
|
166
|
+
/** Empty means *not known* — a name-only comparison — never *no band*. */
|
|
167
|
+
readonly bands: readonly string[];
|
|
168
|
+
readonly cause: 'edited' | 'token' | 'upstream' | 'contradicted' | 'unexplained';
|
|
169
|
+
/** One sentence, naming the evidence rather than the category. */
|
|
170
|
+
readonly because: string;
|
|
171
|
+
readonly file?: string;
|
|
172
|
+
readonly tokens?: readonly string[];
|
|
173
|
+
readonly upstream?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Components between `upstream` and this one, outermost first.
|
|
176
|
+
*
|
|
177
|
+
* Present only on the `upstream` rung, and absent when the edited component
|
|
178
|
+
* draws this one directly. A reviewer sent to `ProductCard` because a
|
|
179
|
+
* `CardFooter` moved has exactly one question next — *how does that reach
|
|
180
|
+
* this* — and the answer is a couple of names the run already walked.
|
|
181
|
+
*/
|
|
182
|
+
readonly through?: readonly string[];
|
|
183
|
+
/** Other subjects this same component moved in. The "one cause, N subjects" fold. */
|
|
184
|
+
readonly alsoIn: readonly string[];
|
|
185
|
+
/**
|
|
186
|
+
* Subjects where the same component, with the same props, did **not** move.
|
|
187
|
+
*
|
|
188
|
+
* The control group — the *stable states to refer to*. Empty means the suite
|
|
189
|
+
* offered no control, which weakens an unexplained movement rather than
|
|
190
|
+
* strengthening it, and is why this is a list and not a flag.
|
|
191
|
+
*/
|
|
192
|
+
readonly held: readonly string[];
|
|
193
|
+
/**
|
|
194
|
+
* Renders of this component with the same props, other than this one, that the
|
|
195
|
+
* run read.
|
|
196
|
+
*
|
|
197
|
+
* The denominator `held` is a numerator of. Without it an empty control group
|
|
198
|
+
* is two opposite findings wearing one shape — nothing to compare against, or a
|
|
199
|
+
* comparison made in every render and answering the same way — and a reader
|
|
200
|
+
* that guesses picks the wrong one on any suite where the change reached
|
|
201
|
+
* everything.
|
|
202
|
+
*
|
|
203
|
+
* Optional because a report written before this was carried has no answer, and
|
|
204
|
+
* `0` is an answer.
|
|
205
|
+
*/
|
|
206
|
+
readonly compared?: number;
|
|
207
|
+
/**
|
|
208
|
+
* For an unexplained movement only: whether the subject was already proven
|
|
209
|
+
* unstable in this run.
|
|
210
|
+
*
|
|
211
|
+
* `flake` means the subject failed to read the same way twice *and* nothing in
|
|
212
|
+
* the run explains what moved — the two halves of the sentence, together, for
|
|
213
|
+
* the first time. `suspect` means nobody has read it twice yet, so it is a
|
|
214
|
+
* shortlist entry and not a verdict.
|
|
215
|
+
*
|
|
216
|
+
* Absent on every explained movement, where the question does not arise.
|
|
217
|
+
*/
|
|
218
|
+
readonly standing?: 'flake' | 'suspect';
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* What one run learned by comparing its subjects to each other.
|
|
222
|
+
*
|
|
223
|
+
* Absent from a report whose collection produced no semantic snapshots — a
|
|
224
|
+
* raster-only tier has no boundaries to join, and an empty graph would read as
|
|
225
|
+
* "this suite shares nothing", which is a different and false claim.
|
|
226
|
+
*/
|
|
227
|
+
export interface CompositionReport {
|
|
228
|
+
/** Subjects that contributed a snapshot, in plan order. The denominator. */
|
|
229
|
+
readonly subjects: readonly string[];
|
|
230
|
+
/** Sorted by name, code-unit order. */
|
|
231
|
+
readonly components: readonly ComponentRecord[];
|
|
232
|
+
/** Widest first. Capped — see `truncated`. */
|
|
233
|
+
readonly echoes: readonly EchoRecord[];
|
|
234
|
+
/** Sorted by component name. */
|
|
235
|
+
readonly divergences: readonly DivergenceRecord[];
|
|
236
|
+
/** Every component the run found moved, in the order the observations came. */
|
|
237
|
+
readonly movements: readonly MovementRecord[];
|
|
238
|
+
/**
|
|
239
|
+
* What was left out of the lists above, when anything was.
|
|
240
|
+
*
|
|
241
|
+
* A cap that says nothing is a cap that reads as coverage. Present only when a
|
|
242
|
+
* list was shortened, and counts what did not make it rather than what did.
|
|
243
|
+
*/
|
|
244
|
+
readonly truncated?: {
|
|
245
|
+
readonly echoes: number;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The suite as one graph, in the artifact.
|
|
3
|
+
*
|
|
4
|
+
* Every other part of a run report is *one subject against its baseline* — two
|
|
5
|
+
* revisions, one thing. This is the other axis, and it is the one a suite of
|
|
6
|
+
* examples has always implied and never written down:
|
|
7
|
+
*
|
|
8
|
+
* > A visual-regression example is a component built from components. The example
|
|
9
|
+
* > *is* a component, at a boundary; the same component appears again, with the
|
|
10
|
+
* > same or different props, inside larger examples.
|
|
11
|
+
*
|
|
12
|
+
* Once the boundaries are addressable, the run can say which of its examples are
|
|
13
|
+
* watching literally the same bytes, which of them disagree at one commit, and —
|
|
14
|
+
* for anything that moved — what in the run explains it. None of that needs a
|
|
15
|
+
* second render, a second image, or a store: it is a fold over digests the
|
|
16
|
+
* collection already produced.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the shapes here are smaller than the ones that produced them
|
|
19
|
+
*
|
|
20
|
+
* `@variance-authority/core`'s `Composition` carries every site of every
|
|
21
|
+
* rendering, which is one entry per component boundary per subject — tens of
|
|
22
|
+
* thousands of objects on a real suite, and a report is a file people open. What
|
|
23
|
+
* survives into the artifact is what a *sentence* needs: the names, the counts,
|
|
24
|
+
* and the subject lists that let a reader go and look. A consumer that wants the
|
|
25
|
+
* full graph recomputes it from the snapshots, which is the same bargain the
|
|
26
|
+
* report already makes about masks and PNGs.
|
|
27
|
+
*
|
|
28
|
+
* The one thing kept in full is the movement list, because it is short by
|
|
29
|
+
* construction — only components that moved — and because it is the part somebody
|
|
30
|
+
* acts on.
|
|
31
|
+
*/
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.js","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG","sourcesContent":["/**\n * The suite as one graph, in the artifact.\n *\n * Every other part of a run report is *one subject against its baseline* — two\n * revisions, one thing. This is the other axis, and it is the one a suite of\n * examples has always implied and never written down:\n *\n * > A visual-regression example is a component built from components. The example\n * > *is* a component, at a boundary; the same component appears again, with the\n * > same or different props, inside larger examples.\n *\n * Once the boundaries are addressable, the run can say which of its examples are\n * watching literally the same bytes, which of them disagree at one commit, and —\n * for anything that moved — what in the run explains it. None of that needs a\n * second render, a second image, or a store: it is a fold over digests the\n * collection already produced.\n *\n * ## Why the shapes here are smaller than the ones that produced them\n *\n * `@variance-authority/core`'s `Composition` carries every site of every\n * rendering, which is one entry per component boundary per subject — tens of\n * thousands of objects on a real suite, and a report is a file people open. What\n * survives into the artifact is what a *sentence* needs: the names, the counts,\n * and the subject lists that let a reader go and look. A consumer that wants the\n * full graph recomputes it from the snapshots, which is the same bargain the\n * report already makes about masks and PNGs.\n *\n * The one thing kept in full is the movement list, because it is short by\n * construction — only components that moved — and because it is the part somebody\n * acts on.\n */\n\n/** A component's census entry: where it is, what it is inside, what it reads. */\nexport interface ComponentRecord {\n readonly component: string;\n /** Subjects holding at least one boundary of it, in plan order. */\n readonly subjects: readonly string[];\n /** Boundaries summed across every subject. Distinct from `subjects.length`. */\n readonly instances: number;\n\n /**\n * Subjects whose shallowest attributed boundary is this component.\n *\n * The narrow example — the subject that exists to *show* this thing rather than\n * a page that happens to contain it. Empty is a real answer and a common one: a\n * component that appears only inside pages has no example, which is the gap a\n * reviewer is usually looking for.\n */\n readonly examples: readonly string[];\n\n /** Components that enclose it somewhere, sorted. The graph, upwards. */\n readonly within: readonly string[];\n\n /**\n * Components that **mounted** it somewhere, sorted. Usually the useful edge.\n *\n * `within` is where the boundary sits and this is who wrote the element, and in\n * a real application they are mostly different: measured on\n * `examples/todomvc`, every `Chip` is `within: [\"Stack\"]` and\n * `createdBy: [\"TodoFooter\"]`. `TodoFooter` renders nothing but other\n * components, so it owns no DOM node, is a boundary nowhere, and would be\n * absent from this graph entirely if only `within` were recorded — while being\n * the file a reviewer has to open.\n *\n * Empty on a production build, where React's `_debugOwner` is gone. Empty is\n * *not* \"nothing mounted it\".\n */\n readonly createdBy: readonly string[];\n\n /** Components it encloses somewhere, sorted. The graph, downwards. */\n readonly renders: readonly string[];\n /** Custom properties its own nodes resolve through, anywhere in the suite. */\n readonly tokens: readonly string[];\n\n /**\n * Distinct props digests it was rendered with, and distinct renderings it\n * produced.\n *\n * Two numbers rather than the classes themselves, because the classes are\n * digests and a digest tells a human nothing. What the pair says is worth a\n * line: `variants: 4, renderings: 4` is a component whose output is a function\n * of its input, and `variants: 1, renderings: 3` is one whose output is not —\n * which is what `divergences` is about.\n *\n * `variants` counts the unknown-props class as one when it occurred, because\n * instances with no provenance are a group this report can describe and not one\n * it may join on.\n */\n readonly variants: number;\n readonly renderings: number;\n}\n\n/**\n * One rendering, several subjects. The dots, connected.\n *\n * The finding is a rendering that survives being mounted somewhere else — three\n * identical chips in one list say nothing, and the same chip in a chip story and\n * in a page footer says that a reviewer looking at two diffs is looking at one.\n */\nexport interface EchoRecord {\n readonly component: string;\n /** The joining digest: the four content digests of the boundary, together. */\n readonly rendering: string;\n /** At least two, in plan order. */\n readonly subjects: readonly string[];\n /** Boundaries, which is at least `subjects.length`. */\n readonly sites: number;\n /** The narrow example among them, when the suite has one. */\n readonly example?: string;\n}\n\n/**\n * The same component, the same **inputs**, more than one rendering. At one commit.\n *\n * Not a regression — there is no baseline anywhere in it. It says the component's\n * own inputs do not determine its output, which is either a fact about the design\n * (a token, a theme, an ancestor's cascade) or a reading that is not repeatable.\n * `bands` says which kind, in the vocabulary a sensitivity absorbs, so a\n * divergence entirely inside a relaxed band can be dismissed without opening it.\n *\n * *Inputs*, not *props*: a props digest excludes `children`, and `divergencesOf`\n * in `core` refuses every pair the exclusion could explain. An empty list is a\n * real and common answer — `examples/todomvc` produces exactly zero — and it\n * means nothing in the suite rendered two ways from one input.\n */\nexport interface DivergenceRecord {\n readonly component: string;\n readonly bands: readonly string[];\n\n /**\n * The subjects behind each distinct rendering, widest group first. At least two.\n *\n * Grouped rather than counted, and that is the whole record. A divergence says\n * one input produced more than one output, and the only sentence a reader can\n * act on is *which* subjects sit on each side of it — `price` and `receipt`\n * render one way, `promo` renders another, so `promo` is the one to open. A\n * count with a flat subject list beside it, which is what this was, names every\n * subject involved and withholds the only thing distinguishing them: the reader\n * has three names, knows two of them agree, and cannot tell which two.\n *\n * Deduplicated per group, because a rendering reached twice in one subject is\n * one subject to go and look at. `renderings.length` is the count this replaced.\n */\n readonly renderings: readonly (readonly string[])[];\n\n /**\n * Why each rendering after the first parted from it.\n *\n * The sentence, under the split. `renderings` says *promo is the odd one out*;\n * this says *`Price` inherited a different `color` — an ancestor declared it*,\n * which is the difference between a reader knowing where to look and knowing\n * what they will find when they get there.\n *\n * Absent when the run kept no documents to read. Empty is never written.\n */\n readonly partings?: readonly PartingRecord[];\n}\n\n/** One rendering of a divergence, spoken. */\nexport interface PartingRecord {\n /** Index into {@link DivergenceRecord.renderings}, always at least 1. */\n readonly rendering: number;\n /** Already phrased for a reader; the first line is the triage slice. */\n readonly lines: readonly string[];\n}\n\n/**\n * Why one component moved in one subject, and what the suite held against it.\n *\n * The ladder is `edited` → `token` → `upstream` → `contradicted` → `unexplained`,\n * and the last one is the finding. See\n * [`flakiness.md`](../../../docs/flakiness.md) for what it is and is not allowed\n * to conclude.\n */\nexport interface MovementRecord {\n readonly subject: string;\n readonly component: string;\n /** Empty means *not known* — a name-only comparison — never *no band*. */\n readonly bands: readonly string[];\n readonly cause: 'edited' | 'token' | 'upstream' | 'contradicted' | 'unexplained';\n /** One sentence, naming the evidence rather than the category. */\n readonly because: string;\n\n readonly file?: string;\n readonly tokens?: readonly string[];\n readonly upstream?: string;\n\n /**\n * Components between `upstream` and this one, outermost first.\n *\n * Present only on the `upstream` rung, and absent when the edited component\n * draws this one directly. A reviewer sent to `ProductCard` because a\n * `CardFooter` moved has exactly one question next — *how does that reach\n * this* — and the answer is a couple of names the run already walked.\n */\n readonly through?: readonly string[];\n\n /** Other subjects this same component moved in. The \"one cause, N subjects\" fold. */\n readonly alsoIn: readonly string[];\n\n /**\n * Subjects where the same component, with the same props, did **not** move.\n *\n * The control group — the *stable states to refer to*. Empty means the suite\n * offered no control, which weakens an unexplained movement rather than\n * strengthening it, and is why this is a list and not a flag.\n */\n readonly held: readonly string[];\n\n /**\n * Renders of this component with the same props, other than this one, that the\n * run read.\n *\n * The denominator `held` is a numerator of. Without it an empty control group\n * is two opposite findings wearing one shape — nothing to compare against, or a\n * comparison made in every render and answering the same way — and a reader\n * that guesses picks the wrong one on any suite where the change reached\n * everything.\n *\n * Optional because a report written before this was carried has no answer, and\n * `0` is an answer.\n */\n readonly compared?: number;\n\n /**\n * For an unexplained movement only: whether the subject was already proven\n * unstable in this run.\n *\n * `flake` means the subject failed to read the same way twice *and* nothing in\n * the run explains what moved — the two halves of the sentence, together, for\n * the first time. `suspect` means nobody has read it twice yet, so it is a\n * shortlist entry and not a verdict.\n *\n * Absent on every explained movement, where the question does not arise.\n */\n readonly standing?: 'flake' | 'suspect';\n}\n\n/**\n * What one run learned by comparing its subjects to each other.\n *\n * Absent from a report whose collection produced no semantic snapshots — a\n * raster-only tier has no boundaries to join, and an empty graph would read as\n * \"this suite shares nothing\", which is a different and false claim.\n */\nexport interface CompositionReport {\n /** Subjects that contributed a snapshot, in plan order. The denominator. */\n readonly subjects: readonly string[];\n /** Sorted by name, code-unit order. */\n readonly components: readonly ComponentRecord[];\n /** Widest first. Capped — see `truncated`. */\n readonly echoes: readonly EchoRecord[];\n /** Sorted by component name. */\n readonly divergences: readonly DivergenceRecord[];\n /** Every component the run found moved, in the order the observations came. */\n readonly movements: readonly MovementRecord[];\n\n /**\n * What was left out of the lists above, when anything was.\n *\n * A cap that says nothing is a cap that reads as coverage. Present only when a\n * list was shortened, and counts what did not make it rather than what did.\n */\n readonly truncated?: {\n readonly echoes: number;\n };\n}\n"]}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the config *declared*, and what each declaration did in this run.
|
|
3
|
+
*
|
|
4
|
+
* The two ledgers a run keeps about its own settings. An ignore says *part of
|
|
5
|
+
* this page is not my subject*; a sensitivity says *assert on this much of it*.
|
|
6
|
+
* Both make a run less observant on purpose, and both are only safe because
|
|
7
|
+
* every run counts what they absorbed — a declaration that stopped being needed
|
|
8
|
+
* is invisible without a count of zero, and a mask that outlives its cause grows
|
|
9
|
+
* quietly over a real regression.
|
|
10
|
+
*
|
|
11
|
+
* They live here rather than with the command that computes them for the reason
|
|
12
|
+
* the rest of this package exists: they have several readers. The CLI folds them,
|
|
13
|
+
* a terminal prints them, an HTML page tabulates them, and a review service
|
|
14
|
+
* stores them so the audit survives the machine the run happened on. A shape
|
|
15
|
+
* owned by the first of those bends towards a terminal.
|
|
16
|
+
*
|
|
17
|
+
* ## One decision, taken once
|
|
18
|
+
*
|
|
19
|
+
* A rule's *state* — spent, mistyped, expired, or simply working — is a reading
|
|
20
|
+
* of the counts, not another count. It is taken by {@link ignoreState} and
|
|
21
|
+
* {@link sensitivityState} and nowhere else, because a second reader that
|
|
22
|
+
* re-derived it would eventually disagree: the first HTML report to try shipped
|
|
23
|
+
* `pixels === 0` as the whole test for *dead*, which calls every rule in a fresh
|
|
24
|
+
* checkout dead, on the run that proves least about any of them.
|
|
25
|
+
*/
|
|
26
|
+
/** What one ignore rule did this run. */
|
|
27
|
+
export interface IgnoreUsage {
|
|
28
|
+
readonly rule: string;
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
/** Changed pixels this rule absorbed across the run. */
|
|
31
|
+
readonly pixels: number;
|
|
32
|
+
/** Subjects where it excluded something, whether or not it absorbed anything. */
|
|
33
|
+
readonly subjects: number;
|
|
34
|
+
/**
|
|
35
|
+
* Subjects where it excluded something **and a comparison happened**.
|
|
36
|
+
*
|
|
37
|
+
* The denominator that stops "absorbed nothing" from being an accusation. A
|
|
38
|
+
* subject that is `new`, `incomparable`, or settled from a digest compared no
|
|
39
|
+
* pixels, so an ignore over it had nothing to absorb — which says nothing at
|
|
40
|
+
* all about whether the rule is still needed. Without this a fresh checkout
|
|
41
|
+
* reported every ignore in the config as dead.
|
|
42
|
+
*/
|
|
43
|
+
readonly comparedIn: number;
|
|
44
|
+
/** Subjects where it excluded something and absorbed nothing there. */
|
|
45
|
+
readonly inertIn: number;
|
|
46
|
+
/** `true` when it never resolved to a place in any subject. */
|
|
47
|
+
readonly unresolved: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Tags the rule names that no subject in this run wears.
|
|
50
|
+
*
|
|
51
|
+
* The only defence a tag has. A misspelled *key* is refused by name because
|
|
52
|
+
* the config's objects are closed; a misspelled *tag* is a legal word that
|
|
53
|
+
* simply matches nothing, and the rule then silently applies nowhere while the
|
|
54
|
+
* operator reads their config and believes it applies somewhere. So the words
|
|
55
|
+
* nothing answered to are named, with the near-misses that were present, and
|
|
56
|
+
* an empty list is the ordinary case rather than the interesting one.
|
|
57
|
+
*/
|
|
58
|
+
readonly unwornTags: readonly string[];
|
|
59
|
+
/** `true` when it is past `until` and no longer absorbing. */
|
|
60
|
+
readonly expired: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Every ignore rule the config named, and what the run as a whole absorbed. */
|
|
63
|
+
export interface IgnoreLedger {
|
|
64
|
+
readonly rules: readonly IgnoreUsage[];
|
|
65
|
+
/**
|
|
66
|
+
* Rules that absorbed nothing anywhere this run.
|
|
67
|
+
*
|
|
68
|
+
* The list an operator is meant to act on. Named separately rather than left
|
|
69
|
+
* to be derived, because a derivation nobody writes is a report nobody reads.
|
|
70
|
+
*/
|
|
71
|
+
readonly dead: readonly string[];
|
|
72
|
+
/** Subjects whose only differences were absorbed. Green, and not `unchanged`. */
|
|
73
|
+
readonly fullyIgnored: readonly string[];
|
|
74
|
+
readonly totalPixels: number;
|
|
75
|
+
/** Every tag worn by a subject this run planned, for the near-miss hint. */
|
|
76
|
+
readonly vocabulary: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
/** What one sensitivity rule did this run. */
|
|
79
|
+
export interface SensitivityUsage {
|
|
80
|
+
readonly rule: string;
|
|
81
|
+
readonly reason: string;
|
|
82
|
+
readonly level: string;
|
|
83
|
+
/** Subjects this rule was in scope for, whether or not it absorbed them. */
|
|
84
|
+
readonly scoped: number;
|
|
85
|
+
/** Subjects whose verdict it decided. */
|
|
86
|
+
readonly absorbed: readonly string[];
|
|
87
|
+
/** Bands it absorbed, across every subject it decided. */
|
|
88
|
+
readonly bands: readonly string[];
|
|
89
|
+
/**
|
|
90
|
+
* `true` when the rule matched no subject this run planned.
|
|
91
|
+
*
|
|
92
|
+
* A different failure from absorbing nothing, and worth its own word: a rule
|
|
93
|
+
* naming `route/*` in a project whose subjects are all `story:*` is a typo,
|
|
94
|
+
* not a policy that has outlived its cause.
|
|
95
|
+
*/
|
|
96
|
+
readonly unscoped: boolean;
|
|
97
|
+
}
|
|
98
|
+
/** Every sensitivity rule the config named, and how much they relaxed. */
|
|
99
|
+
export interface SensitivityLedger {
|
|
100
|
+
readonly rules: readonly SensitivityUsage[];
|
|
101
|
+
readonly totalAbsorbed: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* What an ignore rule turned out to be, read from what it did.
|
|
105
|
+
*
|
|
106
|
+
* - `expired` — past its date. The differences it used to absorb are back.
|
|
107
|
+
* - `unworn` — it names a tag no subject in this run wears, so it applied nowhere.
|
|
108
|
+
* - `unresolved` — its selector matched no element in any subject.
|
|
109
|
+
* - `untested` — it excluded a subtree, and nothing it covered was compared.
|
|
110
|
+
* - `dead` — it excluded a subtree in subjects that *were* compared, and took nothing.
|
|
111
|
+
* - `live` — it absorbed something.
|
|
112
|
+
*
|
|
113
|
+
* The order is the order the checks run in, and it matters: an expired rule that
|
|
114
|
+
* also names a mistyped tag is expired, because that is the fact that explains
|
|
115
|
+
* the other. `untested` and `dead` are the pair worth keeping apart — the first
|
|
116
|
+
* is an absence of evidence and the second is evidence of absence, and a report
|
|
117
|
+
* that spells both *dead* tells an operator to delete a rule on the run that
|
|
118
|
+
* proves least about it.
|
|
119
|
+
*/
|
|
120
|
+
export type IgnoreState = 'expired' | 'unworn' | 'unresolved' | 'untested' | 'dead' | 'live';
|
|
121
|
+
/**
|
|
122
|
+
* What a sensitivity rule turned out to be.
|
|
123
|
+
*
|
|
124
|
+
* - `unscoped` — it matched no subject this run planned.
|
|
125
|
+
* - `dead` — it was in scope and decided nothing; nothing here needed relaxing.
|
|
126
|
+
* - `live` — it decided at least one verdict.
|
|
127
|
+
*/
|
|
128
|
+
export type SensitivityState = 'unscoped' | 'dead' | 'live';
|
|
129
|
+
/** The single reading of an ignore's counts. Every surface asks this one. */
|
|
130
|
+
export declare function ignoreState(entry: IgnoreUsage): IgnoreState;
|
|
131
|
+
/** The single reading of a sensitivity's counts. */
|
|
132
|
+
export declare function sensitivityState(entry: SensitivityUsage): SensitivityState;
|
|
133
|
+
/**
|
|
134
|
+
* Whether a state names something to do about the config.
|
|
135
|
+
*
|
|
136
|
+
* `untested` is deliberately not one. It is the absence of evidence, and a
|
|
137
|
+
* report that flagged it would ask an operator to act on a run that measured
|
|
138
|
+
* nothing — which is how audits stop being read.
|
|
139
|
+
*/
|
|
140
|
+
export declare function isActionable(state: IgnoreState | SensitivityState): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* The sentence behind an ignore's state.
|
|
143
|
+
*
|
|
144
|
+
* Here rather than in a renderer for the reason the state itself is here: an
|
|
145
|
+
* HTML table, a terminal and a review page all have to answer *why is this rule
|
|
146
|
+
* marked* and there is one answer. Two of them writing their own is how a report
|
|
147
|
+
* and a service come to disagree about the same run in front of the same person.
|
|
148
|
+
*
|
|
149
|
+
* The near-miss on `unworn` is the part worth carrying: the likeliest cause of a
|
|
150
|
+
* tag nothing wears is a typo, and naming the tags that *are* worn turns a
|
|
151
|
+
* report into a fix.
|
|
152
|
+
*/
|
|
153
|
+
export declare function ignoreSays(entry: IgnoreUsage, ledger: IgnoreLedger): string;
|
|
154
|
+
/**
|
|
155
|
+
* The run-level line under the ignore table, vocabulary included.
|
|
156
|
+
*
|
|
157
|
+
* The last clause is the one that has to be there. With no vocabulary the
|
|
158
|
+
* unworn-tag check did not run at all, and a footer that said nothing about it
|
|
159
|
+
* would read as every tag having been checked and every tag having been found.
|
|
160
|
+
*
|
|
161
|
+
* The first clause counts the rules that *absorbed*, over the rules that were
|
|
162
|
+
* declared, and the two are only the same number on a run where every rule
|
|
163
|
+
* worked. Counting declarations there put the whole run's pixels behind the size
|
|
164
|
+
* of the config: two rules, one of them matching nothing, read as *647 pixels
|
|
165
|
+
* absorbed by 2 rules* directly above a table saying one of them resolved
|
|
166
|
+
* nowhere. The table is the audit; the footer must not contradict it.
|
|
167
|
+
*/
|
|
168
|
+
export declare function ignoreTotals(ledger: IgnoreLedger): string;
|
|
169
|
+
/** The sentence behind a sensitivity's state. */
|
|
170
|
+
export declare function sensitivitySays(entry: SensitivityUsage): string;
|
|
171
|
+
/**
|
|
172
|
+
* The run-level line under the sensitivity table.
|
|
173
|
+
*
|
|
174
|
+
* Rules that decided something, over rules declared — the same reading as
|
|
175
|
+
* {@link ignoreTotals}, and for the same reason. *0 subjects not asserted on in
|
|
176
|
+
* full, by 1 rule* is a sentence with a rule in it that did nothing.
|
|
177
|
+
*/
|
|
178
|
+
export declare function sensitivityTotals(ledger: SensitivityLedger): string;
|
|
179
|
+
/**
|
|
180
|
+
* How many of the declared ignores absorbed anything, over how many were written.
|
|
181
|
+
*
|
|
182
|
+
* A phrase rather than a number because there are four footers printing it — an
|
|
183
|
+
* HTML report, a review page and two terminal summaries — and the arithmetic is
|
|
184
|
+
* the part that has to be identical between them. Whether a rule counts here is
|
|
185
|
+
* the same reading {@link ignoreState} takes, and a surface that re-derived it
|
|
186
|
+
* would eventually take a different one.
|
|
187
|
+
*/
|
|
188
|
+
export declare function ignoreShare(ledger: IgnoreLedger): string;
|
|
189
|
+
/** The same reading for sensitivities: rules that decided a verdict, over rules written. */
|
|
190
|
+
export declare function sensitivityShare(ledger: SensitivityLedger): string;
|
|
191
|
+
/**
|
|
192
|
+
* The bands a rule absorbed, or the fact that they were not kept.
|
|
193
|
+
*
|
|
194
|
+
* `undefined` is the third answer, and it is the whole reason this is a function
|
|
195
|
+
* rather than a field read. A rule that decided verdicts with no band recorded is
|
|
196
|
+
* not a rule that absorbed nothing — it is a report written before the bands were
|
|
197
|
+
* kept, or by a writer that dropped them, and an empty list says the first.
|
|
198
|
+
*/
|
|
199
|
+
export declare function absorbedBands(entry: SensitivityUsage): readonly string[] | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* The part of an observation that says why it came back green.
|
|
202
|
+
*
|
|
203
|
+
* Structural rather than the whole `ObservationRecord`, because the second
|
|
204
|
+
* caller is a review service whose subjects arrive from a database and carry
|
|
205
|
+
* only what was stored. Writing the relation over the fields it actually reads
|
|
206
|
+
* is what lets both of them ask the same question, and stops the answer from
|
|
207
|
+
* being re-derived on the far side of the wire.
|
|
208
|
+
*/
|
|
209
|
+
export interface GreenSubject {
|
|
210
|
+
readonly verdict: 'unchanged' | 'changed' | 'new' | 'incomparable' | 'ignored';
|
|
211
|
+
readonly ignored?: {
|
|
212
|
+
readonly pixels: number;
|
|
213
|
+
readonly boxes: number;
|
|
214
|
+
readonly byRule: Readonly<Record<string, number>>;
|
|
215
|
+
} | undefined;
|
|
216
|
+
readonly relaxed?: {
|
|
217
|
+
readonly rule: string;
|
|
218
|
+
readonly level: string;
|
|
219
|
+
readonly bands: readonly string[];
|
|
220
|
+
} | undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Why one subject is green.
|
|
224
|
+
*
|
|
225
|
+
* - `measured` — nothing differed, and no declaration stood over it.
|
|
226
|
+
* - `masked` — nothing differed, and a rule was watching anyway. Its boxes
|
|
227
|
+
* caught nothing here, which is how a mask starts outliving its cause.
|
|
228
|
+
* - `relaxed` — it differed, and every band that moved is one this subject is
|
|
229
|
+
* not asserted on.
|
|
230
|
+
* - `absorbed` — it differed, and every differing pixel fell inside an excluded
|
|
231
|
+
* subtree. The rules that took them are named.
|
|
232
|
+
* - `unsaid` — it is `ignored` and the record does not say what did it.
|
|
233
|
+
*/
|
|
234
|
+
export type Green = {
|
|
235
|
+
readonly kind: 'measured';
|
|
236
|
+
} | {
|
|
237
|
+
readonly kind: 'masked';
|
|
238
|
+
readonly boxes: number;
|
|
239
|
+
} | {
|
|
240
|
+
readonly kind: 'relaxed';
|
|
241
|
+
readonly rule: string;
|
|
242
|
+
readonly level: string;
|
|
243
|
+
readonly bands: readonly string[];
|
|
244
|
+
} | {
|
|
245
|
+
readonly kind: 'absorbed';
|
|
246
|
+
readonly rules: readonly string[];
|
|
247
|
+
readonly pixels: number;
|
|
248
|
+
} | {
|
|
249
|
+
readonly kind: 'unsaid';
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* What decided a green subject, in the words of whatever decided it.
|
|
253
|
+
*
|
|
254
|
+
* `unsaid` is the case this exists for. A subject reported `ignored` whose
|
|
255
|
+
* record carries neither block says *a declaration absorbed this* and does not
|
|
256
|
+
* say which — and both obvious renderings of that are false. Printing nothing
|
|
257
|
+
* reads as *nothing was absorbed*; printing `0 px` reads as *a rule absorbed
|
|
258
|
+
* nothing*. So the absence is a state, and every surface has to render it.
|
|
259
|
+
*
|
|
260
|
+
* It is reachable two ways and they are worth telling apart when reading a bug:
|
|
261
|
+
* an older report written before the blocks were kept, and a store that dropped
|
|
262
|
+
* them on the way in. The second is the one that turns a report and a service
|
|
263
|
+
* into two different accounts of one run.
|
|
264
|
+
*/
|
|
265
|
+
export declare function greenBecause(entry: GreenSubject): Green;
|
|
266
|
+
//# sourceMappingURL=declarations.d.ts.map
|