@uniaria/testing 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/browser.d.ts +73 -0
- package/dist/browser.js +352 -0
- package/dist/browser.js.map +1 -0
- package/dist/commands-DPaip6f1.d.ts +61 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +341 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/scenario-DMaR4JJc.d.ts +383 -0
- package/dist/template-Cn6bp45u.js +152 -0
- package/dist/template-Cn6bp45u.js.map +1 -0
- package/package.json +73 -3
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
//#region src/conformance/template.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adapter-neutral markup for conformance scenarios. A scenario describes the
|
|
4
|
+
* DOM a consumer would write — part elements, their content and the
|
|
5
|
+
* attributes the consumer owns — and each adapter renders it its own way:
|
|
6
|
+
* React spreads part props onto these elements, the custom element wraps them
|
|
7
|
+
* in its host. Scenarios never import a definition, so they can be written
|
|
8
|
+
* before the implementation exists.
|
|
9
|
+
*/
|
|
10
|
+
interface TemplateElement {
|
|
11
|
+
/** Tag name, as the consumer would write it (`button`, `a`, `div`). */
|
|
12
|
+
readonly tag: string;
|
|
13
|
+
/** The UniAria part this element is, if any (`root`, `trigger`). */
|
|
14
|
+
readonly part?: string;
|
|
15
|
+
/** Consumer-owned attributes, with DOM names (`class`, `for`, `aria-label`). */
|
|
16
|
+
readonly attrs: Readonly<Record<string, string>>;
|
|
17
|
+
readonly children: readonly TemplateNode[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Another UniAria component inside the markup, such as a checkbox in its group.
|
|
21
|
+
* React renders it with its own `useComponent()`; custom elements wrap its
|
|
22
|
+
* children in its own host.
|
|
23
|
+
*/
|
|
24
|
+
interface TemplateComponent {
|
|
25
|
+
/** The nested component's definition name: `checkbox`. */
|
|
26
|
+
readonly component: string;
|
|
27
|
+
/** Its props, passed the way the adapter's consumers pass them. */
|
|
28
|
+
readonly props: Readonly<Record<string, unknown>>;
|
|
29
|
+
/** Its markup: part elements in it are its own parts. */
|
|
30
|
+
readonly children: readonly TemplateNode[];
|
|
31
|
+
}
|
|
32
|
+
/** What a `rows()` node renders from (Listbox virtualization spec). */
|
|
33
|
+
interface RenderedRows {
|
|
34
|
+
/** The component's `renderedKeys`: the options to render, in list order. */
|
|
35
|
+
readonly keys: readonly string[];
|
|
36
|
+
/** Its `renderedSections`: the groups to render, in list order. */
|
|
37
|
+
readonly sections: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rows the consumer renders from the component's public state rather than
|
|
41
|
+
* from every item, as a virtualized list's consumer does. React renders
|
|
42
|
+
* them in a child component that selects `renderedKeys` and
|
|
43
|
+
* `renderedSections`, so the owner never renders again for them; custom
|
|
44
|
+
* elements render them again on `renderedkeyschange`, synchronously,
|
|
45
|
+
* keeping each element whose part and `data-key` stay.
|
|
46
|
+
*/
|
|
47
|
+
interface TemplateRows {
|
|
48
|
+
readonly rows: (rendered: RenderedRows) => readonly TemplateNode[];
|
|
49
|
+
}
|
|
50
|
+
/** An element, a nested component, rows or a text node. */
|
|
51
|
+
type TemplateNode = TemplateElement | TemplateComponent | TemplateRows | string;
|
|
52
|
+
type TemplateAttrs = Readonly<Record<string, string>> & {
|
|
53
|
+
readonly part?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Builds a template element: `h('button', { part: 'root' }, 'Save')`. The
|
|
57
|
+
* `part` key names the UniAria part; every other key is a consumer-owned
|
|
58
|
+
* attribute.
|
|
59
|
+
*/
|
|
60
|
+
declare function h(tag: string, attrs?: TemplateAttrs | null, ...children: TemplateNode[]): TemplateElement;
|
|
61
|
+
/**
|
|
62
|
+
* Nests another component: `nested('checkbox', { value: 'email' }, …)`.
|
|
63
|
+
* Its children's `part`s are its own parts.
|
|
64
|
+
*/
|
|
65
|
+
declare function nested(component: string, props?: Readonly<Record<string, unknown>> | null, ...children: TemplateNode[]): TemplateComponent;
|
|
66
|
+
/**
|
|
67
|
+
* Rows rendered from the component's `renderedKeys` and `renderedSections`:
|
|
68
|
+
* `rows(({ keys }) => keys.map((key) => h('li', { part: 'option', 'data-key': key }, key)))`.
|
|
69
|
+
* Each row's top element carries `data-key`, as a consumer's keyed list does.
|
|
70
|
+
*/
|
|
71
|
+
declare function rows(render: (rendered: RenderedRows) => readonly TemplateNode[]): TemplateRows;
|
|
72
|
+
/** Whether a node is a nested component. */
|
|
73
|
+
declare function isNested(node: TemplateNode): node is TemplateComponent;
|
|
74
|
+
/** Whether a node is rows rendered from the component's state. */
|
|
75
|
+
declare function isRows(node: TemplateNode): node is TemplateRows;
|
|
76
|
+
/**
|
|
77
|
+
* Stands for `virtualizer` from `@uniaria/core/virtual` in a scenario's
|
|
78
|
+
* props: scenarios never import the implementation, so each adapter passes
|
|
79
|
+
* the real one in its place.
|
|
80
|
+
*/
|
|
81
|
+
declare const VIRTUALIZER: unique symbol;
|
|
82
|
+
/**
|
|
83
|
+
* The component's part elements in document order, for adapters that render
|
|
84
|
+
* templates; nested components' parts and rows are left out.
|
|
85
|
+
*/
|
|
86
|
+
declare function templateParts(nodes: readonly TemplateNode[]): TemplateElement[];
|
|
87
|
+
/**
|
|
88
|
+
* The CSS selector for a part of `component` (root AGENTS.md → Part
|
|
89
|
+
* markers): the root by `data-uni`, a part outside the root by
|
|
90
|
+
* `data-uni-part`, any other part by its `data-element` inside the root,
|
|
91
|
+
* minus those inside another component's root. A part no root is around
|
|
92
|
+
* counts too, as in a component rendered without its root, which has nothing
|
|
93
|
+
* else to scope its parts by. One compound, so `[data-key="…"]` can follow
|
|
94
|
+
* it.
|
|
95
|
+
*/
|
|
96
|
+
declare function partSelector(component: string, part: string): string;
|
|
97
|
+
/** Which part of `component` an element is, by its markers. */
|
|
98
|
+
declare function partName(element: Element, component: string): string | undefined;
|
|
99
|
+
/** How `renderTemplate()` renders what only the adapter knows. */
|
|
100
|
+
interface RenderTemplateOptions {
|
|
101
|
+
/** Adds attributes to part elements as they are created. */
|
|
102
|
+
readonly decorate?: (element: HTMLElement, node: TemplateElement) => void;
|
|
103
|
+
/** Creates the element for a nested component; its children go into it. */
|
|
104
|
+
readonly host?: (node: TemplateComponent) => HTMLElement;
|
|
105
|
+
/** Is told where a `rows()` node goes, and renders it there itself. */
|
|
106
|
+
readonly rowsIn?: (node: TemplateRows, parent: ParentNode) => void;
|
|
107
|
+
/** A component's parts outside its root: its definition's `outsideParts`. */
|
|
108
|
+
readonly outsideParts?: (component: string) => readonly string[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Renders a template of the component `component` to real DOM, marking part
|
|
112
|
+
* elements the way a consumer of the custom elements would (`partMarker()`):
|
|
113
|
+
* the root with `data-uni`, a part outside the root with `data-uni-part`,
|
|
114
|
+
* the others with `data-element`.
|
|
115
|
+
*/
|
|
116
|
+
declare function renderTemplate(nodes: readonly TemplateNode[], doc: Document, component: string, { decorate, host, rowsIn, outsideParts }?: RenderTemplateOptions): DocumentFragment;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/conformance/contract.d.ts
|
|
119
|
+
/** What the runner asks an adapter to render. */
|
|
120
|
+
interface MountRequest {
|
|
121
|
+
/** Definition name: `button`. */
|
|
122
|
+
readonly component: string;
|
|
123
|
+
/** Initial props, passed the way the adapter's consumers pass them. */
|
|
124
|
+
readonly props: Readonly<Record<string, unknown>>;
|
|
125
|
+
/** The consumer's markup. Part elements carry `part`. */
|
|
126
|
+
readonly template: readonly TemplateNode[];
|
|
127
|
+
/** Component events to observe; call `record` whenever one fires. */
|
|
128
|
+
readonly events: readonly string[];
|
|
129
|
+
record(event: string, detail: unknown): void;
|
|
130
|
+
/** An empty element already attached to `document.body`. Render into it. */
|
|
131
|
+
readonly container: HTMLElement;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* What an adapter returns from `mount()`.
|
|
135
|
+
*
|
|
136
|
+
* Guarantees every adapter must keep, because scenarios assert right after an
|
|
137
|
+
* awaited interaction:
|
|
138
|
+
* - Attribute writes for a state change are visible when the interaction that
|
|
139
|
+
* caused it resolves (the DOM binder writes synchronously after a flush).
|
|
140
|
+
* - `setProps()` resolves once the DOM reflects the new props.
|
|
141
|
+
*/
|
|
142
|
+
interface AdapterMount {
|
|
143
|
+
/** Merges props, the way the adapter's consumers update them. */
|
|
144
|
+
setProps(props: Readonly<Record<string, unknown>>): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Calls one of the component's public actions (`open`), the way the
|
|
147
|
+
* adapter's consumers do: the React handle's `actions`, the element's
|
|
148
|
+
* method. Resolves once the DOM reflects it.
|
|
149
|
+
*/
|
|
150
|
+
call(action: string): Promise<void>;
|
|
151
|
+
/** The object the definition's `expose()` returns right now. */
|
|
152
|
+
state(): Readonly<Record<string, unknown>>;
|
|
153
|
+
/**
|
|
154
|
+
* How many times the framework rendered the component that owns the
|
|
155
|
+
* instance, including the first render. Adapters without renders (custom
|
|
156
|
+
* elements) return 0.
|
|
157
|
+
*/
|
|
158
|
+
renderCount(): number;
|
|
159
|
+
/** Removes everything rendered and stops the instance. */
|
|
160
|
+
unmount(): Promise<void>;
|
|
161
|
+
}
|
|
162
|
+
interface ConformanceAdapter {
|
|
163
|
+
/** `react`, `elements`. Appears in test names. */
|
|
164
|
+
readonly name: string;
|
|
165
|
+
mount(request: MountRequest): Promise<AdapterMount>;
|
|
166
|
+
}
|
|
167
|
+
interface RecordedEvent {
|
|
168
|
+
/** camelCase event name: `press`. */
|
|
169
|
+
readonly name: string;
|
|
170
|
+
/** The event payload: the React callback argument or the DOM event's `detail`. */
|
|
171
|
+
readonly detail: unknown;
|
|
172
|
+
}
|
|
173
|
+
interface MountOptions<Props> {
|
|
174
|
+
/** The consumer's markup: one node or several. */
|
|
175
|
+
readonly template: TemplateNode | readonly TemplateNode[];
|
|
176
|
+
readonly props?: Partial<Props>;
|
|
177
|
+
/** Component events to record, camelCase. */
|
|
178
|
+
readonly events?: readonly string[];
|
|
179
|
+
}
|
|
180
|
+
/** A mounted component, as scenarios see it. */
|
|
181
|
+
interface Mounted<Props, State> {
|
|
182
|
+
/** The element the adapter rendered into. */
|
|
183
|
+
readonly container: HTMLElement;
|
|
184
|
+
/**
|
|
185
|
+
* The element of a part of the mounted component (`root` by default)
|
|
186
|
+
* inside the container, found by `partSelector()`: the root by
|
|
187
|
+
* `data-uni="<component>"`, a part outside the root by
|
|
188
|
+
* `data-uni-part="<component>-<part>"`, any other part by its
|
|
189
|
+
* `data-element` inside the root. Throws when there is none or more than one, so a
|
|
190
|
+
* missing marker fails loudly.
|
|
191
|
+
*/
|
|
192
|
+
part(name?: string): HTMLElement;
|
|
193
|
+
parts(name: string): HTMLElement[];
|
|
194
|
+
/** Every recorded event so far, in order. */
|
|
195
|
+
readonly events: readonly RecordedEvent[];
|
|
196
|
+
/** Just the names of the recorded events, in order. */
|
|
197
|
+
eventNames(): string[];
|
|
198
|
+
clearEvents(): void;
|
|
199
|
+
state(): State;
|
|
200
|
+
setProps(props: Partial<Props>): Promise<void>;
|
|
201
|
+
/** Calls a public action of the component, such as `open`. */
|
|
202
|
+
call(action: string): Promise<void>;
|
|
203
|
+
renderCount(): number;
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/conformance/scenario.d.ts
|
|
207
|
+
type MouseButton = 'left' | 'right' | 'middle';
|
|
208
|
+
/** Real mouse input through Playwright, at the target's centre. */
|
|
209
|
+
interface PointerInput {
|
|
210
|
+
/** Moves to the target and presses a button (left by default). */
|
|
211
|
+
down(target: Element, options?: {
|
|
212
|
+
button?: MouseButton;
|
|
213
|
+
}): Promise<void>;
|
|
214
|
+
/** Moves to the target's centre with the button state unchanged. */
|
|
215
|
+
move(target: Element): Promise<void>;
|
|
216
|
+
/** Moves to a point 24px below the target. */
|
|
217
|
+
moveOutside(target: Element): Promise<void>;
|
|
218
|
+
/** Releases a button (left by default) wherever the pointer is. */
|
|
219
|
+
up(options?: {
|
|
220
|
+
button?: MouseButton;
|
|
221
|
+
}): Promise<void>;
|
|
222
|
+
/**
|
|
223
|
+
* Turns the wheel wherever the pointer is, with the modifier keys held
|
|
224
|
+
* down: a negative `deltaY` scrolls up. One call is one `wheel` event.
|
|
225
|
+
*/
|
|
226
|
+
wheel(delta: {
|
|
227
|
+
deltaX?: number;
|
|
228
|
+
deltaY?: number;
|
|
229
|
+
}): Promise<void>;
|
|
230
|
+
/** `down` on the target, then `up`. */
|
|
231
|
+
click(target: Element, options?: {
|
|
232
|
+
button?: MouseButton;
|
|
233
|
+
}): Promise<void>;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Real keyboard input through Playwright, sent to the focused element. Key
|
|
237
|
+
* names follow Playwright: `Enter`, `Space`, `Escape`, `ArrowDown`.
|
|
238
|
+
*/
|
|
239
|
+
interface KeyboardInput {
|
|
240
|
+
/** Presses and holds a key. Calling it again while held sends a repeat. */
|
|
241
|
+
down(key: string): Promise<void>;
|
|
242
|
+
up(key: string): Promise<void>;
|
|
243
|
+
/** `down`, then `up`. */
|
|
244
|
+
press(key: string): Promise<void>;
|
|
245
|
+
/** Moves focus with Tab, or Shift+Tab with `isBackward`. */
|
|
246
|
+
tab(options?: {
|
|
247
|
+
isBackward?: boolean;
|
|
248
|
+
}): Promise<void>;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Real touch input, one finger at a time, through DevTools: only Chromium
|
|
252
|
+
* takes it, so scenarios that use it declare `needs: ['touch']`. The browser
|
|
253
|
+
* turns it into touch and pointer events, compatibility mouse events and a
|
|
254
|
+
* click, and a finger that moves away starts a scroll.
|
|
255
|
+
*/
|
|
256
|
+
interface TouchInput {
|
|
257
|
+
/** Puts a finger down on the target's centre. */
|
|
258
|
+
down(target: Element): Promise<void>;
|
|
259
|
+
/** Moves the finger to the target's centre. */
|
|
260
|
+
move(target: Element): Promise<void>;
|
|
261
|
+
/** Moves the finger 24px below the target. */
|
|
262
|
+
moveOutside(target: Element): Promise<void>;
|
|
263
|
+
/** Lifts the finger wherever it is. */
|
|
264
|
+
up(): Promise<void>;
|
|
265
|
+
/** `down` on the target, then `up`. */
|
|
266
|
+
tap(target: Element): Promise<void>;
|
|
267
|
+
}
|
|
268
|
+
/** Which real screen reader a scenario runs with. */
|
|
269
|
+
type ScreenReaderName = 'voiceover' | 'nvda';
|
|
270
|
+
/**
|
|
271
|
+
* A real screen reader driven through Guidepup: VoiceOver on macOS, NVDA on
|
|
272
|
+
* Windows, both with Chromium. Keys go through the operating system, so the
|
|
273
|
+
* screen reader hears them as a user's and speaks what they do. Only in
|
|
274
|
+
* scenarios that declare `needs: ['screenReader']`, which run nightly.
|
|
275
|
+
*
|
|
276
|
+
* The screen readers word things differently (`check box` in NVDA,
|
|
277
|
+
* `checkbox` in VoiceOver), so scenarios match what they need to hear, not
|
|
278
|
+
* whole phrases.
|
|
279
|
+
*/
|
|
280
|
+
interface ScreenReaderInput {
|
|
281
|
+
/** Which screen reader runs. */
|
|
282
|
+
readonly name: ScreenReaderName;
|
|
283
|
+
/**
|
|
284
|
+
* Clicks `element` (a text field before the component), waits for the
|
|
285
|
+
* screen reader to speak it and forgets what it said: the scenario starts
|
|
286
|
+
* from there, usually with Tab into the component.
|
|
287
|
+
*/
|
|
288
|
+
startFrom(element: HTMLElement): Promise<void>;
|
|
289
|
+
/**
|
|
290
|
+
* Presses a key through the screen reader (Playwright's names: `Tab`,
|
|
291
|
+
* `ArrowDown`, `Shift+Tab`) and waits until it stops speaking.
|
|
292
|
+
*/
|
|
293
|
+
press(key: string): Promise<void>;
|
|
294
|
+
/** Types text through the screen reader, a key per character. */
|
|
295
|
+
type(text: string): Promise<void>;
|
|
296
|
+
/** Moves the screen reader's own cursor to the next item. */
|
|
297
|
+
next(): Promise<void>;
|
|
298
|
+
/**
|
|
299
|
+
* What the screen reader holds for the element with keyboard focus: NVDA
|
|
300
|
+
* reads it again (NVDA+Tab), VoiceOver gives its cursor's item text. A
|
|
301
|
+
* check that holds when the speech on arrival is cut short.
|
|
302
|
+
*/
|
|
303
|
+
readFocus(): Promise<string>;
|
|
304
|
+
/**
|
|
305
|
+
* What it has said since `startFrom` or the last call: each key's phrases
|
|
306
|
+
* in order, joined with ` / `.
|
|
307
|
+
*/
|
|
308
|
+
spoken(): Promise<string>;
|
|
309
|
+
}
|
|
310
|
+
/** Input a scenario needs beyond mouse and keyboard. */
|
|
311
|
+
type InputNeed = 'touch' | 'screenReader';
|
|
312
|
+
interface ScenarioContext<Props, State> {
|
|
313
|
+
/** Mounts the suite's component through the adapter under test. */
|
|
314
|
+
mount(options: MountOptions<Props>): Promise<Mounted<Props, State>>;
|
|
315
|
+
pointer: PointerInput;
|
|
316
|
+
keyboard: KeyboardInput;
|
|
317
|
+
/** Only in scenarios that declare `needs: ['touch']`. */
|
|
318
|
+
touch: TouchInput;
|
|
319
|
+
/** Only in scenarios that declare `needs: ['screenReader']`. */
|
|
320
|
+
screenReader: ScreenReaderInput;
|
|
321
|
+
/**
|
|
322
|
+
* Reads the whole document with the virtual screen reader and returns every
|
|
323
|
+
* phrase, from `document` to `end of document`.
|
|
324
|
+
*/
|
|
325
|
+
spoken(): Promise<string[]>;
|
|
326
|
+
/**
|
|
327
|
+
* Starts hearing live regions with the virtual screen reader. The returned
|
|
328
|
+
* function gives what they have announced since, in order, each phrase
|
|
329
|
+
* prefixed with its politeness: `assertive: Saved`. Listening lasts until
|
|
330
|
+
* the scenario ends, and `spoken()` can't run meanwhile.
|
|
331
|
+
*/
|
|
332
|
+
listen(): Promise<() => Promise<string[]>>;
|
|
333
|
+
}
|
|
334
|
+
type ScenarioRun<Props, State> = (context: ScenarioContext<Props, State>) => Promise<void>;
|
|
335
|
+
interface ScenarioOptions {
|
|
336
|
+
/** Roadmap phase whose features the scenario needs. Default 1. */
|
|
337
|
+
readonly phase?: number;
|
|
338
|
+
/**
|
|
339
|
+
* Input beyond mouse and keyboard. Where the browser can't take it, the
|
|
340
|
+
* scenario is skipped; a scenario that needs a real screen reader runs
|
|
341
|
+
* only where one runs.
|
|
342
|
+
*/
|
|
343
|
+
readonly needs?: readonly InputNeed[];
|
|
344
|
+
}
|
|
345
|
+
interface Scenario<Props, State> {
|
|
346
|
+
readonly kind: 'scenario';
|
|
347
|
+
readonly name: string;
|
|
348
|
+
readonly phase: number;
|
|
349
|
+
readonly needs: readonly InputNeed[];
|
|
350
|
+
readonly run: ScenarioRun<Props, State>;
|
|
351
|
+
}
|
|
352
|
+
interface ScenarioGroup<Props, State> {
|
|
353
|
+
readonly kind: 'group';
|
|
354
|
+
readonly name: string;
|
|
355
|
+
readonly children: readonly (Scenario<Props, State> | ScenarioGroup<Props, State>)[];
|
|
356
|
+
}
|
|
357
|
+
interface ConformanceSuite<Props, State> {
|
|
358
|
+
/** Definition name: `button`. */
|
|
359
|
+
readonly component: string;
|
|
360
|
+
readonly children: readonly (Scenario<Props, State> | ScenarioGroup<Props, State>)[];
|
|
361
|
+
}
|
|
362
|
+
interface ConformanceBuilder<Props, State> {
|
|
363
|
+
scenario(name: string, run: ScenarioRun<Props, State>, options?: ScenarioOptions): void;
|
|
364
|
+
group(name: string, define: () => void): void;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Declares a component's conformance suite:
|
|
368
|
+
*
|
|
369
|
+
* ```ts
|
|
370
|
+
* export const buttonConformance = defineConformance<ButtonProps, ButtonState>(
|
|
371
|
+
* 'button',
|
|
372
|
+
* ({ scenario, group }) => {
|
|
373
|
+
* group('keyboard', () => {
|
|
374
|
+
* scenario('Enter presses on keyup', async ({ mount, keyboard }) => { … });
|
|
375
|
+
* });
|
|
376
|
+
* },
|
|
377
|
+
* );
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
declare function defineConformance<Props, State>(component: string, define: (builder: ConformanceBuilder<Props, State>) => void): ConformanceSuite<Props, State>;
|
|
381
|
+
//#endregion
|
|
382
|
+
export { h as A, RenderedRows as C, TemplateNode as D, TemplateElement as E, partSelector as F, renderTemplate as I, rows as L, isRows as M, nested as N, TemplateRows as O, partName as P, templateParts as R, RenderTemplateOptions as S, TemplateComponent as T, ConformanceAdapter as _, MouseButton as a, Mounted as b, ScenarioContext as c, ScenarioRun as d, ScreenReaderInput as f, AdapterMount as g, defineConformance as h, KeyboardInput as i, isNested as j, VIRTUALIZER as k, ScenarioGroup as l, TouchInput as m, ConformanceSuite as n, PointerInput as o, ScreenReaderName as p, InputNeed as r, Scenario as s, ConformanceBuilder as t, ScenarioOptions as u, MountOptions as v, TemplateAttrs as w, RecordedEvent as x, MountRequest as y };
|
|
383
|
+
//# sourceMappingURL=scenario-DMaR4JJc.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { partMarker } from "@uniaria/core";
|
|
2
|
+
//#region src/conformance/template.ts
|
|
3
|
+
/**
|
|
4
|
+
* Adapter-neutral markup for conformance scenarios. A scenario describes the
|
|
5
|
+
* DOM a consumer would write — part elements, their content and the
|
|
6
|
+
* attributes the consumer owns — and each adapter renders it its own way:
|
|
7
|
+
* React spreads part props onto these elements, the custom element wraps them
|
|
8
|
+
* in its host. Scenarios never import a definition, so they can be written
|
|
9
|
+
* before the implementation exists.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Builds a template element: `h('button', { part: 'root' }, 'Save')`. The
|
|
13
|
+
* `part` key names the UniAria part; every other key is a consumer-owned
|
|
14
|
+
* attribute.
|
|
15
|
+
*/
|
|
16
|
+
function h(tag, attrs, ...children) {
|
|
17
|
+
const { part, ...rest } = attrs ?? {};
|
|
18
|
+
return part === void 0 ? {
|
|
19
|
+
tag,
|
|
20
|
+
attrs: rest,
|
|
21
|
+
children
|
|
22
|
+
} : {
|
|
23
|
+
tag,
|
|
24
|
+
part,
|
|
25
|
+
attrs: rest,
|
|
26
|
+
children
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Nests another component: `nested('checkbox', { value: 'email' }, …)`.
|
|
31
|
+
* Its children's `part`s are its own parts.
|
|
32
|
+
*/
|
|
33
|
+
function nested(component, props, ...children) {
|
|
34
|
+
return {
|
|
35
|
+
component,
|
|
36
|
+
props: props ?? {},
|
|
37
|
+
children
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Rows rendered from the component's `renderedKeys` and `renderedSections`:
|
|
42
|
+
* `rows(({ keys }) => keys.map((key) => h('li', { part: 'option', 'data-key': key }, key)))`.
|
|
43
|
+
* Each row's top element carries `data-key`, as a consumer's keyed list does.
|
|
44
|
+
*/
|
|
45
|
+
function rows(render) {
|
|
46
|
+
return { rows: render };
|
|
47
|
+
}
|
|
48
|
+
/** Whether a node is a nested component. */
|
|
49
|
+
function isNested(node) {
|
|
50
|
+
return typeof node !== "string" && "component" in node;
|
|
51
|
+
}
|
|
52
|
+
/** Whether a node is rows rendered from the component's state. */
|
|
53
|
+
function isRows(node) {
|
|
54
|
+
return typeof node !== "string" && "rows" in node;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stands for `virtualizer` from `@uniaria/core/virtual` in a scenario's
|
|
58
|
+
* props: scenarios never import the implementation, so each adapter passes
|
|
59
|
+
* the real one in its place.
|
|
60
|
+
*/
|
|
61
|
+
const VIRTUALIZER = Symbol.for("uniaria.conformance.virtualizer");
|
|
62
|
+
/**
|
|
63
|
+
* The component's part elements in document order, for adapters that render
|
|
64
|
+
* templates; nested components' parts and rows are left out.
|
|
65
|
+
*/
|
|
66
|
+
function templateParts(nodes) {
|
|
67
|
+
const found = [];
|
|
68
|
+
const walk = (list) => {
|
|
69
|
+
for (const node of list) {
|
|
70
|
+
if (typeof node === "string" || isNested(node) || isRows(node)) continue;
|
|
71
|
+
if (node.part !== void 0) found.push(node);
|
|
72
|
+
walk(node.children);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
walk(nodes);
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
/** `[attribute="value"]` for a part's marker. */
|
|
79
|
+
function markerSelector(component, part, isOutside = false) {
|
|
80
|
+
const [attribute, value] = partMarker(component, part, isOutside);
|
|
81
|
+
return `[${attribute}="${value}"]`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The CSS selector for a part of `component` (root AGENTS.md → Part
|
|
85
|
+
* markers): the root by `data-uni`, a part outside the root by
|
|
86
|
+
* `data-uni-part`, any other part by its `data-element` inside the root,
|
|
87
|
+
* minus those inside another component's root. A part no root is around
|
|
88
|
+
* counts too, as in a component rendered without its root, which has nothing
|
|
89
|
+
* else to scope its parts by. One compound, so `[data-key="…"]` can follow
|
|
90
|
+
* it.
|
|
91
|
+
*/
|
|
92
|
+
function partSelector(component, part) {
|
|
93
|
+
const root = markerSelector(component, "root");
|
|
94
|
+
if (part === "root") return root;
|
|
95
|
+
const marker = markerSelector(component, part);
|
|
96
|
+
return `:is(${markerSelector(component, part, true)}, ${root} ${marker}:not(${root} [data-uni]:not(${root}) *), ${marker}:not([data-uni] *))`;
|
|
97
|
+
}
|
|
98
|
+
/** Which part of `component` an element is, by its markers. */
|
|
99
|
+
function partName(element, component) {
|
|
100
|
+
if (element.getAttribute("data-uni") === component) return "root";
|
|
101
|
+
const outside = element.getAttribute("data-uni-part");
|
|
102
|
+
if (outside?.startsWith(`${component}-`)) return outside.slice(component.length + 1).replace(/-([a-z0-9])/g, (_, letter) => letter.toUpperCase());
|
|
103
|
+
const marker = element.getAttribute("data-element");
|
|
104
|
+
if (!marker) return void 0;
|
|
105
|
+
const part = marker.charAt(0).toLowerCase() + marker.slice(1);
|
|
106
|
+
return element.matches(partSelector(component, part)) ? part : void 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Renders a template of the component `component` to real DOM, marking part
|
|
110
|
+
* elements the way a consumer of the custom elements would (`partMarker()`):
|
|
111
|
+
* the root with `data-uni`, a part outside the root with `data-uni-part`,
|
|
112
|
+
* the others with `data-element`.
|
|
113
|
+
*/
|
|
114
|
+
function renderTemplate(nodes, doc, component, { decorate, host, rowsIn, outsideParts } = {}) {
|
|
115
|
+
const fragment = doc.createDocumentFragment();
|
|
116
|
+
const append = (parent, list, owner = component) => {
|
|
117
|
+
for (const node of list) {
|
|
118
|
+
if (typeof node === "string") {
|
|
119
|
+
parent.appendChild(doc.createTextNode(node));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (isRows(node)) {
|
|
123
|
+
if (!rowsIn) throw new Error("The template has rows, which need the component.");
|
|
124
|
+
rowsIn(node, parent);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (isNested(node)) {
|
|
128
|
+
if (!host) throw new Error(`The template nests "${node.component}", which needs a host.`);
|
|
129
|
+
const element = host(node);
|
|
130
|
+
append(element, node.children, node.component);
|
|
131
|
+
parent.appendChild(element);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const element = doc.createElement(node.tag);
|
|
135
|
+
for (const [name, value] of Object.entries(node.attrs)) element.setAttribute(name, value);
|
|
136
|
+
const { part } = node;
|
|
137
|
+
if (part !== void 0) {
|
|
138
|
+
const isOutside = outsideParts?.(owner).includes(part) ?? false;
|
|
139
|
+
element.setAttribute(...partMarker(owner, part, isOutside));
|
|
140
|
+
}
|
|
141
|
+
if (part !== void 0) decorate?.(element, node);
|
|
142
|
+
append(element, node.children, owner);
|
|
143
|
+
parent.appendChild(element);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
append(fragment, nodes);
|
|
147
|
+
return fragment;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { nested as a, renderTemplate as c, isRows as i, rows as l, h as n, partName as o, isNested as r, partSelector as s, VIRTUALIZER as t, templateParts as u };
|
|
151
|
+
|
|
152
|
+
//# sourceMappingURL=template-Cn6bp45u.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-Cn6bp45u.js","names":[],"sources":["../src/conformance/template.ts"],"sourcesContent":["/**\n * Adapter-neutral markup for conformance scenarios. A scenario describes the\n * DOM a consumer would write — part elements, their content and the\n * attributes the consumer owns — and each adapter renders it its own way:\n * React spreads part props onto these elements, the custom element wraps them\n * in its host. Scenarios never import a definition, so they can be written\n * before the implementation exists.\n */\n\nimport { partMarker } from '@uniaria/core';\n\nexport interface TemplateElement {\n /** Tag name, as the consumer would write it (`button`, `a`, `div`). */\n readonly tag: string;\n /** The UniAria part this element is, if any (`root`, `trigger`). */\n readonly part?: string;\n /** Consumer-owned attributes, with DOM names (`class`, `for`, `aria-label`). */\n readonly attrs: Readonly<Record<string, string>>;\n readonly children: readonly TemplateNode[];\n}\n\n/**\n * Another UniAria component inside the markup, such as a checkbox in its group.\n * React renders it with its own `useComponent()`; custom elements wrap its\n * children in its own host.\n */\nexport interface TemplateComponent {\n /** The nested component's definition name: `checkbox`. */\n readonly component: string;\n /** Its props, passed the way the adapter's consumers pass them. */\n readonly props: Readonly<Record<string, unknown>>;\n /** Its markup: part elements in it are its own parts. */\n readonly children: readonly TemplateNode[];\n}\n\n/** What a `rows()` node renders from (Listbox virtualization spec). */\nexport interface RenderedRows {\n /** The component's `renderedKeys`: the options to render, in list order. */\n readonly keys: readonly string[];\n /** Its `renderedSections`: the groups to render, in list order. */\n readonly sections: readonly string[];\n}\n\n/**\n * Rows the consumer renders from the component's public state rather than\n * from every item, as a virtualized list's consumer does. React renders\n * them in a child component that selects `renderedKeys` and\n * `renderedSections`, so the owner never renders again for them; custom\n * elements render them again on `renderedkeyschange`, synchronously,\n * keeping each element whose part and `data-key` stay.\n */\nexport interface TemplateRows {\n readonly rows: (rendered: RenderedRows) => readonly TemplateNode[];\n}\n\n/** An element, a nested component, rows or a text node. */\nexport type TemplateNode =\n TemplateElement | TemplateComponent | TemplateRows | string;\n\nexport type TemplateAttrs = Readonly<Record<string, string>> & {\n readonly part?: string;\n};\n\n/**\n * Builds a template element: `h('button', { part: 'root' }, 'Save')`. The\n * `part` key names the UniAria part; every other key is a consumer-owned\n * attribute.\n */\nexport function h(\n tag: string,\n attrs?: TemplateAttrs | null,\n ...children: TemplateNode[]\n): TemplateElement {\n const { part, ...rest } = attrs ?? {};\n return part === undefined\n ? { tag, attrs: rest, children }\n : { tag, part, attrs: rest, children };\n}\n\n/**\n * Nests another component: `nested('checkbox', { value: 'email' }, …)`.\n * Its children's `part`s are its own parts.\n */\nexport function nested(\n component: string,\n props?: Readonly<Record<string, unknown>> | null,\n ...children: TemplateNode[]\n): TemplateComponent {\n return { component, props: props ?? {}, children };\n}\n\n/**\n * Rows rendered from the component's `renderedKeys` and `renderedSections`:\n * `rows(({ keys }) => keys.map((key) => h('li', { part: 'option', 'data-key': key }, key)))`.\n * Each row's top element carries `data-key`, as a consumer's keyed list does.\n */\nexport function rows(\n render: (rendered: RenderedRows) => readonly TemplateNode[],\n): TemplateRows {\n return { rows: render };\n}\n\n/** Whether a node is a nested component. */\nexport function isNested(node: TemplateNode): node is TemplateComponent {\n return typeof node !== 'string' && 'component' in node;\n}\n\n/** Whether a node is rows rendered from the component's state. */\nexport function isRows(node: TemplateNode): node is TemplateRows {\n return typeof node !== 'string' && 'rows' in node;\n}\n\n/**\n * Stands for `virtualizer` from `@uniaria/core/virtual` in a scenario's\n * props: scenarios never import the implementation, so each adapter passes\n * the real one in its place.\n */\nexport const VIRTUALIZER: unique symbol = Symbol.for(\n 'uniaria.conformance.virtualizer',\n);\n\n/**\n * The component's part elements in document order, for adapters that render\n * templates; nested components' parts and rows are left out.\n */\nexport function templateParts(\n nodes: readonly TemplateNode[],\n): TemplateElement[] {\n const found: TemplateElement[] = [];\n const walk = (list: readonly TemplateNode[]) => {\n for (const node of list) {\n if (typeof node === 'string' || isNested(node) || isRows(node)) continue;\n if (node.part !== undefined) found.push(node);\n walk(node.children);\n }\n };\n walk(nodes);\n return found;\n}\n\n/** `[attribute=\"value\"]` for a part's marker. */\nfunction markerSelector(component: string, part: string, isOutside = false) {\n const [attribute, value] = partMarker(component, part, isOutside);\n return `[${attribute}=\"${value}\"]`;\n}\n\n/**\n * The CSS selector for a part of `component` (root AGENTS.md → Part\n * markers): the root by `data-uni`, a part outside the root by\n * `data-uni-part`, any other part by its `data-element` inside the root,\n * minus those inside another component's root. A part no root is around\n * counts too, as in a component rendered without its root, which has nothing\n * else to scope its parts by. One compound, so `[data-key=\"…\"]` can follow\n * it.\n */\nexport function partSelector(component: string, part: string): string {\n const root = markerSelector(component, 'root');\n if (part === 'root') return root;\n const marker = markerSelector(component, part);\n return (\n `:is(${markerSelector(component, part, true)}, ` +\n `${root} ${marker}:not(${root} [data-uni]:not(${root}) *), ` +\n `${marker}:not([data-uni] *))`\n );\n}\n\n/** Which part of `component` an element is, by its markers. */\nexport function partName(\n element: Element,\n component: string,\n): string | undefined {\n if (element.getAttribute('data-uni') === component) return 'root';\n const outside = element.getAttribute('data-uni-part');\n if (outside?.startsWith(`${component}-`)) {\n return outside\n .slice(component.length + 1)\n .replace(/-([a-z0-9])/g, (_, letter: string) => letter.toUpperCase());\n }\n const marker = element.getAttribute('data-element');\n if (!marker) return undefined;\n const part = marker.charAt(0).toLowerCase() + marker.slice(1);\n return element.matches(partSelector(component, part)) ? part : undefined;\n}\n\n/** How `renderTemplate()` renders what only the adapter knows. */\nexport interface RenderTemplateOptions {\n /** Adds attributes to part elements as they are created. */\n readonly decorate?: (element: HTMLElement, node: TemplateElement) => void;\n /** Creates the element for a nested component; its children go into it. */\n readonly host?: (node: TemplateComponent) => HTMLElement;\n /** Is told where a `rows()` node goes, and renders it there itself. */\n readonly rowsIn?: (node: TemplateRows, parent: ParentNode) => void;\n /** A component's parts outside its root: its definition's `outsideParts`. */\n readonly outsideParts?: (component: string) => readonly string[];\n}\n\n/**\n * Renders a template of the component `component` to real DOM, marking part\n * elements the way a consumer of the custom elements would (`partMarker()`):\n * the root with `data-uni`, a part outside the root with `data-uni-part`,\n * the others with `data-element`.\n */\nexport function renderTemplate(\n nodes: readonly TemplateNode[],\n doc: Document,\n component: string,\n { decorate, host, rowsIn, outsideParts }: RenderTemplateOptions = {},\n): DocumentFragment {\n const fragment = doc.createDocumentFragment();\n const append = (\n parent: ParentNode,\n list: readonly TemplateNode[],\n owner = component,\n ) => {\n for (const node of list) {\n if (typeof node === 'string') {\n parent.appendChild(doc.createTextNode(node));\n continue;\n }\n if (isRows(node)) {\n if (!rowsIn) {\n throw new Error('The template has rows, which need the component.');\n }\n rowsIn(node, parent);\n continue;\n }\n if (isNested(node)) {\n if (!host) {\n throw new Error(\n `The template nests \"${node.component}\", which needs a host.`,\n );\n }\n const element = host(node);\n append(element, node.children, node.component);\n parent.appendChild(element);\n continue;\n }\n const element = doc.createElement(node.tag);\n for (const [name, value] of Object.entries(node.attrs)) {\n element.setAttribute(name, value);\n }\n const { part } = node;\n if (part !== undefined) {\n const isOutside = outsideParts?.(owner).includes(part) ?? false;\n element.setAttribute(...partMarker(owner, part, isOutside));\n }\n if (part !== undefined) decorate?.(element, node);\n append(element, node.children, owner);\n parent.appendChild(element);\n }\n };\n append(fragment, nodes);\n return fragment;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniaria/testing",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Testing tools for UniAria: test instances without a framework, the conformance suite runner and screen-reader helpers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"uniaria-source": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./browser": {
|
|
14
|
+
"uniaria-source": "./src/browser.ts",
|
|
15
|
+
"types": "./dist/browser.d.ts",
|
|
16
|
+
"default": "./dist/browser.js"
|
|
17
|
+
},
|
|
18
|
+
"./commands": {
|
|
19
|
+
"uniaria-source": "./src/commands.ts",
|
|
20
|
+
"types": "./dist/commands.d.ts",
|
|
21
|
+
"default": "./dist/commands.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/tenphi/uniaria.git",
|
|
31
|
+
"directory": "packages/testing"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"headless",
|
|
35
|
+
"ui",
|
|
36
|
+
"testing",
|
|
37
|
+
"accessibility",
|
|
38
|
+
"screen-reader"
|
|
39
|
+
],
|
|
40
|
+
"author": "Andrey Yamanov",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/tenphi/uniaria/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/tenphi/uniaria#readme",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@guidepup/virtual-screen-reader": "^0.33.0",
|
|
54
|
+
"@uniaria/core": "0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@guidepup/guidepup": "^0.35.0",
|
|
58
|
+
"@vitest/browser-playwright": "^5.0.0",
|
|
59
|
+
"vitest": "^5.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@guidepup/guidepup": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@vitest/browser-playwright": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@guidepup/guidepup": "^0.35.0"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsdown",
|
|
74
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
75
|
+
}
|
|
6
76
|
}
|