@orkestrel/test 0.0.7 → 0.0.9

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.
@@ -8,6 +8,89 @@
8
8
  */
9
9
  export declare const ACCESSIBLE_ROLES: readonly string[];
10
10
 
11
+ /**
12
+ * Composites one color over another.
13
+ *
14
+ * @param front - The color painted on top.
15
+ * @param back - The color already on the surface.
16
+ * @returns The opaque result a reader sees, its alpha always `1`.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * blendColor([255, 255, 255, 0.5], [0, 0, 0, 1]) // [127.5, 127.5, 127.5, 1]
21
+ * ```
22
+ */
23
+ export declare function blendColor(front: Color, back: Color): Color;
24
+
25
+ /**
26
+ * Builds one unmounted element of a known tag, wearing the classes, text, and attributes asked for.
27
+ *
28
+ * @param tag - The HTML tag name, which fixes the returned element's exact type.
29
+ * @param options - The class list, the text, and the attributes to apply.
30
+ * @returns The built element, not yet in any document.
31
+ *
32
+ * @remarks
33
+ * The element is unmounted on purpose, so a fixture is assembled before the page ever sees it and a
34
+ * test decides where it goes. Nothing here resolves against the cascade: a built element computes no
35
+ * style and lays out no box until {@link mount} puts it in the document.
36
+ *
37
+ * The text is set as text rather than parsed as markup, so a `<` in it stays a `<`. Use
38
+ * {@link render} where the fixture is markup.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const button = build('button', { classes: 'primary', text: 'Save', attributes: { type: 'button' } })
43
+ * ```
44
+ */
45
+ export declare function build<K extends keyof HTMLElementTagNameMap>(tag: K, options?: ElementOptions): HTMLElementTagNameMap[K];
46
+
47
+ /**
48
+ * The page a browser paints an unstyled document onto.
49
+ *
50
+ * @remarks
51
+ * This is the floor a backdrop walk ends on wherever the caller wants the browser's own canvas
52
+ * assumed. `readBackdrop` takes its floor as an argument rather than reaching for this one, so a
53
+ * measurement over a surface the canvas never shows through names the color it actually sits on.
54
+ */
55
+ export declare const CANVAS_COLOR: Color;
56
+
57
+ /**
58
+ * The attribute marking the runner's tester pane, and the rule that sizes it, while a frame is
59
+ * staged.
60
+ *
61
+ * @remarks
62
+ * `stagePane` writes it onto the pane and onto the stylesheet it appends, and `releasePane` finds
63
+ * both by it. Nothing else reads it, so a document carrying it after a capture returned is a pane
64
+ * that was never released.
65
+ */
66
+ export declare const CAPTURE_PANE = "data-capture-pane";
67
+
68
+ /**
69
+ * Shoots one frame at one viewport size and proves the file on disk holds this run's bytes.
70
+ *
71
+ * @param options - The path to write, the viewport to shoot at, and the element to shoot.
72
+ * @returns The absolute path of the written frame, after it has been read back and matched.
73
+ * @throws Thrown when the pane cannot be staged, when the provider wrote the frame somewhere else,
74
+ * and when the bytes on disk are not the ones this shot produced.
75
+ *
76
+ * @remarks
77
+ * The path a screenshot call returns is the path it meant to write, so it is not evidence a file
78
+ * exists. The file is read back through the runner's built-in `readFile` command and compared with
79
+ * the shot itself, which is what separates a frame this run wrote from one an earlier run left
80
+ * behind. The provider resolves `options.path` against the calling test file and returns an absolute
81
+ * path, so the two are compared by the segments that survive resolving `.` and `..` lexically — the
82
+ * refusal is what a provider resolving that path against a different base would trip.
83
+ *
84
+ * Omit `options.element` to shoot the whole page. The pane is staged for the frame and released
85
+ * before this returns, on the failing path as well as the passing one.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * await captureFrame({ path: '../../tmp/capture/start.png', width: 390, height: 844 })
90
+ * ```
91
+ */
92
+ export declare function captureFrame(options: FrameOptions): Promise<string>;
93
+
11
94
  /** One theme-and-viewport pair a capture run renders. */
12
95
  export declare interface CaptureVariant {
13
96
  /** The variant's name, which is the second half of every filename the run writes. */
@@ -23,6 +106,21 @@ export declare interface CaptureVariant {
23
106
  readonly apply?: () => void;
24
107
  }
25
108
 
109
+ /**
110
+ * Clears both browser storage surfaces.
111
+ *
112
+ * @remarks
113
+ * A browser test file shares one page, so a key written by one test is read by the next one that
114
+ * looks for it. Call this from an `afterEach` hook, which runs after a failed test as well as a
115
+ * passing one, rather than at the end of each test that happens to write a key.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * afterEach(clearStorage)
120
+ * ```
121
+ */
122
+ export declare function clearStorage(): void;
123
+
26
124
  /**
27
125
  * Clicks one visible, focus-reachable control by its accessible name through the browser provider.
28
126
  *
@@ -76,12 +174,15 @@ export declare function clickAccessibleWithin(region: string, role: string, name
76
174
  *
77
175
  * @param name - The summary text a person reads.
78
176
  * @returns A promise resolving after trusted activation completes.
79
- * @throws When no visible, focus-reachable native summary has that rendered name, or several do.
177
+ * @throws When no native summary with that rendered name passes {@link isReachable}, or several do.
80
178
  *
81
179
  * @remarks
82
180
  * Chromium exposes `<summary>` as a native disclosure rather than through an ARIA role accepted by
83
181
  * `getByRole`, so this resolver names the platform element and its rendered text directly.
84
182
  *
183
+ * It applies the same {@link isReachable} filter the other acting verbs apply, so a summary marked
184
+ * `aria-disabled="true"` is refused here exactly as a button marked that way is refused there.
185
+ *
85
186
  * @example
86
187
  * ```ts
87
188
  * await clickDisclosure('Advanced')
@@ -89,12 +190,79 @@ export declare function clickAccessibleWithin(region: string, role: string, name
89
190
  */
90
191
  export declare function clickDisclosure(name: string): Promise<void>;
91
192
 
193
+ /**
194
+ * One rendered color as straight sRGB channels and its alpha.
195
+ *
196
+ * @remarks
197
+ * The channels run 0–255 and the alpha runs 0–1, which is the shape a computed `rgb()` value already
198
+ * carries. `parseColor` converts the 0–1 channels of `color(srgb …)` onto the same scale, so every
199
+ * color the measurement family passes around is comparable without asking where it came from.
200
+ */
201
+ export declare type Color = readonly [red: number, green: number, blue: number, alpha: number];
202
+
203
+ /**
204
+ * Determines whether two colors render the same, within the rounding a browser does.
205
+ *
206
+ * @param first - A CSS color expression or an already-parsed color.
207
+ * @param second - A CSS color expression or an already-parsed color.
208
+ * @returns `true` when every channel and the alpha agree within the tolerance; `false` otherwise,
209
+ * including when either side names no readable color.
210
+ *
211
+ * @remarks
212
+ * Each string side is resolved through {@link rgba}, so a keyword, a token reference, and the
213
+ * `rgb()` the engine computes for either of them compare equal without a test converting anything
214
+ * first. A side that resolves to nothing makes the answer `false` rather than a throw, because this
215
+ * is a predicate.
216
+ *
217
+ * The tolerance is half a channel step on the 0–255 scale, and the alpha is scaled onto that same
218
+ * range before it is compared, so one number covers both. Half a step is what a composite of
219
+ * translucent layers and a `color-mix()` round trip actually drift by; anything a reader could see
220
+ * is further than that and reports unequal.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * colorEqual('rebeccapurple', 'rgb(102, 51, 153)') // true
225
+ * colorEqual('red', [0, 0, 255, 1]) // false
226
+ * ```
227
+ */
228
+ export declare function colorEqual(first: string | Color, second: string | Color): boolean;
229
+
230
+ /**
231
+ * Sets one field's value and commits it, the way typing and then leaving the field does.
232
+ *
233
+ * @param element - The input or textarea to write into.
234
+ * @param text - The value to set.
235
+ *
236
+ * @remarks
237
+ * The order is the browser's: {@link typeInput} first, so `input` is dispatched with the value
238
+ * already set, and one bubbling `change` after it. A component that reads the value from either
239
+ * event therefore reads `text` from both.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * commitInput(requireValue(container.querySelector('input')), 'Ada')
244
+ * ```
245
+ */
246
+ export declare function commitInput(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
247
+
248
+ /**
249
+ * The roles whose accessible name is the text a reader can see inside them.
250
+ *
251
+ * @remarks
252
+ * `readName` reads an element in this list from its own rendered text, after every `aria-hidden`
253
+ * descendant is dropped, and falls through to `title` for every other role.
254
+ */
255
+ export declare const CONTENT_ROLES: readonly string[];
256
+
92
257
  /**
93
258
  * Measures the WCAG 2.x contrast ratio between an element's computed text and background colors.
94
259
  *
95
260
  * @param element - The element whose rendered text contrast to measure.
261
+ * @param floor - The opaque color the backdrop walk ends on. Omit it to refuse a stack the floor
262
+ * would show through instead of assuming one.
96
263
  * @returns The relative-luminance contrast ratio.
97
- * @throws When the browser does not expose parseable computed colors.
264
+ * @throws Thrown when the element exposes no computed foreground color, and — with `floor` omitted
265
+ * — when the walk from the element upwards reaches no opaque layer.
98
266
  *
99
267
  * @remarks
100
268
  * A transparent or translucent background resolves through the element's ancestors: every painted
@@ -103,18 +271,131 @@ export declare function clickDisclosure(name: string): Promise<void>;
103
271
  * full-strength paint. A translucent foreground then resolves against that effective background
104
272
  * before luminance is measured.
105
273
  *
106
- * Every element from the target upwards must be reachable, and at least one of them must paint:
107
- * the measurement throws rather than assuming a white canvas when nothing in the chain declares a
108
- * background color. The element itself must expose a computed foreground color a detached
109
- * element exposes none, and the measurement throws rather than guessing one.
274
+ * With `floor` omitted, the walk from the target upwards must reach a fully opaque layer: the
275
+ * measurement throws rather than assuming a white canvas wherever that canvas would still be part
276
+ * of the answer. The refusal reads the alpha of the deepest layer {@link readLayers} collected, so
277
+ * a chain that declares no background color at all, a chain painting only translucent layers, and a
278
+ * chain deep enough for its composite to round to the canvas's own channels are refused alike,
279
+ * because the number any of them produces is as much a report of the assumption as of the page.
280
+ * Supply a floor wherever the caller knows what the stack sits on — a fragment mounted into a
281
+ * painted host, or a document whose canvas is {@link CANVAS_COLOR} — and the composite is taken
282
+ * over it rather than refused.
283
+ *
284
+ * The element itself must expose a computed foreground color either way. A detached element exposes
285
+ * none, and the measurement throws rather than guessing one.
110
286
  *
111
287
  * @example
112
288
  * ```ts
113
289
  * const container = render('<p style="background: #000; color: #fff">Ready</p>')
114
290
  * contrast(requireValue(container.firstElementChild)) // 21
291
+ * contrast(requireValue(container.firstElementChild), CANVAS_COLOR) // 21, and never refuses
292
+ * ```
293
+ */
294
+ export declare function contrast(element: Element, floor?: Color): number;
295
+
296
+ /**
297
+ * Creates one console channel that records every call it receives and hands that call on unchanged.
298
+ *
299
+ * @param name - The channel's name, which prefixes each line it records.
300
+ * @param output - The list each call is recorded into, appended to in place.
301
+ * @param forward - The channel every call is passed on to after it is recorded.
302
+ * @returns A channel carrying the console's own call signature.
303
+ *
304
+ * @remarks
305
+ * One call becomes one line. Every argument of that call is put through `String` and joined with a
306
+ * space, so a call carrying several values reads as the one line the page printed rather than as
307
+ * several entries.
308
+ *
309
+ * Nothing is swallowed. The record happens first and `forward` receives the arguments it would have
310
+ * received, so a page recorded through this prints exactly what it printed without it. The list
311
+ * belongs to the caller, so a channel writes into whatever it was handed and holds no state of its
312
+ * own. {@link createJournal} builds one channel per console method over one list.
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * const output: string[] = []
317
+ * console.log = createChannel('log', output, console.log)
318
+ * ```
319
+ */
320
+ export declare function createChannel(name: string, output: string[], forward: (...data: unknown[]) => void): (...data: unknown[]) => void;
321
+
322
+ /**
323
+ * Creates one real drag event carrying a live data transfer, ready to dispatch.
324
+ *
325
+ * @param name - The event type, such as `dragstart`.
326
+ * @param options - Any `DragEventInit` member, each one overriding the default beneath it.
327
+ * @returns A real `DragEvent` of that type.
328
+ *
329
+ * @remarks
330
+ * A drag event with no `dataTransfer` is the shape that makes a drop handler fail in a test and work
331
+ * in a browser, so one is allocated. Pass your own to seed it: a `dataTransfer` given in `options`
332
+ * replaces the allocated one, which is how a drop is driven with the payload the drag was supposed
333
+ * to carry.
334
+ *
335
+ * The platform declares the `dataTransfer` member on the constructed event as nullable, so calling
336
+ * code still narrows it even though this always supplies one.
337
+ *
338
+ * `bubbles` and `cancelable` are set, because a drop handler that never prevents the default event
339
+ * is a drop the browser handles itself.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * const started = createDragEvent('dragstart')
344
+ * started.dataTransfer?.setData('text/plain', 'row-3')
345
+ * element.dispatchEvent(started)
346
+ * ```
347
+ */
348
+ export declare function createDragEvent(name: string, options?: DragEventInit): DragEvent;
349
+
350
+ /**
351
+ * Creates the journal one scenario records its steps and the page's own output into.
352
+ *
353
+ * @returns A journal that records nothing until it is started.
354
+ *
355
+ * @remarks
356
+ * The console is recorded rather than replaced: every intercepted call is forwarded to the channel
357
+ * that was there when the journal started, so a run under a journal prints exactly what it printed
358
+ * without one. `stop` puts those same function references back by identity.
359
+ *
360
+ * Uncaught errors and unhandled rejections are recorded too, through listeners the journal drops
361
+ * when it stops. `steps` and `output` hand out snapshots, so a list read mid-scenario stays what it
362
+ * was. Each journal owns its own recording, so a file that needs one per scenario creates one per
363
+ * scenario.
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * const journal = createJournal()
368
+ * journal.start()
369
+ * journal.record('click', 'Evaluate', 'alerts=0')
370
+ * journal.stop()
371
+ * journal.steps // [{ action: 'click', trigger: 'Evaluate', result: 'alerts=0' }]
115
372
  * ```
116
373
  */
117
- export declare function contrast(element: Element): number;
374
+ export declare function createJournal(): JournalInterface;
375
+
376
+ /**
377
+ * Creates one real pointer event, ready to dispatch.
378
+ *
379
+ * @param name - The event type, such as `pointerdown`.
380
+ * @param options - Any `PointerEventInit` member, each one overriding the default beneath it.
381
+ * @returns A real `PointerEvent` of that type.
382
+ *
383
+ * @remarks
384
+ * The defaults are what a browser's own pointer event carries and a hand-built one does not:
385
+ * `bubbles` and `cancelable` are set, so a delegated listener hears it and a handler can prevent it,
386
+ * and `pointerId`, `pointerType`, and `isPrimary` describe a single primary mouse, so a component
387
+ * that branches on the pointer kind takes the branch a mouse takes. Override any of them by naming
388
+ * it; a touch is `{ pointerType: 'touch' }` and nothing else has to be restated.
389
+ *
390
+ * The event is real rather than a shaped object, so `instanceof PointerEvent` holds and the
391
+ * coordinate and modifier members a handler reads are the ones the platform defines.
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * element.dispatchEvent(createPointerEvent('pointerdown', { clientX: 10, clientY: 20 }))
396
+ * ```
397
+ */
398
+ export declare function createPointerEvent(name: string, options?: PointerEventInit): PointerEvent;
118
399
 
119
400
  /**
120
401
  * Creates the capture portfolio one run places its screenshots through.
@@ -130,6 +411,10 @@ export declare function contrast(element: Element): number;
130
411
  * none of it. The portfolio refuses an unregistered variant at creation. An enabled run refuses an
131
412
  * unregistered state name and a second placement of one state.
132
413
  *
414
+ * An enabled `place` writes through `captureFrame`, so a placed state carries that helper's staged
415
+ * pane and its byte readback: a path is recorded only after the file on disk has been proved to hold
416
+ * this run's own frame.
417
+ *
133
418
  * @example
134
419
  * ```ts
135
420
  * const portfolio = createPortfolio({
@@ -143,6 +428,74 @@ export declare function contrast(element: Element): number;
143
428
  */
144
429
  export declare function createPortfolio(options: PortfolioOptions): PortfolioInterface;
145
430
 
431
+ /**
432
+ * Describes the order sequential keyboard navigation visits one element's controls in.
433
+ *
434
+ * @param element - The host to walk; its own controls are described, and it is not itself one.
435
+ * @returns One numbered line per reachable control, naming its role and its name.
436
+ *
437
+ * @remarks
438
+ * A positive `tabindex` is honoured, because a browser honours it: those controls come first in
439
+ * ascending order and everything else follows in document order. A control removed from the
440
+ * sequence by `tabindex="-1"`, by being disabled, or by not being rendered at all is absent here,
441
+ * which is the fact a focus-order verdict is about. A control {@link readRole} answers `undefined`
442
+ * for is named by its lowercased tag, so it is still counted rather than silently dropped.
443
+ *
444
+ * @example
445
+ * ```ts
446
+ * describeFocus(container)
447
+ * // 1. button "Save"
448
+ * // 2. link "Cancel"
449
+ * ```
450
+ */
451
+ export declare function describeFocus(element: Element): string;
452
+
453
+ /**
454
+ * Describes the accessible tree one rendered element presents.
455
+ *
456
+ * @param element - The host to walk, which is described first when it carries a role of its own.
457
+ * @returns One indented line per element carrying a role, naming its role, its name, and its
458
+ * states, in document order; an empty string when nothing in the subtree carries one.
459
+ *
460
+ * @remarks
461
+ * The walk is over the real rendered DOM, so what it reports is the tree the shipped markup and the
462
+ * shipped cascade produce together — a landmark lost to a hidden ancestor is missing here exactly as
463
+ * it is missing for a reader. An element {@link isRendered} refuses is dropped with its whole
464
+ * subtree.
465
+ *
466
+ * Depth follows the roles rather than the elements, so the indentation reads as the structure a
467
+ * screen reader announces instead of as the markup's nesting. An element {@link readRole} answers
468
+ * `undefined` for writes no line and adds no depth, so its children sit where it sat. That is how
469
+ * a wrapper `div` disappears, and it is also how an element {@link IMPLICIT_ROLES} does not answer
470
+ * for disappears — visibly, because its roled children stay at the depth it occupied.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * describeTree(container)
475
+ * // main "Board"
476
+ * // heading "Totals"
477
+ * ```
478
+ */
479
+ export declare function describeTree(element: Element): string;
480
+
481
+ /**
482
+ * Options for one built element.
483
+ *
484
+ * @remarks
485
+ * `classes` is written the way a `class` attribute is written — one space-separated string — so a
486
+ * fixture reads as the markup it stands in for. `attributes` is set name by name after the class
487
+ * list and the text, so an `attributes` entry named `class` wins over `classes` rather than merging
488
+ * with it.
489
+ */
490
+ export declare interface ElementOptions {
491
+ /** The class list, space-separated, exactly as a `class` attribute writes it. */
492
+ readonly classes?: string;
493
+ /** The text the element carries, set as text rather than parsed as markup. */
494
+ readonly text?: string;
495
+ /** Every attribute to set, keyed by attribute name. */
496
+ readonly attributes?: Readonly<Record<string, string>>;
497
+ }
498
+
146
499
  /**
147
500
  * Expands a capture registry across every variant into the filenames a complete portfolio holds.
148
501
  *
@@ -163,6 +516,42 @@ export declare function createPortfolio(options: PortfolioOptions): PortfolioInt
163
516
  */
164
517
  export declare function expandCaptures(states: readonly string[], variants: readonly CaptureVariant[]): readonly string[];
165
518
 
519
+ /**
520
+ * Collects every element carrying a component class rendered outside the container it belongs to.
521
+ *
522
+ * @param root - The subtree to sweep.
523
+ * @param child - The component class whose anatomy requires a container, such as `list-group-item`.
524
+ * @param parent - The container class that child class must render inside, such as `list-group`.
525
+ * @returns The markup of every element carrying `child` with no `parent` above it, in document
526
+ * order; an empty list when every one of them is nested correctly.
527
+ *
528
+ * @remarks
529
+ * A component keeps its padding, borders, and radii on the container, so a child class rendered
530
+ * outside one is an unstyled box wearing a component's name, and the interface has to hand-roll the
531
+ * chrome back. The search for the container starts at the element's parent, so an element can never
532
+ * answer the invariant by carrying both classes itself.
533
+ *
534
+ * The class names are arguments, so the check belongs to no framework: name the pair your own
535
+ * cascade defines.
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * extractOrphans(container, 'list-group-item', 'list-group') // []
540
+ * ```
541
+ */
542
+ export declare function extractOrphans(root: ParentNode, child: string, parent: string): readonly string[];
543
+
544
+ /**
545
+ * The role each `input` type carries.
546
+ *
547
+ * @remarks
548
+ * Membership is the contract. The map answers for `button`, `checkbox`, `email`, `number`,
549
+ * `password`, `radio`, `range`, `reset`, `search`, `submit`, `tel`, `text`, and `url`. A type the
550
+ * map omits — `color`, `date`, `file`, `hidden`, and the rest — exposes no role of its own, so
551
+ * `readRole` returns `undefined` for it and `describeTree` writes no line for it.
552
+ */
553
+ export declare const FIELD_ROLES: Readonly<Record<string, string>>;
554
+
166
555
  /**
167
556
  * Replaces a named field's value in one operation, for text too long to type key by key.
168
557
  *
@@ -181,6 +570,104 @@ export declare function expandCaptures(states: readonly string[], variants: read
181
570
  */
182
571
  export declare function fillAccessible(name: string, text: string): Promise<void>;
183
572
 
573
+ /**
574
+ * Finds the animation the cascade declares under one name.
575
+ *
576
+ * @param name - The exact `@keyframes` name.
577
+ * @returns The first matching rule in {@link readRules} order, or `undefined` when the cascade
578
+ * declares no animation under that name.
579
+ *
580
+ * @remarks
581
+ * The name is matched exactly, which is where this parts from {@link findRule}: a selector is
582
+ * compound and a fragment of one is a useful question, and an animation name is one atom that either
583
+ * is or is not the one an `animation` declaration references.
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * findKeyframes('fade')?.cssRules.length
588
+ * ```
589
+ */
590
+ export declare function findKeyframes(name: string): CSSKeyframesRule | undefined;
591
+
592
+ /**
593
+ * Finds the first style rule in the cascade whose selector carries a fragment.
594
+ *
595
+ * @param selector - The selector fragment to look for, matched as a substring of the whole selector
596
+ * text.
597
+ * @returns The first matching rule in {@link readRules} order, or `undefined` when no rule carries
598
+ * the fragment.
599
+ *
600
+ * @remarks
601
+ * This proves a declaration exists in the cascade at all, which is a different question from what an
602
+ * element resolves to: {@link style} reads the winner, and a rule this finds may be overridden by
603
+ * another. Assert on this where the subject is the stylesheet, and on `style` where the subject is
604
+ * the rendered result.
605
+ *
606
+ * The match is a substring, so `findRule('.card')` finds `.card`, `.card:hover`, and
607
+ * `.panel > .card` alike. Pass more of the selector to narrow it.
608
+ *
609
+ * @example
610
+ * ```ts
611
+ * findRule('.card')?.style.getPropertyValue('padding')
612
+ * ```
613
+ */
614
+ export declare function findRule(selector: string): CSSStyleRule | undefined;
615
+
616
+ /**
617
+ * What sequential keyboard navigation can reach, before disabled and unrendered elements go.
618
+ *
619
+ * @remarks
620
+ * `describeFocus` queries this selector and then drops what a browser drops: an element the
621
+ * accessibility tree does not present, a disabled control, and one removed from the sequence by
622
+ * `tabindex="-1"`. `traverseAccessible` counts the same population to bound its walk, so this is
623
+ * the one list either one reads.
624
+ */
625
+ export declare const FOCUSABLE_SELECTOR = "a[href], area[href], button, input, select, summary, textarea, [tabindex]";
626
+
627
+ /** Options for one captured frame. */
628
+ export declare interface FrameOptions {
629
+ /** The frame's path, relative to the calling test file. */
630
+ readonly path: string;
631
+ /** The viewport width in CSS pixels the frame is shot at. */
632
+ readonly width: number;
633
+ /** The viewport height in CSS pixels the frame is shot at. */
634
+ readonly height: number;
635
+ /** The element to shoot. Omit it to shoot the whole page. */
636
+ readonly element?: Element | undefined;
637
+ }
638
+
639
+ /**
640
+ * The role a `th` carries for the header axis its `scope` names.
641
+ *
642
+ * @remarks
643
+ * A header cell heads a column or a row, and this map answers for the `col` and `row` scopes that
644
+ * say which. A `th` declaring no scope keeps {@link IMPLICIT_ROLES}' `columnheader` rather than the
645
+ * ARIA computation that infers the axis from the table's shape.
646
+ */
647
+ export declare const HEADER_ROLES: Readonly<Record<string, string>>;
648
+
649
+ /**
650
+ * The role each listed tag carries in the accessibility tree when it declares none of its own.
651
+ *
652
+ * @remarks
653
+ * Membership is the contract. The map answers for the sectioning elements `ARTICLE`, `ASIDE`,
654
+ * `FOOTER`, `HEADER`, `MAIN`, `NAV`, `SEARCH`, and `SECTION`; the headings `H1` through `H6`; the
655
+ * grouping and list elements `FIELDSET`, `FORM`, `HR`, `LI`, `OL`, and `UL`; the table elements
656
+ * `TABLE`, `TBODY`, `THEAD`, `TR`, `TD`, and `TH`; and the widgets `BUTTON`, `DIALOG`, `IMG`,
657
+ * `OPTION`, `OUTPUT`, `PROGRESS`, `SUMMARY`, and `TEXTAREA`.
658
+ *
659
+ * A tag the map omits carries no implicit role, so `readRole` returns `undefined` for it,
660
+ * `describeTree` writes no line for it, and the walk continues straight into its children at the
661
+ * depth the omitted element sat at. `A`, `INPUT`, and `SELECT` are absent deliberately: each takes
662
+ * its role from an attribute rather than from its tag, and `readRole` answers for them from their
663
+ * own anatomy.
664
+ *
665
+ * `SECTION` maps to `region`, which `readRole` withholds from an unnamed one, because an unnamed
666
+ * section is not a landmark. `TH` maps to `columnheader`, which {@link HEADER_ROLES} replaces when
667
+ * the cell declares a `scope`.
668
+ */
669
+ export declare const IMPLICIT_ROLES: Readonly<Record<string, string>>;
670
+
184
671
  /**
185
672
  * Determines whether a rectangle lies wholly outside the browser viewport.
186
673
  *
@@ -194,6 +681,215 @@ export declare function fillAccessible(name: string, text: string): Promise<void
194
681
  */
195
682
  export declare function isOutsideViewport(rectangle: DOMRectReadOnly): boolean;
196
683
 
684
+ /**
685
+ * Determines whether a person can click one element where it currently sits.
686
+ *
687
+ * @param element - The element to judge.
688
+ * @returns `true` when the element is connected, visible, laid out with a non-zero box, in the
689
+ * sequential focus order, neither disabled nor marked `aria-disabled="true"`, and outside every
690
+ * `[inert]` subtree; `false` otherwise.
691
+ *
692
+ * @remarks
693
+ * This is the one reachability filter the layer applies. `resolveRendered`, `clickAccessibleWithin`,
694
+ * and `clickDisclosure` each narrow their own candidates and then keep the ones this accepts, so a
695
+ * journey meets one rule rather than three near-copies of it.
696
+ *
697
+ * It measures geometry, which is what separates it from {@link isRendered}. A control clipped to a
698
+ * zero-size rectangle is announced and is not clickable, so `isRendered` accepts it and this
699
+ * refuses it. Nothing here asks about the viewport: `resolveAccessible` scrolls a wholly
700
+ * off-viewport target into view and measures that separately with {@link isOutsideViewport}.
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * isReachable(requireValue(container.querySelector('button')))
705
+ * ```
706
+ */
707
+ export declare function isReachable(element: Element): boolean;
708
+
709
+ /**
710
+ * Determines whether the accessibility tree presents one element at all.
711
+ *
712
+ * @param element - The element to judge.
713
+ * @returns `false` when the element is hidden from assistive technology, from sight, or from both;
714
+ * `true` otherwise.
715
+ *
716
+ * @remarks
717
+ * A control clipped to a zero-size rectangle is still announced, which is the whole point of that
718
+ * idiom, so nothing here reads geometry: only the removals a browser honours — `aria-hidden`
719
+ * anywhere above it, the `hidden` attribute, a hidden input, and a `display` or `visibility` that
720
+ * takes it off the page. {@link isReachable} is the clickable half of the pair and does read
721
+ * geometry.
722
+ *
723
+ * The last two are asked about the element's ancestors as well as itself, which reading a computed
724
+ * `display` cannot do: the computed value of a child of a `display: none` container is the child's
725
+ * own, so a control inside a closed drawer reports itself as laid out. `checkVisibility` answers
726
+ * for the box tree, and `visibility` inherits, so between them an ancestor cannot hide a control
727
+ * from a reader and leave it standing in a description.
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * isRendered(requireValue(container.querySelector('[aria-hidden="true"] button'))) // false
732
+ * ```
733
+ */
734
+ export declare function isRendered(element: Element): boolean;
735
+
736
+ /**
737
+ * The record of one scenario: every step it took and everything the page said while it ran.
738
+ *
739
+ * @remarks
740
+ * Recording is off until {@link JournalInterface.start} arms it, so a suite that never starts a
741
+ * journal pays for none of it. The console is observed by standing in front of it and forwarding
742
+ * every call to the channel that was there: a browser offers no listener for its own output, and a
743
+ * journal that swallowed what it read would hide exactly the diagnostics it exists to keep.
744
+ */
745
+ export declare interface JournalInterface {
746
+ /** Every step recorded since the journal started, in the order it was taken; a snapshot. */
747
+ readonly steps: readonly JournalStep[];
748
+ /** Every console line and uncaught failure the page emitted since it started; a snapshot. */
749
+ readonly output: readonly string[];
750
+ /**
751
+ * Starts a fresh recording, dropping whatever the previous scenario left.
752
+ *
753
+ * @remarks
754
+ * Calling this on a started journal clears both lists and leaves the console interception
755
+ * standing, so a restart never wraps its own wrappers.
756
+ */
757
+ start(): void;
758
+ /**
759
+ * Stops recording and hands every intercepted console channel back by identity.
760
+ *
761
+ * @remarks
762
+ * Calling this on a stopped journal does nothing. The recorded lists survive, so a scenario is
763
+ * read after its recording ends.
764
+ */
765
+ stop(): void;
766
+ /**
767
+ * Records one step, when the journal is started.
768
+ *
769
+ * @param action - What the run did, as one verb.
770
+ * @param trigger - The exact thing it did it to.
771
+ * @param result - What was observed on the surface after the step landed.
772
+ */
773
+ record(action: string, trigger: string, result: string): void;
774
+ }
775
+
776
+ /** One scripted step a journal recorded, and what the surface did about it. */
777
+ export declare interface JournalStep {
778
+ /** What the run did, as one verb. */
779
+ readonly action: string;
780
+ /** The exact thing it did it to. */
781
+ readonly trigger: string;
782
+ /** What was observed on the surface after the step landed. */
783
+ readonly result: string;
784
+ }
785
+
786
+ /**
787
+ * Measures the WCAG 2.x contrast ratio between two opaque colors.
788
+ *
789
+ * @param front - The foreground color, already composited.
790
+ * @param back - The opaque backdrop.
791
+ * @returns The ratio, from `1` for two identical colors to `21` for black against white.
792
+ *
793
+ * @remarks
794
+ * The ratio is symmetric: the brighter of the two luminances is always the numerator, so swapping
795
+ * the arguments returns the same number.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * measureContrast([0, 0, 0, 1], [255, 255, 255, 1]) // 21
800
+ * ```
801
+ */
802
+ export declare function measureContrast(front: Color, back: Color): number;
803
+
804
+ /**
805
+ * Measures one opaque color's WCAG relative luminance.
806
+ *
807
+ * @param color - The color to weigh. Its alpha is ignored, so composite before calling.
808
+ * @returns The relative luminance, from `0` for black to `1` for white.
809
+ *
810
+ * @example
811
+ * ```ts
812
+ * measureLuminance([255, 255, 255, 1]) // 1
813
+ * ```
814
+ */
815
+ export declare function measureLuminance(color: Color): number;
816
+
817
+ /**
818
+ * Puts one element into the document and hands it straight back.
819
+ *
820
+ * @param element - The element to attach.
821
+ * @returns The same element, now appended to `document.body`.
822
+ *
823
+ * @remarks
824
+ * What this buys is the composition, not the attachment: the `append` method returns `void`, and
825
+ * this hands the element back, so it fits where an expression is expected. The {@link render} helper
826
+ * returns its fixture through it, and the {@link rgba} helper probes through `mount(build('span'))`.
827
+ * A bare `append` call breaks each of those call sites.
828
+ *
829
+ * Being connected is what the attachment then buys: `getComputedStyle` resolves against the shipped
830
+ * cascade, custom properties inherit from `:root`, and the element lays out a real box. A detached
831
+ * element answers each of those questions with the initial value instead, which reads as a styling
832
+ * defect rather than as a detached node.
833
+ *
834
+ * Taking it back out belongs to the consumer's teardown, because this records nothing: a browser
835
+ * test file shares one page, so a fixture left behind is the next test's resolver ambiguity. Build a
836
+ * recorded container in a setup module and remove it from an `afterEach` hook.
837
+ *
838
+ * @example
839
+ * ```ts
840
+ * const panel = mount(build('div', { classes: 'surface' }))
841
+ * panel.remove()
842
+ * ```
843
+ */
844
+ export declare function mount<T extends Element>(element: T): T;
845
+
846
+ /**
847
+ * Parses one computed CSS color value into straight sRGB channels.
848
+ *
849
+ * @param value - A computed `rgb()`, `rgba()`, or `color(srgb …)` value.
850
+ * @returns The color's channels, or `undefined` when the value names no color this reader speaks.
851
+ *
852
+ * @remarks
853
+ * A computed color resolves to `rgb()` or `rgba()` for every legacy source, and a `color-mix()`
854
+ * declaration resolves to `color(srgb r g b [/ a])` with channels on the 0–1 scale. Both forms are
855
+ * read here and nothing else is: a keyword, a hex triple, an empty string from a detached element,
856
+ * and a color space the cascade never hands back all return `undefined`. Absence is the answer
857
+ * rather than a transparent color, so a caller decides what an unreadable value means instead of
858
+ * measuring a black it never saw.
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * parseColor('rgba(255, 255, 255, 0.5)') // [255, 255, 255, 0.5]
863
+ * parseColor('rebeccapurple') // undefined
864
+ * ```
865
+ */
866
+ export declare function parseColor(value: string): Color | undefined;
867
+
868
+ /**
869
+ * Reads one resolved CSS length as a number of pixels.
870
+ *
871
+ * @param element - The element whose resolved style to inspect.
872
+ * @param property - The CSS property name, registered or custom.
873
+ * @returns The leading numeric part of the resolved value, and `0` when it carries none.
874
+ *
875
+ * @remarks
876
+ * A resolved length is text with a unit — `'12px'` — so this reads the number in front of the unit
877
+ * and discards the rest. The unit is not checked: the resolved value of a length is in pixels in
878
+ * every case a browser hands back, and a property that resolves to something else is the caller's
879
+ * mistake rather than this reader's.
880
+ *
881
+ * An unparsable value reads as `0` rather than as absence, because every caller of this is measuring
882
+ * and `'auto'`, `'none'`, and `''` each contribute no pixels to what a reader sees. Where the
883
+ * distinction matters, read the text with {@link style} instead.
884
+ *
885
+ * @example
886
+ * ```ts
887
+ * pixels(button, 'padding-left') // 12
888
+ * pixels(button, 'width') // 0 when the width resolves to `auto`
889
+ * ```
890
+ */
891
+ export declare function pixels(element: Element, property: string): number;
892
+
197
893
  /** The registry of capture states one run places, and the files it wrote placing them. */
198
894
  export declare interface PortfolioInterface {
199
895
  /** The name of the variant this run renders. */
@@ -205,14 +901,16 @@ export declare interface PortfolioInterface {
205
901
  /** The registry expanded across every variant: the filenames a complete portfolio holds. */
206
902
  readonly files: readonly string[];
207
903
  /**
208
- * Places one registered state: applies the variant, resizes the viewport, and writes the
904
+ * Places one registered state: applies the variant, stages the pane, and writes the verified
209
905
  * screenshot.
210
906
  *
211
907
  * @param state - The state name from the registry.
908
+ * @param element - The element to shoot. Omit it to shoot the whole page.
212
909
  * @returns The written path, or `undefined` when the portfolio is not enabled.
213
- * @throws When the state is not registered or has already been placed.
910
+ * @throws When the state is not registered, has already been placed, or the written frame does
911
+ * not read back as the bytes this shot produced.
214
912
  */
215
- place(state: string): Promise<string | undefined>;
913
+ place(state: string, element?: Element): Promise<string | undefined>;
216
914
  }
217
915
 
218
916
  /** Options for a capture portfolio. */
@@ -248,15 +946,53 @@ export declare interface PortfolioOptions {
248
946
  */
249
947
  export declare function pressKeys(keys: string): Promise<void>;
250
948
 
949
+ /**
950
+ * Resolves the opaque color standing behind one element.
951
+ *
952
+ * @param element - The element whose backdrop to resolve.
953
+ * @param floor - The opaque color the walk ends on when nothing above it paints.
954
+ * @returns The composited color a reader sees behind the element.
955
+ *
956
+ * @remarks
957
+ * The layers {@link readLayers} collects composite top-over-bottom onto the floor, so a 3% surface
958
+ * tint reads as a tint over what shows through it rather than as a full-strength paint.
959
+ *
960
+ * The floor is required, because this leaf never guesses what a document sits on. Pass
961
+ * {@link CANVAS_COLOR} for the page a browser paints behind an unstyled document, or the color of
962
+ * the surface a fragment is really rendered into. When no layer paints, the floor is returned by
963
+ * identity.
964
+ *
965
+ * The composite alone never says whether the floor is part of the answer. A caller that must know
966
+ * reads the stack instead.
967
+ *
968
+ * @example
969
+ * ```ts
970
+ * readBackdrop(requireValue(container.querySelector('p')), CANVAS_COLOR)
971
+ * ```
972
+ */
973
+ export declare function readBackdrop(element: Element, floor: Color): Color;
974
+
251
975
  /**
252
976
  * Collects every class token the stylesheets loaded into this document actually define.
253
977
  *
254
- * @returns The set of class names reachable in the shipped cascade.
978
+ * @returns The set of class names reachable in the shipped cascade, in {@link readRules} order.
255
979
  *
256
980
  * @remarks
257
981
  * The set is what an authored-class conformance check measures against, so a class no loaded
258
982
  * stylesheet defines — an invented utility, a misspelled framework name — is absent from it.
259
983
  *
984
+ * The tokens come from the {@link readRules} walk, which decides both the membership and the
985
+ * insertion order this reader reports, and each answer is a deliberate difference from 0.0.8. A
986
+ * class declared inside a grouping rule — a media query, a supports block, a layer, a nested style
987
+ * rule — counts as defined, because a class the cascade defines under a condition is still one the
988
+ * cascade defines; 0.0.8 read the top-level rules alone. Insertion order is breadth-first, so a
989
+ * top-level class lands before a class declared inside an earlier grouping rule; 0.0.8 popped a
990
+ * stack and inserted the deepest rule first. Iterate the set where the order is the subject, and
991
+ * read `has` where membership is.
992
+ *
993
+ * `@keyframes` children are outside that walk, so an animation's own rules define no token here.
994
+ * Reach the animation itself through {@link findKeyframes}.
995
+ *
260
996
  * @example
261
997
  * ```ts
262
998
  * readCascade().has('card')
@@ -279,6 +1015,56 @@ export declare function readCascade(): ReadonlySet<string>;
279
1015
  */
280
1016
  export declare function readFocus(): string | undefined;
281
1017
 
1018
+ /**
1019
+ * Collects the painted layers standing between one element and the surface it sits on.
1020
+ *
1021
+ * @param element - The element to walk up from.
1022
+ * @returns Every layer the walk paints, the element's own first and the deepest last.
1023
+ *
1024
+ * @remarks
1025
+ * A surface token paints one ancestor while every element between it and the text paints nothing,
1026
+ * so a backdrop is found by walking up rather than by reading the element's own `background-color`,
1027
+ * which is almost always transparent. A fully transparent layer paints nothing and is left out, and
1028
+ * the walk stops at the first fully opaque layer, because nothing above that layer is visible.
1029
+ *
1030
+ * The stack is what tells a resolved backdrop from an assumed one: the walk reached an opaque
1031
+ * surface exactly when its last layer's alpha is `1`. {@link contrast} refuses on that reading,
1032
+ * which no comparison of composited colors can replace — 64 half-transparent layers composite to
1033
+ * the same channels over opposite floors, because the floor's remaining share falls below the last
1034
+ * bit a channel carries.
1035
+ *
1036
+ * @example
1037
+ * ```ts
1038
+ * readLayers(requireValue(container.querySelector('p')))
1039
+ * ```
1040
+ */
1041
+ export declare function readLayers(element: Element): readonly Color[];
1042
+
1043
+ /**
1044
+ * Reads the accessible name one element is announced under.
1045
+ *
1046
+ * @param element - The element to name.
1047
+ * @returns The computed name, or an empty string when the element carries none.
1048
+ *
1049
+ * @remarks
1050
+ * The order is the one a browser follows: `aria-labelledby`, then `aria-label`, then a form
1051
+ * control's own labels, then an image's `alt`, then the text inside a role {@link CONTENT_ROLES}
1052
+ * names, then `title`. A submit, reset, or button input is named by its value, because it renders
1053
+ * no text to read. An `aria-labelledby` naming several ids joins their texts in the order the
1054
+ * attribute lists them, and an id nothing answers for is skipped rather than fatal.
1055
+ *
1056
+ * Each step answers only when it has something to say, so a step that carries nothing hands the
1057
+ * element to the next one. An image whose `alt` is absent or blank is the case that shows it:
1058
+ * `<img title="Chart">` is named `Chart` rather than the empty string its own `alt` step would
1059
+ * have returned, and an image carrying both keeps answering `alt`.
1060
+ *
1061
+ * @example
1062
+ * ```ts
1063
+ * readName(requireValue(container.querySelector('button'))) // 'Save changes'
1064
+ * ```
1065
+ */
1066
+ export declare function readName(element: Element): string;
1067
+
282
1068
  /**
283
1069
  * Reads the normalized visible text of the whole page.
284
1070
  *
@@ -312,6 +1098,66 @@ export declare function readPage(): string;
312
1098
  */
313
1099
  export declare function readPerception(name: string): string;
314
1100
 
1101
+ /**
1102
+ * Measures the contrast the focus chrome painted on one control reaches against its own backdrop.
1103
+ *
1104
+ * @param control - The control that holds the focus.
1105
+ * @param worn - The element the control's focus chrome is painted onto. Default: `control`.
1106
+ * @returns The strongest ratio the painted focus chrome reaches, or `undefined` when the control is
1107
+ * not showing `:focus-visible` or the cascade paints no chrome of its own.
1108
+ *
1109
+ * @remarks
1110
+ * This reads and never acts. Focus arrives through the published verbs — `traverseAccessible`,
1111
+ * `pressKeys`, a real click — and this measures what the browser painted once it landed. A control
1112
+ * that is not matching `:focus-visible` when the call is made reports nothing, because no
1113
+ * measurement taken then would be about focus.
1114
+ *
1115
+ * Some controls are two elements: one that takes the focus and one a reader can see. A hidden radio
1116
+ * beside the label that carries every pixel of its chrome is the case `worn` exists for, so a
1117
+ * measurement is not taken on a rectangle nobody is looking at. The focus state is still read off
1118
+ * `control`, because that is what holds it.
1119
+ *
1120
+ * The backdrop is the surface behind the element the chrome is worn on, resolved from that element's
1121
+ * parent through {@link readBackdrop} onto {@link CANVAS_COLOR}. A control whose ancestry paints
1122
+ * nothing is therefore measured against the browser's own canvas, which is what a reader looking at
1123
+ * an unstyled document sees.
1124
+ *
1125
+ * Only chrome the cascade paints is measured — an `outline` with a real style and width, and the
1126
+ * first color in a `box-shadow`. A control left the browser's own `outline-style: auto` ring reports
1127
+ * `undefined`, because that ring's two tones are guaranteed against any backdrop and its computed
1128
+ * color names neither. A focus style that only changes the control's own fill reports `undefined`
1129
+ * too: the resting fill is gone by the time focus is on the control, and this never moves focus to
1130
+ * go and read it.
1131
+ *
1132
+ * @example
1133
+ * ```ts
1134
+ * await traverseAccessible('Evaluate')
1135
+ * readRing(resolveRendered('Evaluate')) // the ratio the painted ring reaches
1136
+ * ```
1137
+ */
1138
+ export declare function readRing(control: Element, worn?: Element): number | undefined;
1139
+
1140
+ /**
1141
+ * Reads the role one element carries in the accessibility tree.
1142
+ *
1143
+ * @param element - The element to classify.
1144
+ * @returns The declared role, the implicit one, or `undefined` when the element carries none.
1145
+ *
1146
+ * @remarks
1147
+ * A declared `role` wins outright, and its first token is the answer when several are listed.
1148
+ * Otherwise the element's own anatomy decides: an anchor is a link only while it holds an `href`,
1149
+ * an `input` takes the role {@link FIELD_ROLES} gives its type, a `select` is a combobox until it
1150
+ * offers several rows at once, a `section` is a region only once something names it, and a `th`
1151
+ * heads whichever axis its `scope` names. Every other tag answers from {@link IMPLICIT_ROLES},
1152
+ * whose membership is the contract for what this can answer at all.
1153
+ *
1154
+ * @example
1155
+ * ```ts
1156
+ * readRole(requireValue(container.querySelector('a[href]'))) // 'link'
1157
+ * ```
1158
+ */
1159
+ export declare function readRole(element: Element): string | undefined;
1160
+
315
1161
  /**
316
1162
  * Reads the normalized visible text of every element a selector matches, in document order.
317
1163
  *
@@ -331,6 +1177,76 @@ export declare function readPerception(name: string): string;
331
1177
  */
332
1178
  export declare function readRows(root: ParentNode, selector: string): readonly string[];
333
1179
 
1180
+ /**
1181
+ * Collects every rule the stylesheets loaded into this document hold, nested grouping rules
1182
+ * included.
1183
+ *
1184
+ * @returns Every rule reachable in the shipped cascade: each sheet's own rules in sheet order, then
1185
+ * the rules nested inside them, level by level.
1186
+ *
1187
+ * @remarks
1188
+ * The walk is iterative and reads the list it is still appending to, which is what expands a media
1189
+ * query, a supports block, a layer, and a nested style rule without recursion. Expanding by level
1190
+ * rather than by depth is why a top-level rule is always met before a rule nested inside an earlier
1191
+ * one; {@link findRule} returns the first match in exactly this order.
1192
+ *
1193
+ * The descent reaches a `CSSGroupingRule` and nothing else, and a `@keyframes` rule is not one. The
1194
+ * `@keyframes` rule itself is collected wherever it sits, and the keyframe rules inside it are not;
1195
+ * {@link findKeyframes} is the door to those.
1196
+ *
1197
+ * A stylesheet the document cannot read — a cross-origin sheet with no CORS grant — throws from its
1198
+ * own `cssRules` getter, and that sheet is skipped rather than ending the walk. What a page loaded
1199
+ * from another origin declares is unreadable to every caller here, so the alternative is a helper
1200
+ * that works until a test page adds a font or an analytics stylesheet.
1201
+ *
1202
+ * @example
1203
+ * ```ts
1204
+ * readRules().filter((rule) => rule instanceof CSSKeyframesRule)
1205
+ * ```
1206
+ */
1207
+ export declare function readRules(): readonly CSSRule[];
1208
+
1209
+ /**
1210
+ * Reads the states one element is announced in.
1211
+ *
1212
+ * @param element - The element to read.
1213
+ * @returns Every state the element declares, in one fixed order.
1214
+ *
1215
+ * @remarks
1216
+ * A state a reader is told about is one this records: what is unavailable, disclosed, pressed,
1217
+ * current, refused, chosen, announcing itself, demanded, uneditable, described, or busy. The order
1218
+ * is fixed, so two descriptions of the same surface are comparable line for line.
1219
+ *
1220
+ * A native disclosure states its expansion on the parent `details` element's own `open` rather than
1221
+ * on an ARIA attribute, so a summary that declares no `aria-expanded` is read from the platform's
1222
+ * one copy of that fact.
1223
+ *
1224
+ * @example
1225
+ * ```ts
1226
+ * readStates(requireValue(container.querySelector('summary'))) // ['collapsed']
1227
+ * ```
1228
+ */
1229
+ export declare function readStates(element: Element): readonly string[];
1230
+
1231
+ /**
1232
+ * Reads one element's rendered text the way a name computation reads it.
1233
+ *
1234
+ * @param element - The element whose announced words are wanted.
1235
+ * @returns The text with every `aria-hidden` descendant dropped and whitespace runs collapsed.
1236
+ *
1237
+ * @remarks
1238
+ * A glyph marked `aria-hidden` contributes nothing to a name, so a control captioned by an icon
1239
+ * plus a word reads as the word alone — which is what a reader hears, and what a verdict citing a
1240
+ * description has to compare against the copy a template writes. Reach for `readRows` wherever the
1241
+ * subject is what the page paints rather than what it announces: that one keeps the glyph.
1242
+ *
1243
+ * @example
1244
+ * ```ts
1245
+ * readText(requireValue(container.querySelector('button'))) // 'Save'
1246
+ * ```
1247
+ */
1248
+ export declare function readText(element: Element): string;
1249
+
334
1250
  /**
335
1251
  * Reads the value a resolved control renders.
336
1252
  *
@@ -351,19 +1267,75 @@ export declare function readRows(root: ParentNode, selector: string): readonly s
351
1267
  export declare function readValue(role: string, name: string): string;
352
1268
 
353
1269
  /**
354
- * Renders trusted fixture markup into a container attached to the document.
1270
+ * Hands the tester pane back to the runner's own layout.
1271
+ *
1272
+ * @remarks
1273
+ * A staged pane is the runner's fitting scale suppressed, so a pane left staged outlives the capture
1274
+ * that needed it and every later act in the file happens on a surface the runner is no longer
1275
+ * fitting to its window. What that costs is not a wrong picture: it is a control whose page
1276
+ * coordinates fall outside the pane, which the runner's own layout then intercepts, so an ordinary
1277
+ * press fails with the voice of a control that is covered. Calling this on an unstaged pane does
1278
+ * nothing.
1279
+ *
1280
+ * @example
1281
+ * ```ts
1282
+ * releasePane()
1283
+ * ```
1284
+ */
1285
+ export declare function releasePane(): void;
1286
+
1287
+ /**
1288
+ * Deletes one IndexedDB database and reports what the request actually did.
355
1289
  *
356
- * @param markup - The fixture markup to render.
357
- * @returns The attached container.
1290
+ * @param name - The database name to delete.
1291
+ * @returns A promise resolving after the deletion completes.
1292
+ * @throws Thrown when the request errors, and when an open connection blocks it.
1293
+ *
1294
+ * @remarks
1295
+ * Deleting a database that was never created succeeds, so this is safe to call from a teardown hook
1296
+ * that runs whether or not the test reached the code that opens one.
1297
+ *
1298
+ * A block is a rejection rather than a wait. `blocked` fires when another connection is still open,
1299
+ * and a suite that swallowed it would leave the next test reading the previous test's records
1300
+ * through a database that reports itself deleted. The connection holding it open is the caller's to
1301
+ * close, so the block is handed back rather than absorbed.
1302
+ *
1303
+ * @example
1304
+ * ```ts
1305
+ * afterEach(() => removeDatabase('ledger'))
1306
+ * ```
1307
+ */
1308
+ export declare function removeDatabase(name: string): Promise<void>;
1309
+
1310
+ /**
1311
+ * Renders one fixture into the document, from trusted markup or from a tag and its classes.
1312
+ *
1313
+ * @param first - The fixture markup, or the HTML tag name when `second` is present.
1314
+ * @param second - The class list when `first` supplies the tag name.
1315
+ * @returns The attached container for the markup form, and the attached element itself for the tag
1316
+ * form.
1317
+ *
1318
+ * @remarks
1319
+ * The class list is required in the tag form, which is what keeps the two forms apart: a
1320
+ * one-argument call is always markup. A tag with no classes is `mount(build(tag))`.
1321
+ *
1322
+ * The markup form parses `first` into a fresh container and returns that container, so the fixture's
1323
+ * own nodes are its children. The tag form returns the element itself, typed as exactly that tag.
1324
+ * Both attach to `document.body` and neither records anything, so removal is the caller's, exactly
1325
+ * as it is for {@link mount}.
358
1326
  *
359
1327
  * @example
360
1328
  * ```ts
361
1329
  * const container = render('<button type="button">Save</button>')
1330
+ * const panel = render('section', 'surface muted')
362
1331
  * container.remove()
1332
+ * panel.remove()
363
1333
  * ```
364
1334
  */
365
1335
  export declare function render(markup: string): HTMLDivElement;
366
1336
 
1337
+ export declare function render<K extends keyof HTMLElementTagNameMap>(tag: K, classes: string): HTMLElementTagNameMap[K];
1338
+
367
1339
  /**
368
1340
  * Resolves one visible, focus-reachable interactive element by its exact accessible name. A
369
1341
  * wholly-off-viewport target is scrolled into view before reachability is measured.
@@ -421,12 +1393,109 @@ export declare function resolveAccessible(role: string, name: string): HTMLEleme
421
1393
  */
422
1394
  export declare function resolveRendered(first: string, second?: string): HTMLElement;
423
1395
 
1396
+ /**
1397
+ * Resolves any CSS color expression to straight sRGB channels, by asking the browser.
1398
+ *
1399
+ * @param value - Any value the `color` property accepts: a keyword, a hex triple, a `var()`
1400
+ * reference, a `color-mix()`, or an already-computed `rgb()`.
1401
+ * @returns The resolved color's channels, or `undefined` when the CSSOM refuses the value or the
1402
+ * computed result names no color {@link parseColor} speaks.
1403
+ *
1404
+ * @remarks
1405
+ * This is the live half of the pair {@link parseColor} opens. `parseColor` reads text and speaks
1406
+ * only the computed syntaxes a cascade hands back; this stages a probe element, hands it to the real
1407
+ * cascade, and reads back what the engine computed — which is the only way a keyword, a hex triple,
1408
+ * or a `var()` reference becomes channels at all. The read itself goes through `parseColor`, so both
1409
+ * halves agree on what a computed value means.
1410
+ *
1411
+ * The probe is mounted, because an unmounted element inherits nothing and a `var()` reference to a
1412
+ * token declared on `:root` would resolve to the initial value instead. It is removed in a `finally`,
1413
+ * so a value that throws on the way through leaves no node behind.
1414
+ *
1415
+ * Refusal is the CSSOM's: an expression it will not parse leaves the probe's inline `color` empty
1416
+ * and this returns `undefined`. A `var()` naming an undeclared custom property is not refused,
1417
+ * because the cascade accepts it and computes the inherited color, so a test that means to catch a
1418
+ * missing token asserts on {@link token} rather than on this.
1419
+ *
1420
+ * @example
1421
+ * ```ts
1422
+ * rgba('rebeccapurple') // [102, 51, 153, 1]
1423
+ * rgba('not-a-color') // undefined
1424
+ * ```
1425
+ */
1426
+ export declare function rgba(value: string): Color | undefined;
1427
+
1428
+ /**
1429
+ * Reads one custom property from the document element.
1430
+ *
1431
+ * @param name - The custom property name, with or without its leading dashes.
1432
+ * @returns The resolved value, trimmed; an empty string when the document declares no such property.
1433
+ *
1434
+ * @remarks
1435
+ * This is {@link token} against `document.documentElement`, which is where a theme declares its
1436
+ * tokens and where a `[data-theme]` switch retunes them. It exists as its own name because that
1437
+ * element is the one a token question is nearly always about, and naming it at every call site
1438
+ * buries the question.
1439
+ *
1440
+ * @example
1441
+ * ```ts
1442
+ * rootToken('surface')
1443
+ * ```
1444
+ */
1445
+ export declare function rootToken(name: string): string;
1446
+
1447
+ /**
1448
+ * Sets the tester's viewport and renders the runner's pane at the size that viewport claims.
1449
+ *
1450
+ * @param width - The viewport width in CSS pixels.
1451
+ * @param height - The viewport height in CSS pixels.
1452
+ * @returns A promise resolving after the resized pane has been painted.
1453
+ * @throws Thrown when the tester sits inside no pane a capture can size, and when the staged pane
1454
+ * does not render at the viewport it was given.
1455
+ *
1456
+ * @remarks
1457
+ * This depends on the runner's own tester layout, and that dependency is contract rather than an
1458
+ * accident: `vitest@4.1.11` lays its tester out inside a smaller page, fits it by scaling the pane
1459
+ * the tester sits in, and clips whatever overflows that pane. Layout inside the tester is
1460
+ * unaffected — the tester reports the viewport it was given and every breakpoint answers to it —
1461
+ * but a screenshot is taken off the page the runner painted, so a frame shot through that scale is
1462
+ * a thumbnail of the surface and a frame shot after only unscaling it is a sliver. The tester is
1463
+ * therefore unscaled and lifted to the window's own origin for the shot. The `iframe[data-vitest]`
1464
+ * selector and the `--tester-transform`, `--tester-margin-left`, `--viewport-width`, and
1465
+ * `--viewport-height` custom properties are the runner's, so a Vitest release that renames any of
1466
+ * them reddens the size check below rather than writing a wrong frame.
1467
+ *
1468
+ * Hand the pane straight back with {@link releasePane}. A tester pinned at a viewport taller than
1469
+ * the window puts its lower half beyond what a pointer can reach, so an ordinary press then fails
1470
+ * as a control outside the viewport, in a test that took no picture at all.
1471
+ *
1472
+ * The rule is declared rather than written inline, because the runner writes its own scale onto the
1473
+ * pane as inline custom properties and rewrites them whenever the tester resizes. A declared rule
1474
+ * marked important outranks an inline value and survives every rewrite. It finds the pane by the
1475
+ * tester it contains as well as by {@link CAPTURE_PANE}, because a re-render between the staging and
1476
+ * the shot replaces the node and takes any attribute of ours with it.
1477
+ *
1478
+ * The wait is two frames rather than a delay: the first carries the resize into layout and the
1479
+ * second is the paint a screenshot reads.
1480
+ *
1481
+ * @example
1482
+ * ```ts
1483
+ * await stagePane(390, 844)
1484
+ * ```
1485
+ */
1486
+ export declare function stagePane(width: number, height: number): Promise<void>;
1487
+
424
1488
  /**
425
1489
  * Reads one resolved CSS property from a real browser element.
426
1490
  *
427
1491
  * @param element - The element whose resolved style to inspect.
428
- * @param property - The CSS property name.
429
- * @returns The browser's resolved property value.
1492
+ * @param property - The CSS property name, registered or custom.
1493
+ * @returns The browser's resolved property value, trimmed; an empty string when the element resolves
1494
+ * none.
1495
+ *
1496
+ * @remarks
1497
+ * The value is trimmed, so what comes back is the value and never the whitespace around it. Internal
1498
+ * whitespace is kept: `--shadow: 0 0 2px` reads back with its spaces.
430
1499
  *
431
1500
  * @example
432
1501
  * ```ts
@@ -435,6 +1504,32 @@ export declare function resolveRendered(first: string, second?: string): HTMLEle
435
1504
  */
436
1505
  export declare function style(element: Element, property: string): string;
437
1506
 
1507
+ /**
1508
+ * Reads one custom property from an element's resolved style.
1509
+ *
1510
+ * @param element - The element whose resolved style to inspect.
1511
+ * @param name - The custom property name, with or without its leading dashes.
1512
+ * @returns The resolved value, trimmed; an empty string when the element inherits no such property.
1513
+ *
1514
+ * @remarks
1515
+ * The dashes are optional because a token is spoken about both ways — `--surface` in a stylesheet
1516
+ * and `surface` in prose — and a reader that accepted only one spelling would turn that into a silent
1517
+ * empty string. An absent token reads as `''`, which is what the CSSOM returns and is
1518
+ * indistinguishable from a token declared empty; assert on the value you expect rather than on
1519
+ * presence.
1520
+ *
1521
+ * Resolution is inheritance, so a token declared on `:root` reads from any mounted descendant and
1522
+ * from an unmounted element reads as `''`. Use {@link rootToken} where the declaration is the
1523
+ * document's.
1524
+ *
1525
+ * @example
1526
+ * ```ts
1527
+ * token(panel, 'surface') // '#ffffff'
1528
+ * token(panel, '--surface') // '#ffffff'
1529
+ * ```
1530
+ */
1531
+ export declare function token(element: Element, name: string): string;
1532
+
438
1533
  /**
439
1534
  * Reaches a named control only through natural forward Tab traversal from the current focus.
440
1535
  *
@@ -463,6 +1558,31 @@ export declare function traverseAccessible(name: string): Promise<HTMLElement>;
463
1558
  */
464
1559
  export declare function typeAccessible(name: string, text: string): Promise<void>;
465
1560
 
1561
+ /**
1562
+ * Sets one field's value and announces it the way typing into the field does.
1563
+ *
1564
+ * @param element - The input or textarea to write into.
1565
+ * @param text - The value to set.
1566
+ *
1567
+ * @remarks
1568
+ * This is the synthetic pair of {@link typeAccessible}, for a component that listens for `input` and
1569
+ * a test that has the element already. It sets the value in one write and dispatches one bubbling
1570
+ * `input` event, so a delegated listener on an ancestor hears it. It sends no keystrokes, so a
1571
+ * component reading `key`, composition, or selection sees nothing. The dispatched event is a plain
1572
+ * `Event`, never an `InputEvent`, so a component reading `inputType` or testing
1573
+ * `instanceof InputEvent` sees neither. Drive a component that reads any of those through
1574
+ * `typeAccessible` instead.
1575
+ *
1576
+ * No `change` event follows. Use {@link commitInput} where the component waits for the field to be
1577
+ * committed.
1578
+ *
1579
+ * @example
1580
+ * ```ts
1581
+ * typeInput(requireValue(container.querySelector('input')), 'Ada')
1582
+ * ```
1583
+ */
1584
+ export declare function typeInput(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
1585
+
466
1586
  /**
467
1587
  * Waits for one animation frame to settle pending browser paint work.
468
1588
  *