@orkestrel/test 0.0.16 → 0.0.18

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.
@@ -23,6 +23,11 @@
23
23
  * @packageDocumentation
24
24
  */
25
25
 
26
+ import type { JourneyVariant } from '@orkestrel/test';
27
+ import type { StatechartStatus } from '@orkestrel/test';
28
+ import type { StateScenario } from '@orkestrel/test';
29
+ import type { WaitOptions } from '@orkestrel/test';
30
+
26
31
  /**
27
32
  * Names the interactive ARIA roles a bare accessible name is searched across.
28
33
  *
@@ -69,6 +74,117 @@ export declare function blendColor(front: Color, back: Color): Color;
69
74
  */
70
75
  export declare function build<K extends keyof HTMLElementTagNameMap>(tag: K, options?: ElementOptions): HTMLElementTagNameMap[K];
71
76
 
77
+ /**
78
+ * Builds detached markup carrying one undeclared class token on HTML and another on SVG.
79
+ *
80
+ * @returns The detached root, the token the HTML element carries, and the one the SVG carries.
81
+ *
82
+ * @remarks
83
+ * The SVG element is the trap a census has to survive: `className` on an SVG element is an
84
+ * `SVGAnimatedString` rather than a string, so a reader splitting that value finds nothing and
85
+ * reports one undeclared token where two are carried. {@link readCensus} reads every element
86
+ * through `classList`, and this is the control that proves it.
87
+ *
88
+ * The two tokens are returned rather than written into a caller's expectation, so a cascade that
89
+ * later declares one of these names moves the fixture and the assertion together. Each token also
90
+ * carries a suffix drawn per call from `crypto.getRandomValues`, so a consumer cascade cannot
91
+ * declare either of them in advance and two controls in one document never share a token.
92
+ * `getRandomValues` rather than `randomUUID`, because that one answers outside a secure context
93
+ * too, and a browser project served from a remote host is not one.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * const control = buildCensus()
98
+ * readCensus(control.root).undeclared // [control.mark, control.token], sorted
99
+ * ```
100
+ */
101
+ export declare function buildCensus(): CensusFixture;
102
+
103
+ /**
104
+ * Builds a detached translucent stack whose composited and flat contrast readings straddle one bar.
105
+ *
106
+ * @param bar - The contrast ratio `refused` and `accepted` must sit on opposite sides of.
107
+ * @returns The opaque root carrying the tint, and the refused and accepted foregrounds under it.
108
+ * @throws An `Error` when no grey foreground puts the two readings on opposite sides of the bar.
109
+ *
110
+ * @remarks
111
+ * A contrast instrument that never composites still clears every fixture painting its own opaque
112
+ * background, so this is the control that makes {@link readContrast}'s ancestor walk and alpha
113
+ * blend the thing under test. The stack is an opaque floor, a translucent tint over it, and two
114
+ * grey foregrounds inside the tint. `refused` reads under the bar composited and at or over it
115
+ * flat, and `accepted` reads the other way about, so no single non-compositing reading satisfies
116
+ * both.
117
+ *
118
+ * The greys are searched rather than written down, so the control follows the bar it was asked for.
119
+ * A bar at or under `1` is refused because every contrast ratio reaches `1`, and a bar above what
120
+ * the tinted surface can reach is refused because no foreground clears it — each refusal names the
121
+ * bar rather than returning a stack that proves nothing.
122
+ *
123
+ * The nodes are detached, so nothing is mounted for you. Append `root` to the surface you are
124
+ * reading, take both readings, and remove it: a computed color needs the document, and a fixture
125
+ * left behind is the next test's resolver ambiguity.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const control = buildContrast(4.5)
130
+ * mount(control.root)
131
+ * readContrast(control.refused) < 4.5 // true
132
+ * readContrast(control.accepted) >= 4.5 // true
133
+ * control.root.remove()
134
+ * ```
135
+ */
136
+ export declare function buildContrast(bar: number): ContrastFixture;
137
+
138
+ /**
139
+ * Builds the refusal a host withholding a storage operation raises.
140
+ *
141
+ * @param operation - The withheld operation, named as the `Storage` interface names it.
142
+ * @param key - The storage key the operation addressed. Omit it for an operation that takes none.
143
+ * @returns The refusal, unthrown.
144
+ *
145
+ * @remarks
146
+ * A browser with site data blocked, a sandboxed frame, and a hardened privacy mode all raise a
147
+ * `DOMException` named `SecurityError` from the storage object rather than answering, so this is
148
+ * the voice rather than a message of this package's. {@link createStorage} raises it from every
149
+ * operation the permission withholds; it is exported because a fixture implementing `Storage` some
150
+ * other way needs the same voice rather than a second spelling of it.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * buildDenial('getItem', 'theme').message // 'Access is denied for getItem "theme"'
155
+ * buildDenial('length').name // 'SecurityError'
156
+ * ```
157
+ */
158
+ export declare function buildDenial(operation: string, key?: string): DOMException;
159
+
160
+ /**
161
+ * Builds detached markup carrying one style escape of each kind, plus the sheet a project allows.
162
+ *
163
+ * @param permitted - The `id` the caller's reading exempts, placed on the third element.
164
+ * @returns The detached root, the inline escape, the embedded escape, and the exempt sheet.
165
+ *
166
+ * @remarks
167
+ * {@link extractStyles} has two branches — an inline `style` attribute and a `<style>` element —
168
+ * and a reading fed only the first never exercises the second. The exempt sheet is the other half
169
+ * of the control: a project that allows one standalone stylesheet writes an exemption for its id,
170
+ * and a reading that passes by refusing every `<style>` element clears the two escapes and fails
171
+ * that exemption.
172
+ *
173
+ * The declaration `inline`, `embedded`, and `permitted` carry is this package's, so assert on which
174
+ * elements are reported rather than on what they declare.
175
+ *
176
+ * The nodes are detached and stay that way: `extractStyles` takes any `ParentNode`, so the reading
177
+ * runs without mounting, and an embedded sheet that reached the document would join the cascade
178
+ * every other reading measures against.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * const control = buildEscapes('project-stylesheet')
183
+ * extractStyles(control.root).length // 3 — the inline escape, the embedded sheet, and the exempt one
184
+ * ```
185
+ */
186
+ export declare function buildEscapes(permitted: string): EscapeFixture;
187
+
72
188
  /**
73
189
  * Names the color a browser paints an unstyled document with: opaque white.
74
190
  *
@@ -169,14 +285,16 @@ export declare const CAPTURE_STAGINGS = 4;
169
285
  */
170
286
  export declare function captureFrame(options: FrameOptions): Promise<string>;
171
287
 
172
- /** Represents one theme-and-viewport pair a capture run renders, and the document change it needs first. */
173
- export declare interface CaptureVariant {
174
- /** Holds the variant's name, which is the second half of every filename the run writes. */
175
- readonly name: string;
176
- /** Holds the viewport width in pixels. */
177
- readonly width: number;
178
- /** Holds the viewport height in pixels. */
179
- readonly height: number;
288
+ /**
289
+ * Adds to a journey variant the document change a capture run applies before resizing.
290
+ *
291
+ * @remarks
292
+ * The name, the width, and the height come from {@link JourneyVariant}, which a project
293
+ * configuration can serialize and hand to the suite that renders it. The change this adds is a
294
+ * function, so it belongs to the suite rather than to that configuration: a variant a configuration
295
+ * declares and a variant a capture run renders are the same row read at two depths.
296
+ */
297
+ export declare interface CaptureVariant extends JourneyVariant {
180
298
  /**
181
299
  * Holds the document change this variant needs before the viewport is resized — a theme
182
300
  * attribute, a density class, a language direction. Omit it when the variant is a viewport alone.
@@ -184,6 +302,41 @@ export declare interface CaptureVariant {
184
302
  readonly apply?: () => void;
185
303
  }
186
304
 
305
+ /**
306
+ * Holds detached markup an authored-class census must report as undeclared.
307
+ *
308
+ * @remarks
309
+ * One token rides on an HTML element and the other on an SVG element, because `className` on an SVG
310
+ * element is an `SVGAnimatedString` rather than a string: a census splitting that value finds
311
+ * nothing and reports one token where two are carried.
312
+ */
313
+ export declare interface CensusFixture {
314
+ /** Holds the detached root carrying both marked elements. */
315
+ readonly root: HTMLElement;
316
+ /** Holds the class token the HTML element carries. */
317
+ readonly token: string;
318
+ /** Holds the class token the SVG element carries. */
319
+ readonly mark: string;
320
+ }
321
+
322
+ /**
323
+ * Reports an authored-class census: the population walked, the tokens found, and the undeclared.
324
+ *
325
+ * @remarks
326
+ * The population is reported beside the finding because an empty walk satisfies every difference
327
+ * check: a census that read no element reports no undeclared token, and so does a census over
328
+ * markup whose every class the cascade declares. Assert on `elements` as well as on `undeclared`
329
+ * and the two cannot be confused.
330
+ */
331
+ export declare interface CensusReading {
332
+ /** Reports how many elements the walk read, the root included when it is an element. */
333
+ readonly elements: number;
334
+ /** Lists every class token the markup carries, sorted. */
335
+ readonly tokens: readonly string[];
336
+ /** Lists every carried token no loaded stylesheet declares, sorted. */
337
+ readonly undeclared: readonly string[];
338
+ }
339
+
187
340
  /**
188
341
  * Clears both browser storage surfaces.
189
342
  *
@@ -335,6 +488,24 @@ export declare function computeNamePattern(name: string): RegExp;
335
488
  */
336
489
  export declare const CONTENT_ROLES: readonly string[];
337
490
 
491
+ /**
492
+ * Holds a detached translucent stack whose flat and composited readings disagree across one bar.
493
+ *
494
+ * @remarks
495
+ * `refused` and `accepted` are the control a composited-contrast reading owes: the composited
496
+ * reading refuses one and the flat reading clears it, and the other way about for the second. A reader that takes the
497
+ * nearest declared background at full strength answers the opposite pair, so no single
498
+ * non-compositing reading satisfies both.
499
+ */
500
+ export declare interface ContrastFixture {
501
+ /** Holds the opaque floor carrying the translucent tint; append this to read either foreground. */
502
+ readonly root: HTMLElement;
503
+ /** Holds the foreground whose composited reading falls under the bar. */
504
+ readonly refused: HTMLElement;
505
+ /** Holds the foreground whose composited reading reaches the bar. */
506
+ readonly accepted: HTMLElement;
507
+ }
508
+
338
509
  /**
339
510
  * Creates one console channel that records every call it receives and hands that call on unchanged.
340
511
  *
@@ -389,6 +560,59 @@ export declare function createChannel(name: string, output: string[], forward: (
389
560
  */
390
561
  export declare function createDragEvent(name: string, options?: DragEventInit): DragEvent;
391
562
 
563
+ /**
564
+ * Creates a mounted statechart harness that renders one transition table and drives it row by row.
565
+ *
566
+ * @typeParam TState - The states the entity moves between.
567
+ * @typeParam TEvent - The events the entity accepts.
568
+ * @typeParam TContext - The fixture each row drives.
569
+ * @param options - The table, the fixture builder, the state reader, and the delay between rows.
570
+ * @returns The mounted harness, standing idle with its tally at zero.
571
+ * @throws An `Error` reading `Statechart harness mounted no transition` for an empty table, before
572
+ * anything reaches the document.
573
+ *
574
+ * @remarks
575
+ * A page cannot import this package, because the browser entry imports `vitest/browser` at module
576
+ * scope. So the harness is test-side: the suite mounts it, a gate outside the page polls the
577
+ * markup it renders, and `STATECHART_ATTRIBUTES` is the whole contract between the two. Nothing
578
+ * here spells a `data-statechart-*` string of its own, and neither does a gate. That gate has no
579
+ * rejection channel, so every exit writes a terminal status: a run that completes writes `passed` or
580
+ * `failed`, and a run that a `state` reader or a non-`Error` phase throw ends writes `failed` and
581
+ * then rejects with that value by identity, without counting the row as failed.
582
+ *
583
+ * The markup is framework-free. The root carries `status` and the tally; a `role="status"`
584
+ * announcer narrates each step in a sentence; one element carries `state` and renders what the
585
+ * entity's own reader reports; and an ordered list carries one row per scenario, each labelled with
586
+ * its `from`, its `event`, and its `to` and marked with the transition's name. The `state` element
587
+ * mounts empty and takes its attribute from the first row that produces a context, because a state
588
+ * is read from an entity and no entity exists until a row builds one.
589
+ *
590
+ * Construction writes `pending`, mounts the root, renders every row, then writes the row count and
591
+ * `idle` — so a gate that reads `pending` has found a harness whose rows never mounted, and the
592
+ * order is observable from outside through the mutations the document records.
593
+ *
594
+ * `execute` clears every rendered result and the rendered state, writes `running`, and drives each
595
+ * row in order through {@link executeScenario} against a context of that row's own. It continues
596
+ * past a failing row, so one run reports on the whole table rather than stopping at the first
597
+ * finding, and a builder that throws counts as its row failing under {@link buildRefusal}'s
598
+ * sentence, which is the one `executeScenarios` raises. What decides whether a row's phases run is
599
+ * whether its builder returned, not what it returned, so a table whose context is `undefined` drives
600
+ * every phase. A second `execute` runs the same table from a fresh tally and a cleared state.
601
+ *
602
+ * Every reading comes off the markup, so the object and the page cannot disagree, and `failures` is
603
+ * the `scenario` name of each row whose rendered `result` reads `failed` rather than a second list
604
+ * beside them.
605
+ *
606
+ * @example
607
+ * ```ts
608
+ * const harness = createHarness({ scenarios: SCENARIOS, build: buildDisclosure, state: readState })
609
+ * await harness.execute()
610
+ * harness.status // 'passed'
611
+ * harness.destroy()
612
+ * ```
613
+ */
614
+ export declare function createHarness<TState extends string, TEvent extends string, TContext>(options: HarnessOptions<TState, TEvent, TContext>): HarnessInterface;
615
+
392
616
  /**
393
617
  * Creates the journal one scenario records its steps and the page's own output into.
394
618
  *
@@ -470,6 +694,50 @@ export declare function createPointerEvent(name: string, options?: PointerEventI
470
694
  */
471
695
  export declare function createPortfolio(options: PortfolioOptions): PortfolioInterface;
472
696
 
697
+ /**
698
+ * Creates an inert `Storage` a host can withhold, grant, and run out of room in.
699
+ *
700
+ * @param options - The seed, the read and write permissions, and the quota.
701
+ * @returns A store carrying the Web Storage surface plus the grant.
702
+ * @throws An `Error` when `quota` is not a non-negative safe integer. A quota above
703
+ * `Number.MAX_SAFE_INTEGER` carries the same sentence, because a counter that cannot decrement past
704
+ * that point bounds nothing.
705
+ *
706
+ * @remarks
707
+ * Conditions a real origin produces are unreachable from a test otherwise: a browser with
708
+ * site data blocked refuses every operation the permission withholds, an origin with no room left
709
+ * refuses `setItem`, and a person allowing site data grants what was withheld. This makes each of
710
+ * them reachable against a real `Storage` surface rather than a shaped object.
711
+ *
712
+ * The store answers through its methods and intercepts no named-property access, so drive a
713
+ * consumer under test through `getItem` and `setItem`. `Storage` declares an index signature, so
714
+ * `store.theme` typechecks and reads `undefined` while `getItem('theme')` answers, and a property
715
+ * write lands on the object rather than in the store, consuming no quota and meeting no refusal.
716
+ *
717
+ * It is backed by a map of its own and patches nothing: `localStorage` and `sessionStorage` are
718
+ * untouched, no `storage` event is dispatched, and the store is reached only by the code the test
719
+ * hands it to. Reach for {@link clearStorage} where the real browser surfaces are the subject.
720
+ *
721
+ * `length`, `key`, and `getItem` are reads; `clear`, `removeItem`, and `setItem` are writes. A
722
+ * withheld operation raises {@link buildDenial}'s `SecurityError`, and `permit` lifts both
723
+ * permissions at once, the way a person allowing site data lifts them. Reads answer from what the
724
+ * store actually accepted, so a journey reads what the application kept while the host was refusing.
725
+ *
726
+ * `quota` counts accepted `setItem` calls rather than bytes, because the number of writes is what a
727
+ * journey scripts and a byte budget is the browser's own arithmetic. `removeItem` consumes none of
728
+ * it, and `permit` replenishes none of it: room and permission are different refusals, and a test
729
+ * that granted the permission still meets the full origin.
730
+ *
731
+ * @example
732
+ * ```ts
733
+ * const storage = createStorage({ values: { theme: 'dark' }, writes: false })
734
+ * storage.getItem('theme') // 'dark'
735
+ * storage.permit()
736
+ * storage.setItem('theme', 'light')
737
+ * ```
738
+ */
739
+ export declare function createStorage(options?: StorageOptions): WebStorageInterface;
740
+
473
741
  /**
474
742
  * Describes the order sequential keyboard navigation visits one element's controls in.
475
743
  *
@@ -538,6 +806,25 @@ export declare interface ElementOptions {
538
806
  readonly attributes?: Readonly<Record<string, string>>;
539
807
  }
540
808
 
809
+ /**
810
+ * Holds detached markup a style-escape reading must find, and the one it must leave alone.
811
+ *
812
+ * @remarks
813
+ * `inline`, `embedded`, and `permitted` are the branches a style-escape reading has: an inline
814
+ * attribute, an embedded element, and the sheet a project deliberately allows. A reading that passes
815
+ * by refusing every `<style>` element clears the escapes and fails the exemption.
816
+ */
817
+ export declare interface EscapeFixture {
818
+ /** Holds the detached root carrying the inline escape, the embedded escape, and the exempt sheet. */
819
+ readonly root: HTMLElement;
820
+ /** Holds the element carrying an inline `style` attribute. */
821
+ readonly inline: HTMLElement;
822
+ /** Holds the embedded `<style>` element. */
823
+ readonly embedded: HTMLElement;
824
+ /** Holds the `<style>` element carrying the exempt id the caller named. */
825
+ readonly permitted: HTMLElement;
826
+ }
827
+
541
828
  /**
542
829
  * Expands a capture registry across every variant into the filenames a complete portfolio holds.
543
830
  *
@@ -726,6 +1013,75 @@ export declare interface FrameReading {
726
1013
  readonly floor: string | undefined;
727
1014
  }
728
1015
 
1016
+ /**
1017
+ * Holds a mounted statechart harness, the tally it publishes, and the run it drives.
1018
+ *
1019
+ * @remarks
1020
+ * Every reading comes off the mounted markup rather than out of a field beside it, so the object a
1021
+ * test asserts on and the attributes a gate polls cannot disagree. `status`, `total`, `passed`, and
1022
+ * `failed` read the root's own attributes, and `failures` reads the `scenario` name of every row
1023
+ * whose `result` reads `failed`, so no second list can drift from the rows.
1024
+ *
1025
+ * `root` is the mounted element, handed out so a test can read the markup a gate would read.
1026
+ * `failures` hands out a snapshot, so a list read mid-run stays what it was.
1027
+ */
1028
+ export declare interface HarnessInterface {
1029
+ /** Holds the mounted root carrying the status, the tally, and every row. */
1030
+ readonly root: HTMLElement;
1031
+ /** Reports the run state the root announces. */
1032
+ readonly status: StatechartStatus;
1033
+ /** Reports how many rows the table declares. */
1034
+ readonly total: number;
1035
+ /** Reports how many rows the last run finished with a passing result. */
1036
+ readonly passed: number;
1037
+ /** Reports how many rows the last run finished with a failing result. */
1038
+ readonly failed: number;
1039
+ /** Lists the name of every row whose rendered result reads failed, in table order. */
1040
+ readonly failures: readonly string[];
1041
+ /**
1042
+ * Drives every row in table order, from a fresh tally and a cleared state.
1043
+ *
1044
+ * @returns A promise resolving after the last row settles and the terminal status is written.
1045
+ * @throws The value a `state` reader or a non-`Error` phase threw, by identity, after the harness
1046
+ * writes `failed`. Every exit is terminal, because the gate polling the markup has no rejection
1047
+ * channel to read.
1048
+ */
1049
+ execute(): Promise<void>;
1050
+ /** Removes the mounted root, and does nothing when it is already removed. */
1051
+ destroy(): void;
1052
+ }
1053
+
1054
+ /**
1055
+ * Configures the harness that renders one transition table and drives it row by row.
1056
+ *
1057
+ * @typeParam TState - The states the entity moves between, as a string-literal union.
1058
+ * @typeParam TEvent - The events the entity accepts, as a string-literal union.
1059
+ * @typeParam TContext - The fixture the three phases of each row drive.
1060
+ * @remarks
1061
+ * `scenarios` is the table in the order it renders and the order it runs, so the rows a reader sees
1062
+ * are the rows the run walks. `build` is called once per row, the way `executeScenarios` calls it,
1063
+ * so one table can mix fixtures and a row never inherits the entity the row before it left behind.
1064
+ *
1065
+ * `state` is the reader that puts the entity's current state on the page. It is called with the
1066
+ * row's own context after that row settles, pass or fail, so the rendered state is where the event
1067
+ * actually left the entity rather than where the row expected it. It is a reader rather than a
1068
+ * phase: a `state` that throws rejects the run after the harness writes `failed`, and the row it was
1069
+ * reading is not counted as failed.
1070
+ *
1071
+ * `pause` holds a delay between rows, for a table whose entity is worth watching. Omit it and the
1072
+ * rows run back to back.
1073
+ */
1074
+ export declare interface HarnessOptions<TState extends string, TEvent extends string, TContext> {
1075
+ /** Holds the table to render and drive, in the order it is written. */
1076
+ readonly scenarios: ReadonlyArray<StateScenario<TState, TEvent, TContext>>;
1077
+ /** Builds the fixture one row drives, called once per row and awaited when it returns a promise. */
1078
+ readonly build: (scenario: StateScenario<TState, TEvent, TContext>) => TContext | Promise<TContext>;
1079
+ /** Reads the state the entity is in, for the element the harness renders it on. */
1080
+ readonly state: (context: TContext) => TState;
1081
+ /** Holds the delay in milliseconds the harness waits between rows. */
1082
+ readonly pause?: number;
1083
+ }
1084
+
729
1085
  /**
730
1086
  * Names the role a `th` carries for the header axis its `scope` names.
731
1087
  *
@@ -777,8 +1133,9 @@ export declare function isOutsideViewport(rectangle: DOMRectReadOnly): boolean;
777
1133
  *
778
1134
  * @param element - The element to judge.
779
1135
  * @returns True if the element is connected, visible, laid out with a non-zero box, in the
780
- * sequential focus order, neither disabled nor marked `aria-disabled="true"`, and outside every
781
- * `[inert]` subtree; false otherwise.
1136
+ * sequential focus order, neither disabled nor marked `aria-disabled="true"`, outside every
1137
+ * `[inert]` subtree, and inside every shown `[aria-modal="true"]` element its document carries;
1138
+ * false otherwise.
782
1139
  *
783
1140
  * @remarks
784
1141
  * This is the one reachability filter the layer applies. `resolveRendered`, `clickAccessibleWithin`,
@@ -790,6 +1147,29 @@ export declare function isOutsideViewport(rectangle: DOMRectReadOnly): boolean;
790
1147
  * refuses it. Nothing here asks about the viewport: `resolveAccessible` scrolls a wholly
791
1148
  * off-viewport target into view and measures that separately with {@link isOutsideViewport}.
792
1149
  *
1150
+ * An open modal dialog takes the page behind it away, and the element's own facts cannot report
1151
+ * that: the covered control stays connected, laid out, focusable, and outside every `[inert]`
1152
+ * subtree while a pointer, a Tab, and a reader honouring `aria-modal` all stop at the dialog. So
1153
+ * this asks the document for its `[aria-modal="true"]` elements, puts each through
1154
+ * {@link isRendered}, and refuses the subject wherever a shown one does not contain it. A closed
1155
+ * dialog is hidden and excludes nothing, which is why the visibility reading is the announced one
1156
+ * rather than a bare `checkVisibility` call: a drawer parked at `visibility: hidden` is still laid
1157
+ * out. Nesting needs no separate rule, because an element inside the innermost dialog sits inside
1158
+ * every dialog around it.
1159
+ *
1160
+ * Containment follows the flat tree, so a subject inside a shadow tree is judged by its host chain
1161
+ * as well as itself and a dialog holding the host holds its shadow content too. Two arrangements
1162
+ * stay outside the read: a native `<dialog>` opened with `showModal` carries no `aria-modal`
1163
+ * attribute, and a dialog declared inside a shadow tree is not in what a document query returns.
1164
+ * Each leaves the page behind it reachable here.
1165
+ *
1166
+ * Inside a shadow tree it answers for the element's own facts, in an open root and a closed one
1167
+ * alike: the box, the focus order, `:disabled`, and `aria-disabled` are all the element's. The
1168
+ * `[inert]` ancestor is the one read that stops at the boundary, because `closest` never leaves the
1169
+ * element's own tree, so a host marked `[inert]` is invisible here. What the flat tree decides still
1170
+ * reaches the subject — a host the document does not lay out takes the element off the page and this
1171
+ * refuses it. Ask the host separately where an ancestor attribute is the subject.
1172
+ *
793
1173
  * @example
794
1174
  * ```ts
795
1175
  * isReachable(requireValue(container.querySelector('button')))
@@ -816,6 +1196,13 @@ export declare function isReachable(element: Element): boolean;
816
1196
  * for the box tree, and `visibility` inherits, so between them an ancestor cannot hide a control
817
1197
  * from a reader and leave it standing in a description.
818
1198
  *
1199
+ * Inside a shadow tree it answers for the element's own facts, in an open root and a closed one
1200
+ * alike. The `aria-hidden` ancestor is the one read that stops at the boundary, because `closest`
1201
+ * never leaves the element's own tree, so a host marked `aria-hidden="true"` is invisible here and
1202
+ * this reports `true` for a subject a reader is never told about. `checkVisibility` and the computed
1203
+ * `visibility` read the flat tree, so a host the document does not lay out still takes the element
1204
+ * off the page. Ask the host separately where an ancestor attribute is the subject.
1205
+ *
819
1206
  * @example
820
1207
  * ```ts
821
1208
  * isRendered(requireValue(container.querySelector('[aria-hidden="true"] button'))) // false
@@ -1094,6 +1481,32 @@ export declare interface PortfolioOptions {
1094
1481
  readonly enabled?: boolean;
1095
1482
  }
1096
1483
 
1484
+ /**
1485
+ * Sends a key sequence to whatever holds focus, and refuses to send it to nothing.
1486
+ *
1487
+ * @param keys - The sequence in the provider's own key syntax, such as `{Enter}` or `{Escape}`.
1488
+ * @returns A promise resolving after every keystroke completes.
1489
+ * @throws When the document body holds focus, or nothing does.
1490
+ *
1491
+ * @remarks
1492
+ * The refusal is the whole of what this adds over `userEvent.keyboard`. A key sent while focus sits
1493
+ * on the body reaches no control, and every assertion after it reads the surface the key never
1494
+ * touched — which is the false green a guarded keyboard step exists to catch. Bring focus about
1495
+ * first through {@link traverseAccessible}, {@link clickAccessible}, or {@link typeAccessible}, and
1496
+ * send the sequence here.
1497
+ *
1498
+ * Escaping is the caller's, because the sequence is the subject: `{` opens a key name and `[` opens
1499
+ * a code name. Reach for {@link typeAccessible} where the text is the subject and the syntax is in
1500
+ * the way.
1501
+ *
1502
+ * @example
1503
+ * ```ts
1504
+ * await traverseAccessible('Evaluate')
1505
+ * await pressKeys('{Enter}')
1506
+ * ```
1507
+ */
1508
+ export declare function pressKeys(keys: string): Promise<void>;
1509
+
1097
1510
  /**
1098
1511
  * Resolves the opaque color standing behind one element.
1099
1512
  *
@@ -1148,6 +1561,33 @@ export declare function readBackdrop(element: Element, floor: Color): Color;
1148
1561
  */
1149
1562
  export declare function readCascade(): ReadonlySet<string>;
1150
1563
 
1564
+ /**
1565
+ * Takes the authored-class census of one subtree against the cascade this document loaded.
1566
+ *
1567
+ * @param root - The subtree to walk. A detached element and a `DocumentFragment` both work.
1568
+ * @returns The population walked, every class token the markup carries, and every one of them no
1569
+ * loaded stylesheet declares; both lists sorted.
1570
+ * @throws An `Error` when the walk reads no element at all.
1571
+ *
1572
+ * @remarks
1573
+ * This is {@link readClasses} differenced against {@link readCascade}, with the population reported
1574
+ * beside the difference. The population is what makes the reading falsifiable: an empty walk
1575
+ * reports no undeclared token, and so does a subtree whose every class the cascade declares, so a
1576
+ * check reading `undeclared` alone passes for a census that read nothing. The empty walk is refused
1577
+ * outright for the same reason.
1578
+ *
1579
+ * The root counts when it is an `Element`, so a `DocumentFragment` contributes its descendants
1580
+ * alone. Both lists are sorted rather than left in sighting order, because a census is compared
1581
+ * against a previous one or against an expected list, and document order is not a fact about the
1582
+ * classes.
1583
+ *
1584
+ * @example
1585
+ * ```ts
1586
+ * readCensus(container).undeclared // ['lead'] — no loaded stylesheet declares it
1587
+ * ```
1588
+ */
1589
+ export declare function readCensus(root: ParentNode): CensusReading;
1590
+
1151
1591
  /**
1152
1592
  * Collects every class token the markup under one root carries.
1153
1593
  *
@@ -1424,6 +1864,49 @@ export declare function readPerception(name: string): string;
1424
1864
  */
1425
1865
  export declare function readPixels(element: Element, property: string): number;
1426
1866
 
1867
+ /**
1868
+ * Reads the refusal one named target answers with, or nothing when it resolves.
1869
+ *
1870
+ * @param name - The target's exact accessible name.
1871
+ * @returns The refusal sentence {@link resolveRendered} raised, or `undefined` when it resolved.
1872
+ * @throws Whatever the resolver threw that is not an `Error`.
1873
+ *
1874
+ * @example
1875
+ * ```ts
1876
+ * readRefusal('Save changes') // undefined — the control resolves
1877
+ * readRefusal('Menu') // 'Interactive target "Menu" is not visible and focus-reachable'
1878
+ * ```
1879
+ */
1880
+ export declare function readRefusal(name: string): string | undefined;
1881
+
1882
+ /**
1883
+ * Reads the refusal one named target answers with under an exact role, or nothing when it resolves.
1884
+ *
1885
+ * @param role - The target's exact ARIA role.
1886
+ * @param name - The target's exact accessible name.
1887
+ * @returns The refusal sentence {@link resolveRendered} raised, or `undefined` when it resolved.
1888
+ * @throws Whatever the resolver threw that is not an `Error`.
1889
+ *
1890
+ * @remarks
1891
+ * Absent, present-but-gated, and ambiguous are different findings about an interface, and the
1892
+ * layer keeps their sentences distinct, so a journey asserting on the one it means needs the
1893
+ * sentence rather than a boolean. This fixes the resolver, translates the `unknown` a `catch` binds
1894
+ * into `string | undefined`, and rethrows anything that is not an `Error` — a value no resolver
1895
+ * raises, and one a caller reading a message would otherwise lose. `undefined` is rethrown with the
1896
+ * rest: a resolver that returned and a hostile getter that threw `undefined` are different findings,
1897
+ * which is why the `catch` is this function's own rather than a captured thrown value.
1898
+ *
1899
+ * It resolves rather than acts, so a target it reports `undefined` for is one an acting verb
1900
+ * reaches. Assert on the exact sentence: a comparison against a substring passes for a refusal
1901
+ * about a different condition.
1902
+ *
1903
+ * @example
1904
+ * ```ts
1905
+ * readRefusal('tab', 'Drafts') // undefined — the tab resolves under its role
1906
+ * ```
1907
+ */
1908
+ export declare function readRefusal(role: string, name: string): string | undefined;
1909
+
1427
1910
  /**
1428
1911
  * Measures the contrast the focus chrome painted on one control reaches against its own backdrop.
1429
1912
  *
@@ -1868,6 +2351,42 @@ export declare function resolveRendered(first: string, second?: string): HTMLEle
1868
2351
  */
1869
2352
  export declare function stagePane(width: number, height: number): Promise<void>;
1870
2353
 
2354
+ /**
2355
+ * Configures a bounded wait over the states a control announces.
2356
+ *
2357
+ * @remarks
2358
+ * The direction is a boolean because a state is either announced or it is not, and both directions
2359
+ * are the same wait over the same reading. The time bounds and the abort signal come from
2360
+ * {@link WaitOptions} and mean there what they mean everywhere else.
2361
+ */
2362
+ export declare interface StateOptions extends WaitOptions {
2363
+ /** Determines the direction: `true` waits until the state is gone, `false` until it appears. */
2364
+ readonly absent?: boolean;
2365
+ }
2366
+
2367
+ /**
2368
+ * Configures an inert `Storage`: its seed, which operations the host permits, and its quota.
2369
+ *
2370
+ * @remarks
2371
+ * Every member describes a condition a real origin produces. A withheld read or write is what a
2372
+ * browser with site data blocked raises from the storage object, and a quota is what an origin with
2373
+ * no room left raises from `setItem`. Omit a member and the store behaves as an ordinary origin
2374
+ * does: seeded with nothing, permitting everything, and bounded by nothing.
2375
+ */
2376
+ export declare interface StorageOptions {
2377
+ /** Holds the entries the store starts with, keyed by storage key. */
2378
+ readonly values?: Readonly<Record<string, string>>;
2379
+ /** Determines whether the host permits reads. Default: `true`. */
2380
+ readonly reads?: boolean;
2381
+ /** Determines whether the host permits writes. Default: `true`. */
2382
+ readonly writes?: boolean;
2383
+ /**
2384
+ * Caps the accepted `setItem` calls, as a non-negative safe integer. When omitted, nothing bounds
2385
+ * the store.
2386
+ */
2387
+ readonly quota?: number;
2388
+ }
2389
+
1871
2390
  /**
1872
2391
  * Reaches a named control only through natural forward Tab traversal from the current focus.
1873
2392
  *
@@ -1925,6 +2444,43 @@ export declare function typeAccessible(name: string, text: string): Promise<void
1925
2444
  */
1926
2445
  export declare function typeInput(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
1927
2446
 
2447
+ /**
2448
+ * Waits until every finite animation on one element and its subtree has stopped moving.
2449
+ *
2450
+ * @param element - The element whose own animations and descendants' animations to wait on.
2451
+ * @param options - The time bounds and the abort signal.
2452
+ * @returns A promise resolving once no finite animation is still running.
2453
+ * @throws An `Error` when the element is not in a document, when a bound is invalid, or when an
2454
+ * animation is still running at the budget; or the abort reason.
2455
+ *
2456
+ * @remarks
2457
+ * A reading taken while paint is moving reports an interpolated frame — a `background-color` at a
2458
+ * fraction of its alpha, a `color` part way between two values — that no state of the interface
2459
+ * ever paints. This waits for the paint a person sees, whichever state it settles in.
2460
+ *
2461
+ * It parks on each animation's own `finished` promise rather than re-reading on a timer, and reads
2462
+ * the list again after each completion or cancellation, so an animation a finishing one starts is
2463
+ * waited on too.
2464
+ *
2465
+ * Some animations are left out, and each exclusion is a decision rather than an oversight. An animation
2466
+ * whose effect declares infinite iterations never finishes, so a spinner that runs forever is a
2467
+ * finding about the reading rather than a wait to lengthen. A finished animation filling its target
2468
+ * stays in the list a browser reports and is already at rest. A paused animation is at rest too,
2469
+ * and nothing here resumes it.
2470
+ *
2471
+ * The bounds are the wait family's, validated the same way. Default budget: `1000` milliseconds.
2472
+ * The interval is validated for consistency with the family and is not used, because this parks on
2473
+ * the animations. A detached element is refused rather than reported settled, because an element in
2474
+ * no document runs no animation and would answer `true` to every wait.
2475
+ *
2476
+ * @example
2477
+ * ```ts
2478
+ * await clickAccessible('Dark')
2479
+ * await waitForAnimations(document.body)
2480
+ * ```
2481
+ */
2482
+ export declare function waitForAnimations(element: Element, options?: WaitOptions): Promise<void>;
2483
+
1928
2484
  /**
1929
2485
  * Waits for one animation frame to settle pending browser paint work.
1930
2486
  *
@@ -1937,4 +2493,75 @@ export declare function typeInput(element: HTMLInputElement | HTMLTextAreaElemen
1937
2493
  */
1938
2494
  export declare function waitForFrame(): Promise<void>;
1939
2495
 
2496
+ /**
2497
+ * Waits until one named control announces a state, or stops announcing it.
2498
+ *
2499
+ * @param name - The control's exact accessible name.
2500
+ * @param state - The state, spelled as {@link readStates} reports it.
2501
+ * @param options - The time bounds, the abort signal, and the direction.
2502
+ * @returns The states the control announced when the wait resolved.
2503
+ * @throws The resolver's own refusal, the abort reason, or an `Error` when a bound is invalid or
2504
+ * the state is not reached within the budget.
2505
+ *
2506
+ * @example
2507
+ * ```ts
2508
+ * await clickAccessible('Pin note')
2509
+ * await waitForState('Pin note', 'pressed=true')
2510
+ * ```
2511
+ */
2512
+ export declare function waitForState(name: string, state: string, options?: StateOptions): Promise<readonly string[]>;
2513
+
2514
+ /**
2515
+ * Waits until one control of an exact role announces a state, or stops announcing it.
2516
+ *
2517
+ * @param role - The control's exact ARIA role.
2518
+ * @param name - The control's exact accessible name.
2519
+ * @param state - The state, spelled as {@link readStates} reports it.
2520
+ * @param options - The time bounds, the abort signal, and the direction.
2521
+ * @returns The states the control announced when the wait resolved.
2522
+ * @throws The resolver's own refusal, the abort reason, or an `Error` when a bound is invalid or
2523
+ * the state is not reached within the budget.
2524
+ *
2525
+ * @remarks
2526
+ * The control is resolved afresh on every reading, because a framework replaces the node between
2527
+ * one render and the next: the subject is the role and the name, never one element. That also means
2528
+ * the resolver's own voices reach the caller unchanged — a control that leaves the document
2529
+ * mid-wait refuses as absent rather than timing out as unannounced, which is the more useful
2530
+ * finding.
2531
+ *
2532
+ * {@link waitForCondition} owns the poll, so the bounds and the abort reason are that helper's.
2533
+ * Default budget: `1000` milliseconds. Default interval: `10` milliseconds. The exhaustion message
2534
+ * names the control and the state, and carries the last states read, so a wait that ran out says
2535
+ * what the control was announcing instead.
2536
+ *
2537
+ * This is the published replacement for a settle keyed to a framework's own class names. Where a
2538
+ * surface announces nothing, the finding is the surface's: give the control its `aria-expanded`,
2539
+ * `aria-pressed`, or `aria-busy` rather than reading the classes a stylesheet happens to use.
2540
+ *
2541
+ * @example
2542
+ * ```ts
2543
+ * await clickDisclosure('Advanced')
2544
+ * await waitForState('button', 'Advanced', 'collapsed', { absent: true })
2545
+ * ```
2546
+ */
2547
+ export declare function waitForState(role: string, name: string, state: string, options?: StateOptions): Promise<readonly string[]>;
2548
+
2549
+ /**
2550
+ * Holds a store the host can withhold and later grant.
2551
+ *
2552
+ * @remarks
2553
+ * The name carries `Web` because `@orkestrel/database` owns `StorageInterface` for the operations a
2554
+ * driver's transaction scope offers, and the fleet gives one bare exported name one owning package.
2555
+ * This one is the Web Storage surface a browser publishes on `localStorage`, plus the grant a
2556
+ * person allowing site data performs.
2557
+ *
2558
+ * `Storage` declares an index signature, so `store.theme` typechecks against this type. A store
2559
+ * `createStorage` returns answers through its methods alone and intercepts no named-property
2560
+ * access, so drive a consumer under test through `getItem` and `setItem`.
2561
+ */
2562
+ export declare interface WebStorageInterface extends Storage {
2563
+ /** Grants the reads and the writes the host withheld, and replenishes no quota. */
2564
+ permit(): void;
2565
+ }
2566
+
1940
2567
  export { }