@blotch/angular-lynx-testing-library 22.2.3 → 22.2.5
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/README.md +65 -12
- package/dist/accessibility-label-queries.d.ts +43 -0
- package/dist/accessibility-label-queries.d.ts.map +1 -0
- package/dist/accessibility-label-queries.js +53 -0
- package/dist/fire-event.d.ts +65 -19
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +135 -31
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/queries.d.ts +110 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +56 -0
- package/dist/render.d.ts +9 -7
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +5 -3
- package/dist/role-queries.d.ts +37 -0
- package/dist/role-queries.d.ts.map +1 -0
- package/dist/role-queries.js +292 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @blotch/angular-lynx-testing-library
|
|
2
2
|
|
|
3
|
-
Testing utilities for [AngularLynx](https://github.com/Blotch-Smart-Frames/AngularLynx) apps. Renders Angular standalone components through the Lynx PAPI pipeline into a JSDOM tree, then exposes the result via [@testing-library/dom](https://testing-library.com/docs/dom-testing-library/intro) queries.
|
|
3
|
+
Testing utilities for [AngularLynx](https://github.com/Blotch-Smart-Frames/AngularLynx) apps. Renders Angular standalone components through the Lynx PAPI pipeline into a JSDOM tree, then exposes the result via [@testing-library/dom](https://testing-library.com/docs/dom-testing-library/intro) queries, which also read Lynx's `accessibility-*` attributes.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -75,11 +75,11 @@ const { container, getByText, queryByRole } = await render(MyComponent);
|
|
|
75
75
|
|
|
76
76
|
**Return value:**
|
|
77
77
|
|
|
78
|
-
| Property | Type | Description
|
|
79
|
-
| ------------ | ------------ |
|
|
80
|
-
| `container` | `Element` | The JSDOM `<page>` root element. All rendered children are descendants.
|
|
81
|
-
| `unmount` | `() => void` | Destroys the Angular app.
|
|
82
|
-
| `...queries` | — |
|
|
78
|
+
| Property | Type | Description |
|
|
79
|
+
| ------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `container` | `Element` | The JSDOM `<page>` root element. All rendered children are descendants. |
|
|
81
|
+
| `unmount` | `() => void` | Destroys the Angular app. |
|
|
82
|
+
| `...queries` | — | Every query in [`queries`](#queries) bound to `container` (`getByText`, `getByRole`, `getByAccessibilityLabel`, etc.). |
|
|
83
83
|
|
|
84
84
|
### `cleanup()`
|
|
85
85
|
|
|
@@ -110,7 +110,7 @@ Call this after any signal mutation that happens outside an event handler (e.g.
|
|
|
110
110
|
|
|
111
111
|
### `fireEvent`
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
Fires Lynx events at JSDOM elements. Use the named helpers for standard Lynx events:
|
|
114
114
|
|
|
115
115
|
```ts
|
|
116
116
|
import { fireEvent } from '@blotch/angular-lynx-testing-library';
|
|
@@ -120,7 +120,16 @@ fireEvent.input(element, { value: 'hello' });
|
|
|
120
120
|
fireEvent.scroll(element, { scrollTop: 100 });
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
A named helper runs the handlers a device would, in the same order:
|
|
124
|
+
|
|
125
|
+
- **Touch events bubble.** A tap, long press or touch runs the element's own `(bindtap)` or `(catchtap)`, then its ancestors' on the way up to the page. So a tap on a row inside a pressable card reaches the card's `(click)`. `(capture-bind…)` and `(capture-catch…)` handlers run before them, from the page down.
|
|
126
|
+
- **Mouse, wheel and key events bubble too**, as Lynx sends them on desktop and web.
|
|
127
|
+
- **A `catch` handler stops the event** where it runs: a `(catchtap)` panel keeps a tap from reaching the backdrop behind it.
|
|
128
|
+
- **Other events stay on their element.** `scroll`, `input`, `layoutchange` and the rest run only the element's own handler.
|
|
129
|
+
|
|
130
|
+
`init.bubbles` overrides the event's own: `fireEvent.tap(element, { bubbles: false })` runs only the element's handler.
|
|
131
|
+
|
|
132
|
+
`init.eventType` fires at one kind of handler on the element and nothing else: `fireEvent.tap(element, { eventType: 'catchEvent' })` dispatches the DOM event `"catchEvent:tap"`, which reaches only the element's `(catchtap)`.
|
|
124
133
|
|
|
125
134
|
To dispatch a raw DOM event directly:
|
|
126
135
|
|
|
@@ -134,7 +143,7 @@ fireEvent(element, new Event('bindEvent:tap'));
|
|
|
134
143
|
|
|
135
144
|
### `eventMap`
|
|
136
145
|
|
|
137
|
-
The full Lynx event registry. Each entry has a `defaultInit` object
|
|
146
|
+
The full Lynx event registry. Each entry has a `defaultInit` object: `{ bubbles: true }` for the events Lynx sends up the response chain, `{}` for the rest.
|
|
138
147
|
|
|
139
148
|
```ts
|
|
140
149
|
import { eventMap } from '@blotch/angular-lynx-testing-library';
|
|
@@ -143,9 +152,53 @@ console.log(Object.keys(eventMap));
|
|
|
143
152
|
// ['tap', 'longtap', 'touchstart', ...]
|
|
144
153
|
```
|
|
145
154
|
|
|
146
|
-
###
|
|
155
|
+
### `*ByAccessibilityLabel`
|
|
156
|
+
|
|
157
|
+
Find elements by their `accessibility-label`, the text a screen reader reads on Lynx. Testing Library's `*ByLabelText` reads `<label>` and `aria-*` attributes, which Lynx elements don't have.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const { getByAccessibilityLabel } = await render(Toolbar);
|
|
161
|
+
|
|
162
|
+
fireEvent.tap(getByAccessibilityLabel('Close'));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
They come in Testing Library's six variants (`getBy`, `getAllBy`, `queryBy`, `queryAllBy`, `findBy`, `findAllBy`) and take its TextMatch (a string, RegExp or function) and MatcherOptions (`exact`, `normalizer`).
|
|
166
|
+
|
|
167
|
+
### `*ByRole`
|
|
168
|
+
|
|
169
|
+
Testing Library's `*ByRole`, reading Lynx's accessibility attributes as a screen reader does. Elements Testing Library gives a role still match, such as an `<input>` as a `textbox`.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const { getByRole } = await render(Toolbar);
|
|
173
|
+
|
|
174
|
+
fireEvent.tap(getByRole('button', { name: 'Save' }));
|
|
175
|
+
expect(getByRole('button', { name: 'Week', selected: true })).toBeTruthy();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| `accessibility-traits` | Role |
|
|
179
|
+
| ---------------------- | ----------- |
|
|
180
|
+
| `button` | `button` |
|
|
181
|
+
| `link` | `link` |
|
|
182
|
+
| `image` | `img` |
|
|
183
|
+
| `header` | `heading` |
|
|
184
|
+
| `search` | `searchbox` |
|
|
185
|
+
| `adjustable` | `slider` |
|
|
186
|
+
| `tabbar` | `tablist` |
|
|
187
|
+
|
|
188
|
+
- **Role**: from each trait in the table. An `<image>` is an `img` unless it sets `accessibility-traits`.
|
|
189
|
+
- **Reachable**: a screen reader reaches `<text>`, `<image>`, and other elements with `accessibility-element="true"`, outside `accessibility-elements-hidden`. A `tabbar` is read from the container around the tabs, so it needs no `accessibility-element`. Others match only with `hidden: true`.
|
|
190
|
+
- **`name`**: the `accessibility-label`, or a `<text>`'s own text. A `<view>` gets no name from the text inside it, since iOS doesn't read it.
|
|
191
|
+
- **`selected`**: the `selected` trait, as in `accessibility-traits="button,selected"`, which an active `routerLinkActive` element gets, and which `[lynxAccessibilityTraits]="{ selected: true }"` adds. iOS reads a comma-separated list. Android reads one trait (`button` or `image`) and treats a list as `none`.
|
|
192
|
+
- **`value: { text }`**: the `accessibility-value`.
|
|
193
|
+
- **Other ARIA states** (`checked`, `pressed`, `expanded`, `level`, …) have no Lynx attribute, so Lynx elements never match them.
|
|
194
|
+
|
|
195
|
+
### `screen`, `within()` and `getQueriesForElement()`
|
|
196
|
+
|
|
197
|
+
Testing Library's, with the queries above: `screen` binds every query to `document.body`, and `within(element)` binds them to `element`. `getQueriesForElement` is `within`.
|
|
198
|
+
|
|
199
|
+
### `queries`
|
|
147
200
|
|
|
148
|
-
|
|
201
|
+
Every query, unbound (each takes the container first). Spread it into your own queries to keep them next to these. Each query is also exported on its own, such as `getByAccessibilityLabel(container, 'Close')`.
|
|
149
202
|
|
|
150
203
|
## Usage
|
|
151
204
|
|
|
@@ -356,7 +409,7 @@ render(Component)
|
|
|
356
409
|
|
|
357
410
|
AngularLynx registers event handlers via `__AddEvent(element, eventType, eventName, { type: 'worklet', value: callback })`. The testing environment stores this as a DOM event listener under the key `"${eventType}:${eventName}"` (e.g. `"bindEvent:tap"`).
|
|
358
411
|
|
|
359
|
-
`fireEvent.tap(el)`
|
|
412
|
+
`fireEvent.tap(el)` walks Lynx's event response chain, from the page down to `el` and back up, and calls each element's listener for the kind of handler it has, stopping at a `catch` one. The listener calls `runWorklet(callback, [event])` which invokes the Angular handler directly on the main thread. One bubbling `"bindEvent:tap"` DOM event wouldn't do: it would pass a `(catchtap)` ancestor's `"catchEvent:tap"` listener by and run the handlers above it.
|
|
360
413
|
|
|
361
414
|
Template binding → PAPI registration → DOM listener key:
|
|
362
415
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queries that find an element by its `accessibility-label`, the text a
|
|
3
|
+
* screen reader reads for it on Lynx.
|
|
4
|
+
*
|
|
5
|
+
* Testing Library's `*ByLabelText` reads `<label>` elements and the ARIA
|
|
6
|
+
* label attributes, none of which Lynx has: a Lynx element gets its
|
|
7
|
+
* accessible name from `accessibility-label`. Like `*ByTitle` and
|
|
8
|
+
* `*ByAltText`, which also match one attribute, these take a TextMatch with
|
|
9
|
+
* MatcherOptions (`exact`, `normalizer`), throw errors worded the same way,
|
|
10
|
+
* and their `find*` variants retry for `waitFor`'s timeout.
|
|
11
|
+
*/
|
|
12
|
+
import { type AllByBoundAttribute, type FindAllByBoundAttribute, type FindByBoundAttribute, type GetByBoundAttribute, type QueryByBoundAttribute } from '@testing-library/dom';
|
|
13
|
+
/**
|
|
14
|
+
* Finds every element whose `accessibility-label` matches, or none.
|
|
15
|
+
*/
|
|
16
|
+
declare const queryAll: <T extends HTMLElement = HTMLElement>(...args: Parameters<AllByBoundAttribute<T>>) => ReturnType<AllByBoundAttribute<T>>;
|
|
17
|
+
/**
|
|
18
|
+
* Finds the element whose `accessibility-label` matches, or null. Throws when
|
|
19
|
+
* more than one does.
|
|
20
|
+
*/
|
|
21
|
+
declare const query: <T extends HTMLElement = HTMLElement>(...args: Parameters<QueryByBoundAttribute<T>>) => ReturnType<QueryByBoundAttribute<T>>;
|
|
22
|
+
/**
|
|
23
|
+
* Finds every element whose `accessibility-label` matches. Throws when none
|
|
24
|
+
* does.
|
|
25
|
+
*/
|
|
26
|
+
declare const getAll: <T extends HTMLElement = HTMLElement>(...args: Parameters<AllByBoundAttribute<T>>) => ReturnType<AllByBoundAttribute<T>>;
|
|
27
|
+
/**
|
|
28
|
+
* Finds the element whose `accessibility-label` matches. Throws when none or
|
|
29
|
+
* more than one does.
|
|
30
|
+
*/
|
|
31
|
+
declare const get: <T extends HTMLElement = HTMLElement>(...args: Parameters<GetByBoundAttribute<T>>) => ReturnType<GetByBoundAttribute<T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Waits for elements whose `accessibility-label` matches, as `getAll` finds
|
|
34
|
+
* them.
|
|
35
|
+
*/
|
|
36
|
+
declare const findAll: <T extends HTMLElement = HTMLElement>(...args: Parameters<FindAllByBoundAttribute<T>>) => ReturnType<FindAllByBoundAttribute<T>>;
|
|
37
|
+
/**
|
|
38
|
+
* Waits for the element whose `accessibility-label` matches, as `get` finds
|
|
39
|
+
* it.
|
|
40
|
+
*/
|
|
41
|
+
declare const find: <T extends HTMLElement = HTMLElement>(...args: Parameters<FindByBoundAttribute<T>>) => ReturnType<FindByBoundAttribute<T>>;
|
|
42
|
+
export { queryAll as queryAllByAccessibilityLabel, query as queryByAccessibilityLabel, getAll as getAllByAccessibilityLabel, get as getByAccessibilityLabel, findAll as findAllByAccessibilityLabel, find as findByAccessibilityLabel, };
|
|
43
|
+
//# sourceMappingURL=accessibility-label-queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-label-queries.d.ts","sourceRoot":"","sources":["../src/accessibility-label-queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGxB,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAgC9B;;GAEG;AACH,QAAA,MAAM,QAAQ,EAAmC,CAC/C,CAAC,SAAS,WAAW,GAAG,WAAW,EAEnC,GAAG,IAAI,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KACxC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAExC;;;GAGG;AACH,QAAA,MAAM,KAAK,EAAc,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC3D,GAAG,IAAI,EAAE,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,KAC1C,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1C;;;GAGG;AACH,QAAA,MAAM,MAAM,EAAe,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC7D,GAAG,IAAI,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KACxC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAExC;;;GAGG;AACH,QAAA,MAAM,GAAG,EAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACvD,GAAG,IAAI,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KACxC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAExC;;;GAGG;AACH,QAAA,MAAM,OAAO,EAAgB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC/D,GAAG,IAAI,EAAE,UAAU,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,KAC5C,UAAU,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5C;;;GAGG;AACH,QAAA,MAAM,IAAI,EAAa,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACzD,GAAG,IAAI,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,KACzC,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzC,OAAO,EACL,QAAQ,IAAI,4BAA4B,EACxC,KAAK,IAAI,yBAAyB,EAClC,MAAM,IAAI,0BAA0B,EACpC,GAAG,IAAI,uBAAuB,EAC9B,OAAO,IAAI,2BAA2B,EACtC,IAAI,IAAI,wBAAwB,GACjC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queries that find an element by its `accessibility-label`, the text a
|
|
3
|
+
* screen reader reads for it on Lynx.
|
|
4
|
+
*
|
|
5
|
+
* Testing Library's `*ByLabelText` reads `<label>` elements and the ARIA
|
|
6
|
+
* label attributes, none of which Lynx has: a Lynx element gets its
|
|
7
|
+
* accessible name from `accessibility-label`. Like `*ByTitle` and
|
|
8
|
+
* `*ByAltText`, which also match one attribute, these take a TextMatch with
|
|
9
|
+
* MatcherOptions (`exact`, `normalizer`), throw errors worded the same way,
|
|
10
|
+
* and their `find*` variants retry for `waitFor`'s timeout.
|
|
11
|
+
*/
|
|
12
|
+
import { buildQueries, queryHelpers, } from '@testing-library/dom';
|
|
13
|
+
/**
|
|
14
|
+
* Every element in `container` whose `accessibility-label` matches `label`.
|
|
15
|
+
* `buildQueries` names the other variants' suggestions after this function,
|
|
16
|
+
* so it keeps Testing Library's `queryAllBy*` name.
|
|
17
|
+
*/
|
|
18
|
+
const queryAllByAccessibilityLabel = (container, label, options) => queryHelpers.queryAllByAttribute('accessibility-label', container, label, options);
|
|
19
|
+
const [queryBy, getAllBy, getBy, findAllBy, findBy] = buildQueries(queryAllByAccessibilityLabel, (_container, label) => `Found multiple elements with the accessibility-label: ${label}`, (_container, label) => `Unable to find an element with the accessibility-label: ${label}`);
|
|
20
|
+
// buildQueries returns HTMLElement-typed functions. Testing Library declares
|
|
21
|
+
// its own queries generic over the element type, so these are cast to the
|
|
22
|
+
// same shape: getByAccessibilityLabel<TextElement>(...) works like
|
|
23
|
+
// getByTitle<TextElement>(...).
|
|
24
|
+
/**
|
|
25
|
+
* Finds every element whose `accessibility-label` matches, or none.
|
|
26
|
+
*/
|
|
27
|
+
const queryAll = queryAllByAccessibilityLabel;
|
|
28
|
+
/**
|
|
29
|
+
* Finds the element whose `accessibility-label` matches, or null. Throws when
|
|
30
|
+
* more than one does.
|
|
31
|
+
*/
|
|
32
|
+
const query = queryBy;
|
|
33
|
+
/**
|
|
34
|
+
* Finds every element whose `accessibility-label` matches. Throws when none
|
|
35
|
+
* does.
|
|
36
|
+
*/
|
|
37
|
+
const getAll = getAllBy;
|
|
38
|
+
/**
|
|
39
|
+
* Finds the element whose `accessibility-label` matches. Throws when none or
|
|
40
|
+
* more than one does.
|
|
41
|
+
*/
|
|
42
|
+
const get = getBy;
|
|
43
|
+
/**
|
|
44
|
+
* Waits for elements whose `accessibility-label` matches, as `getAll` finds
|
|
45
|
+
* them.
|
|
46
|
+
*/
|
|
47
|
+
const findAll = findAllBy;
|
|
48
|
+
/**
|
|
49
|
+
* Waits for the element whose `accessibility-label` matches, as `get` finds
|
|
50
|
+
* it.
|
|
51
|
+
*/
|
|
52
|
+
const find = findBy;
|
|
53
|
+
export { queryAll as queryAllByAccessibilityLabel, query as queryByAccessibilityLabel, getAll as getAllByAccessibilityLabel, get as getByAccessibilityLabel, findAll as findAllByAccessibilityLabel, find as findByAccessibilityLabel, };
|
package/dist/fire-event.d.ts
CHANGED
|
@@ -1,64 +1,110 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fireEvent — dispatches Lynx events through the dual-thread pipeline.
|
|
3
3
|
*
|
|
4
|
-
* Lynx events are registered via __AddEvent with a key of "
|
|
4
|
+
* Lynx events are registered via __AddEvent with a key of "<type>:<name>"
|
|
5
5
|
* (e.g. "bindEvent:tap"). The event handler stored on the element calls
|
|
6
6
|
* lynxCoreInject.tt.publishEvent(handlerSign, eventData) which routes the
|
|
7
7
|
* event to the background thread (not used by AngularLynx, which runs on
|
|
8
8
|
* the main thread).
|
|
9
9
|
*
|
|
10
10
|
* For AngularLynx, the (event) bindings in templates are wired by
|
|
11
|
-
* LynxRenderer.listen() which calls __AddEvent internally. The
|
|
12
|
-
*
|
|
11
|
+
* LynxRenderer.listen() which calls __AddEvent internally. The named helpers
|
|
12
|
+
* (fireEvent.tap, …) run those handlers on the main thread along the path
|
|
13
|
+
* Lynx sends the event (see deliver()).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The events the named helpers fire. Those with `bubbles: true` travel Lynx's
|
|
17
|
+
* event response chain, from the element up to the page, so a tap on a row
|
|
18
|
+
* inside a pressable card reaches the card's handler. The rest reach only
|
|
19
|
+
* the element they happened on, as Lynx sends an element's own events
|
|
20
|
+
* (scroll, input, layout, …).
|
|
21
|
+
*
|
|
22
|
+
* The touch events bubble on every platform, as React Lynx's testing library
|
|
23
|
+
* has them. Mouse, wheel and key events bubble too, where Lynx sends them:
|
|
24
|
+
* its desktop engine dispatches them with TouchEventHandler::HandleBubbleEvent
|
|
25
|
+
* (all but mouseenter and mouseleave) and Lynx for Web with the DOM event's
|
|
26
|
+
* own bubbling. iOS and Android don't send them.
|
|
13
27
|
*/
|
|
14
28
|
export declare const eventMap: {
|
|
15
29
|
tap: {
|
|
16
|
-
defaultInit: {
|
|
30
|
+
defaultInit: {
|
|
31
|
+
bubbles: boolean;
|
|
32
|
+
};
|
|
17
33
|
};
|
|
18
34
|
longtap: {
|
|
19
|
-
defaultInit: {
|
|
35
|
+
defaultInit: {
|
|
36
|
+
bubbles: boolean;
|
|
37
|
+
};
|
|
20
38
|
};
|
|
21
39
|
touchstart: {
|
|
22
|
-
defaultInit: {
|
|
40
|
+
defaultInit: {
|
|
41
|
+
bubbles: boolean;
|
|
42
|
+
};
|
|
23
43
|
};
|
|
24
44
|
touchmove: {
|
|
25
|
-
defaultInit: {
|
|
45
|
+
defaultInit: {
|
|
46
|
+
bubbles: boolean;
|
|
47
|
+
};
|
|
26
48
|
};
|
|
27
49
|
touchcancel: {
|
|
28
|
-
defaultInit: {
|
|
50
|
+
defaultInit: {
|
|
51
|
+
bubbles: boolean;
|
|
52
|
+
};
|
|
29
53
|
};
|
|
30
54
|
touchend: {
|
|
31
|
-
defaultInit: {
|
|
55
|
+
defaultInit: {
|
|
56
|
+
bubbles: boolean;
|
|
57
|
+
};
|
|
32
58
|
};
|
|
33
59
|
longpress: {
|
|
34
|
-
defaultInit: {
|
|
60
|
+
defaultInit: {
|
|
61
|
+
bubbles: boolean;
|
|
62
|
+
};
|
|
35
63
|
};
|
|
36
64
|
keydown: {
|
|
37
|
-
defaultInit: {
|
|
65
|
+
defaultInit: {
|
|
66
|
+
bubbles: boolean;
|
|
67
|
+
};
|
|
38
68
|
};
|
|
39
69
|
keyup: {
|
|
40
|
-
defaultInit: {
|
|
70
|
+
defaultInit: {
|
|
71
|
+
bubbles: boolean;
|
|
72
|
+
};
|
|
41
73
|
};
|
|
42
74
|
mousedown: {
|
|
43
|
-
defaultInit: {
|
|
75
|
+
defaultInit: {
|
|
76
|
+
bubbles: boolean;
|
|
77
|
+
};
|
|
44
78
|
};
|
|
45
79
|
mouseup: {
|
|
46
|
-
defaultInit: {
|
|
80
|
+
defaultInit: {
|
|
81
|
+
bubbles: boolean;
|
|
82
|
+
};
|
|
47
83
|
};
|
|
48
84
|
mousemove: {
|
|
49
|
-
defaultInit: {
|
|
85
|
+
defaultInit: {
|
|
86
|
+
bubbles: boolean;
|
|
87
|
+
};
|
|
50
88
|
};
|
|
51
89
|
mouseclick: {
|
|
52
|
-
defaultInit: {
|
|
90
|
+
defaultInit: {
|
|
91
|
+
bubbles: boolean;
|
|
92
|
+
};
|
|
53
93
|
};
|
|
54
94
|
mousedblclick: {
|
|
55
|
-
defaultInit: {
|
|
95
|
+
defaultInit: {
|
|
96
|
+
bubbles: boolean;
|
|
97
|
+
};
|
|
56
98
|
};
|
|
57
99
|
mouselongpress: {
|
|
58
|
-
defaultInit: {
|
|
100
|
+
defaultInit: {
|
|
101
|
+
bubbles: boolean;
|
|
102
|
+
};
|
|
59
103
|
};
|
|
60
104
|
wheel: {
|
|
61
|
-
defaultInit: {
|
|
105
|
+
defaultInit: {
|
|
106
|
+
bubbles: boolean;
|
|
107
|
+
};
|
|
62
108
|
};
|
|
63
109
|
scroll: {
|
|
64
110
|
defaultInit: {};
|
package/dist/fire-event.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fire-event.d.ts","sourceRoot":"","sources":["../src/fire-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"fire-event.d.ts","sourceRoot":"","sources":["../src/fire-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CpB,CAAC;AAEF,KAAK,SAAS,GAAG,MAAM,OAAO,QAAQ,CAAC;AACvC,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;AAC/E,KAAK,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG;KAC3D,CAAC,IAAI,SAAS,GAAG,YAAY;CAC/B,CAAC;AA8GF,eAAO,MAAM,SAAS,EAAE,SAET,CAAC"}
|
package/dist/fire-event.js
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fireEvent — dispatches Lynx events through the dual-thread pipeline.
|
|
3
3
|
*
|
|
4
|
-
* Lynx events are registered via __AddEvent with a key of "
|
|
4
|
+
* Lynx events are registered via __AddEvent with a key of "<type>:<name>"
|
|
5
5
|
* (e.g. "bindEvent:tap"). The event handler stored on the element calls
|
|
6
6
|
* lynxCoreInject.tt.publishEvent(handlerSign, eventData) which routes the
|
|
7
7
|
* event to the background thread (not used by AngularLynx, which runs on
|
|
8
8
|
* the main thread).
|
|
9
9
|
*
|
|
10
10
|
* For AngularLynx, the (event) bindings in templates are wired by
|
|
11
|
-
* LynxRenderer.listen() which calls __AddEvent internally. The
|
|
12
|
-
*
|
|
11
|
+
* LynxRenderer.listen() which calls __AddEvent internally. The named helpers
|
|
12
|
+
* (fireEvent.tap, …) run those handlers on the main thread along the path
|
|
13
|
+
* Lynx sends the event (see deliver()).
|
|
13
14
|
*/
|
|
14
|
-
import { createEvent, fireEvent as domFireEvent } from '@testing-library/dom';
|
|
15
|
+
import { createEvent, fireEvent as domFireEvent, getConfig, } from '@testing-library/dom';
|
|
15
16
|
const EVENT_INIT_KEYS = new Set(['bubbles', 'cancelable', 'composed']);
|
|
17
|
+
/**
|
|
18
|
+
* The events the named helpers fire. Those with `bubbles: true` travel Lynx's
|
|
19
|
+
* event response chain, from the element up to the page, so a tap on a row
|
|
20
|
+
* inside a pressable card reaches the card's handler. The rest reach only
|
|
21
|
+
* the element they happened on, as Lynx sends an element's own events
|
|
22
|
+
* (scroll, input, layout, …).
|
|
23
|
+
*
|
|
24
|
+
* The touch events bubble on every platform, as React Lynx's testing library
|
|
25
|
+
* has them. Mouse, wheel and key events bubble too, where Lynx sends them:
|
|
26
|
+
* its desktop engine dispatches them with TouchEventHandler::HandleBubbleEvent
|
|
27
|
+
* (all but mouseenter and mouseleave) and Lynx for Web with the DOM event's
|
|
28
|
+
* own bubbling. iOS and Android don't send them.
|
|
29
|
+
*/
|
|
16
30
|
export const eventMap = {
|
|
17
31
|
// Touch / gesture
|
|
18
|
-
tap: { defaultInit: {} },
|
|
19
|
-
longtap: { defaultInit: {} },
|
|
20
|
-
touchstart: { defaultInit: {} },
|
|
21
|
-
touchmove: { defaultInit: {} },
|
|
22
|
-
touchcancel: { defaultInit: {} },
|
|
23
|
-
touchend: { defaultInit: {} },
|
|
24
|
-
longpress: { defaultInit: {} },
|
|
32
|
+
tap: { defaultInit: { bubbles: true } },
|
|
33
|
+
longtap: { defaultInit: { bubbles: true } },
|
|
34
|
+
touchstart: { defaultInit: { bubbles: true } },
|
|
35
|
+
touchmove: { defaultInit: { bubbles: true } },
|
|
36
|
+
touchcancel: { defaultInit: { bubbles: true } },
|
|
37
|
+
touchend: { defaultInit: { bubbles: true } },
|
|
38
|
+
longpress: { defaultInit: { bubbles: true } },
|
|
25
39
|
// Keyboard
|
|
26
|
-
keydown: { defaultInit: {} },
|
|
27
|
-
keyup: { defaultInit: {} },
|
|
40
|
+
keydown: { defaultInit: { bubbles: true } },
|
|
41
|
+
keyup: { defaultInit: { bubbles: true } },
|
|
28
42
|
// Mouse
|
|
29
|
-
mousedown: { defaultInit: {} },
|
|
30
|
-
mouseup: { defaultInit: {} },
|
|
31
|
-
mousemove: { defaultInit: {} },
|
|
32
|
-
mouseclick: { defaultInit: {} },
|
|
33
|
-
mousedblclick: { defaultInit: {} },
|
|
34
|
-
mouselongpress: { defaultInit: {} },
|
|
35
|
-
wheel: { defaultInit: {} },
|
|
43
|
+
mousedown: { defaultInit: { bubbles: true } },
|
|
44
|
+
mouseup: { defaultInit: { bubbles: true } },
|
|
45
|
+
mousemove: { defaultInit: { bubbles: true } },
|
|
46
|
+
mouseclick: { defaultInit: { bubbles: true } },
|
|
47
|
+
mousedblclick: { defaultInit: { bubbles: true } },
|
|
48
|
+
mouselongpress: { defaultInit: { bubbles: true } },
|
|
49
|
+
wheel: { defaultInit: { bubbles: true } },
|
|
36
50
|
// Scroll / viewport
|
|
37
51
|
scroll: { defaultInit: {} },
|
|
38
52
|
scrollend: { defaultInit: {} },
|
|
@@ -57,23 +71,113 @@ export const eventMap = {
|
|
|
57
71
|
transitioncancel: { defaultInit: {} },
|
|
58
72
|
animationend: { defaultInit: {} },
|
|
59
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* The kinds of handler Lynx runs on the way down the response chain to the
|
|
76
|
+
* target (capture) and on the way back up (bubble). An element has at most
|
|
77
|
+
* one handler per event name, of any kind (see the runtime's
|
|
78
|
+
* combinedEventType), so the order within a phase only matters to a test
|
|
79
|
+
* that registers more.
|
|
80
|
+
*/
|
|
81
|
+
const CAPTURE_PHASE = ['capture-bindEvent', 'capture-catchEvent'];
|
|
82
|
+
const BUBBLE_PHASE = ['bindEvent', 'catchEvent'];
|
|
83
|
+
/**
|
|
84
|
+
* The handler __AddEvent registered on `element` for the event `type:name`,
|
|
85
|
+
* if any. The testing environment keeps an element's handlers in an
|
|
86
|
+
* `eventMap` keyed that way, each also listening for the DOM event of that
|
|
87
|
+
* name (setup.ts's __GetEvents reads the same map).
|
|
88
|
+
*/
|
|
89
|
+
const handlerOf = (element, type, name) => element.eventMap?.[`${type}:${name}`];
|
|
90
|
+
/**
|
|
91
|
+
* The event a handler of `type` receives: the `<type>:<name>` DOM event that
|
|
92
|
+
* reaches it when fired on its own, with the Lynx properties and the test's
|
|
93
|
+
* init on it. Standard EventInit keys are read-only on constructed events,
|
|
94
|
+
* so they're passed to the constructor and not assigned.
|
|
95
|
+
*/
|
|
96
|
+
const createLynxEvent = (elem, type, name, init) => {
|
|
97
|
+
const eventInit = { eventType: type, eventName: name, ...init };
|
|
98
|
+
const event = createEvent(`${type}:${name}`, elem, eventInit);
|
|
99
|
+
Object.assign(event, Object.fromEntries(Object.entries(eventInit).filter(([k]) => !EVENT_INIT_KEYS.has(k))));
|
|
100
|
+
return event;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Runs the handlers for the event `name` fired at `target`, as Lynx's
|
|
104
|
+
* TouchEventHandler::HandleEventInternal does on a device. An event that
|
|
105
|
+
* bubbles goes down from the page to the target, running `capture-bind` and
|
|
106
|
+
* `capture-catch` handlers, then back up, running `bind` and `catch` ones. A
|
|
107
|
+
* `catch` handler of either phase runs and stops the event there. Any other
|
|
108
|
+
* event runs the target's own `bind` or `catch` handler and nothing more.
|
|
109
|
+
*
|
|
110
|
+
* It calls the handlers rather than dispatching one DOM event, because the
|
|
111
|
+
* testing environment listens for a separate DOM event per kind
|
|
112
|
+
* (`bindEvent:tap`, `catchEvent:tap`, …): a bubbling `bindEvent:tap` would
|
|
113
|
+
* pass a `(catchtap)` ancestor by, and reach the handlers above it, where a
|
|
114
|
+
* device stops. Returns false if a handler canceled its event, as
|
|
115
|
+
* dispatchEvent does.
|
|
116
|
+
*/
|
|
117
|
+
const deliver = (target, name, init) => {
|
|
118
|
+
// The error Testing Library gives a query that found nothing, rather than
|
|
119
|
+
// a TypeError from reading its parent.
|
|
120
|
+
if (!target) {
|
|
121
|
+
throw new Error(`Unable to fire a "${name}" event - please provide a DOM element.`);
|
|
122
|
+
}
|
|
123
|
+
const chain = [target];
|
|
124
|
+
if (init['bubbles']) {
|
|
125
|
+
for (let el = target.parentElement; el; el = el.parentElement) {
|
|
126
|
+
chain.push(el);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let notCanceled = true;
|
|
130
|
+
/**
|
|
131
|
+
* Runs each element's handlers of `types` in turn. Returns true once a
|
|
132
|
+
* catch handler stops the event.
|
|
133
|
+
*/
|
|
134
|
+
const runPhase = (elements, types) => {
|
|
135
|
+
for (const element of elements) {
|
|
136
|
+
let caught = false;
|
|
137
|
+
for (const type of types) {
|
|
138
|
+
const handler = handlerOf(element, type, name);
|
|
139
|
+
if (!handler)
|
|
140
|
+
continue;
|
|
141
|
+
const event = createLynxEvent(target, type, name, init);
|
|
142
|
+
handler(event);
|
|
143
|
+
notCanceled &&= !event.defaultPrevented;
|
|
144
|
+
caught ||= type.endsWith('catchEvent');
|
|
145
|
+
}
|
|
146
|
+
if (caught)
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
// Lynx has no capture phase for the events that stay on their target.
|
|
152
|
+
const captured = !!init['bubbles'] && runPhase([...chain].reverse(), CAPTURE_PHASE);
|
|
153
|
+
if (!captured)
|
|
154
|
+
runPhase(chain, BUBBLE_PHASE);
|
|
155
|
+
return notCanceled;
|
|
156
|
+
};
|
|
60
157
|
export const fireEvent = ((elem, event) => {
|
|
61
158
|
return domFireEvent(elem, event);
|
|
62
159
|
});
|
|
63
160
|
for (const key of Object.keys(eventMap)) {
|
|
64
161
|
fireEvent[key] = (elem, init) => {
|
|
65
|
-
const eventType = init
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
162
|
+
const { eventType, ...rest } = init ?? {};
|
|
163
|
+
if (eventType === undefined) {
|
|
164
|
+
const eventInit = { ...eventMap[key].defaultInit, ...rest };
|
|
165
|
+
// Run through Testing Library's eventWrapper, as its fireEvent runs a
|
|
166
|
+
// dispatch, for a test that configures one.
|
|
167
|
+
let notCanceled = true;
|
|
168
|
+
getConfig().eventWrapper(() => {
|
|
169
|
+
notCanceled = deliver(elem, key, eventInit);
|
|
170
|
+
});
|
|
171
|
+
return notCanceled;
|
|
172
|
+
}
|
|
173
|
+
// An explicit eventType fires the one `<eventType>:<name>` DOM event at
|
|
174
|
+
// elem: it reaches elem's handler of that kind, or a listener the test
|
|
175
|
+
// added for it, and goes no further unless init asks it to bubble.
|
|
176
|
+
const event = createLynxEvent(elem, String(eventType), key, {
|
|
69
177
|
...eventMap[key].defaultInit,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Assign Lynx-specific custom properties (eventType, eventName, etc.).
|
|
74
|
-
// Standard EventInit keys are read-only on constructed events so we skip them.
|
|
75
|
-
const customProps = Object.fromEntries(Object.entries(eventInit).filter(([k]) => !EVENT_INIT_KEYS.has(k)));
|
|
76
|
-
Object.assign(event, customProps);
|
|
178
|
+
bubbles: false,
|
|
179
|
+
...rest,
|
|
180
|
+
});
|
|
77
181
|
return domFireEvent(elem, event);
|
|
78
182
|
};
|
|
79
183
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,8 @@ export { render, cleanup, waitForUpdate } from './render.js';
|
|
|
2
2
|
export type { RenderOptions, RenderResult } from './render.js';
|
|
3
3
|
export type { ComponentRef } from '@angular/core';
|
|
4
4
|
export { fireEvent, eventMap } from './fire-event.js';
|
|
5
|
-
export { screen, within, getQueriesForElement } from '
|
|
5
|
+
export { screen, within, getQueriesForElement, queries } from './queries.js';
|
|
6
|
+
export type { BoundAccessibilityLabelQueries, BoundLynxQueries, LynxQueries, LynxScreen, } from './queries.js';
|
|
7
|
+
export { queryAllByAccessibilityLabel, queryByAccessibilityLabel, getAllByAccessibilityLabel, getByAccessibilityLabel, findAllByAccessibilityLabel, findByAccessibilityLabel, } from './accessibility-label-queries.js';
|
|
8
|
+
export { queryAllByRole, queryByRole, getAllByRole, getByRole, findAllByRole, findByRole, } from './role-queries.js';
|
|
6
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7E,YAAY,EACV,8BAA8B,EAC9B,gBAAgB,EAChB,WAAW,EACX,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,0BAA0B,EAC1B,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,WAAW,EACX,YAAY,EACZ,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { render, cleanup, waitForUpdate } from './render.js';
|
|
2
2
|
export { fireEvent, eventMap } from './fire-event.js';
|
|
3
|
-
export { screen, within, getQueriesForElement } from '
|
|
3
|
+
export { screen, within, getQueriesForElement, queries } from './queries.js';
|
|
4
|
+
export { queryAllByAccessibilityLabel, queryByAccessibilityLabel, getAllByAccessibilityLabel, getByAccessibilityLabel, findAllByAccessibilityLabel, findByAccessibilityLabel, } from './accessibility-label-queries.js';
|
|
5
|
+
export { queryAllByRole, queryByRole, getAllByRole, getByRole, findAllByRole, findByRole, } from './role-queries.js';
|
|
4
6
|
import { cleanup } from './render.js';
|
|
5
7
|
/**
|
|
6
8
|
* Auto-cleanup after each test (matches @testing-library/react convention).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The queries a test gets from `render()`, `screen` and `within()`: Testing
|
|
3
|
+
* Library's, with `*ByRole` replaced by the Lynx-aware version and the
|
|
4
|
+
* `*ByAccessibilityLabel` queries added.
|
|
5
|
+
*/
|
|
6
|
+
import { type BoundFunction, type BoundFunctions, type FindAllByBoundAttribute, type FindByBoundAttribute, type GetByBoundAttribute, type AllByBoundAttribute, type Queries, type QueryByBoundAttribute, type Screen, queries as domQueries } from '@testing-library/dom';
|
|
7
|
+
/**
|
|
8
|
+
* Every query, unbound: Testing Library's, with the Lynx-aware `*ByRole` and
|
|
9
|
+
* the `*ByAccessibilityLabel` queries. Spread it into your own queries to
|
|
10
|
+
* keep these next to them, as with Testing Library's `queries`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const queries: {
|
|
13
|
+
queryAllByRole: typeof domQueries.queryAllByRole;
|
|
14
|
+
queryByRole: typeof domQueries.queryByRole;
|
|
15
|
+
getAllByRole: typeof domQueries.getAllByRole;
|
|
16
|
+
getByRole: typeof domQueries.getByRole;
|
|
17
|
+
findAllByRole: typeof domQueries.findAllByRole;
|
|
18
|
+
findByRole: typeof domQueries.findByRole;
|
|
19
|
+
queryAllByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined) => ReturnType<AllByBoundAttribute<T>>;
|
|
20
|
+
queryByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined) => ReturnType<QueryByBoundAttribute<T>>;
|
|
21
|
+
getAllByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined) => ReturnType<AllByBoundAttribute<T>>;
|
|
22
|
+
getByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined) => ReturnType<GetByBoundAttribute<T>>;
|
|
23
|
+
findAllByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined) => ReturnType<FindAllByBoundAttribute<T>>;
|
|
24
|
+
findByAccessibilityLabel: <T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined) => ReturnType<FindByBoundAttribute<T>>;
|
|
25
|
+
getByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.GetByText<T>>;
|
|
26
|
+
getAllByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.AllByText<T>>;
|
|
27
|
+
queryByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.QueryByText<T>>;
|
|
28
|
+
queryAllByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.AllByText<T>>;
|
|
29
|
+
findByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<domQueries.FindByText<T>>;
|
|
30
|
+
findAllByLabelText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<domQueries.FindAllByText<T>>;
|
|
31
|
+
getByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<GetByBoundAttribute<T>>;
|
|
32
|
+
getAllByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
33
|
+
queryByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<QueryByBoundAttribute<T>>;
|
|
34
|
+
queryAllByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
35
|
+
findByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindByBoundAttribute<T>>;
|
|
36
|
+
findAllByPlaceholderText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindAllByBoundAttribute<T>>;
|
|
37
|
+
getByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.GetByText<T>>;
|
|
38
|
+
getAllByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.AllByText<T>>;
|
|
39
|
+
queryByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.QueryByText<T>>;
|
|
40
|
+
queryAllByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined): ReturnType<domQueries.AllByText<T>>;
|
|
41
|
+
findByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<domQueries.FindByText<T>>;
|
|
42
|
+
findAllByText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").SelectorMatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<domQueries.FindAllByText<T>>;
|
|
43
|
+
getByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<GetByBoundAttribute<T>>;
|
|
44
|
+
getAllByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
45
|
+
queryByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<QueryByBoundAttribute<T>>;
|
|
46
|
+
queryAllByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
47
|
+
findByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindByBoundAttribute<T>>;
|
|
48
|
+
findAllByAltText<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindAllByBoundAttribute<T>>;
|
|
49
|
+
getByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<GetByBoundAttribute<T>>;
|
|
50
|
+
getAllByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
51
|
+
queryByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<QueryByBoundAttribute<T>>;
|
|
52
|
+
queryAllByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
53
|
+
findByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindByBoundAttribute<T>>;
|
|
54
|
+
findAllByTitle<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindAllByBoundAttribute<T>>;
|
|
55
|
+
getByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<GetByBoundAttribute<T>>;
|
|
56
|
+
getAllByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
57
|
+
queryByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<QueryByBoundAttribute<T>>;
|
|
58
|
+
queryAllByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
59
|
+
findByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindByBoundAttribute<T>>;
|
|
60
|
+
findAllByDisplayValue<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindAllByBoundAttribute<T>>;
|
|
61
|
+
getByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<GetByBoundAttribute<T>>;
|
|
62
|
+
getAllByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
63
|
+
queryByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<QueryByBoundAttribute<T>>;
|
|
64
|
+
queryAllByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined): ReturnType<AllByBoundAttribute<T>>;
|
|
65
|
+
findByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindByBoundAttribute<T>>;
|
|
66
|
+
findAllByTestId<T extends HTMLElement = HTMLElement>(container: HTMLElement, id: import("@testing-library/dom").Matcher, options?: import("@testing-library/dom").MatcherOptions | undefined, waitForElementOptions?: import("@testing-library/dom").waitForOptions | undefined): ReturnType<FindAllByBoundAttribute<T>>;
|
|
67
|
+
};
|
|
68
|
+
export type LynxQueries = typeof queries;
|
|
69
|
+
/**
|
|
70
|
+
* The `*ByAccessibilityLabel` queries bound to a container. Declared like
|
|
71
|
+
* Testing Library's bound queries, so each takes an element type:
|
|
72
|
+
* `getByAccessibilityLabel<MyElement>('Close')`.
|
|
73
|
+
*/
|
|
74
|
+
export type BoundAccessibilityLabelQueries = {
|
|
75
|
+
queryAllByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<AllByBoundAttribute<T>>>): ReturnType<AllByBoundAttribute<T>>;
|
|
76
|
+
queryByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<QueryByBoundAttribute<T>>>): ReturnType<QueryByBoundAttribute<T>>;
|
|
77
|
+
getAllByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<AllByBoundAttribute<T>>>): ReturnType<AllByBoundAttribute<T>>;
|
|
78
|
+
getByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<GetByBoundAttribute<T>>>): ReturnType<GetByBoundAttribute<T>>;
|
|
79
|
+
findAllByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<FindAllByBoundAttribute<T>>>): ReturnType<FindAllByBoundAttribute<T>>;
|
|
80
|
+
findByAccessibilityLabel<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<FindByBoundAttribute<T>>>): ReturnType<FindByBoundAttribute<T>>;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Every query, bound to a container.
|
|
84
|
+
*/
|
|
85
|
+
export type BoundLynxQueries = BoundAccessibilityLabelQueries & BoundFunctions<LynxQueries>;
|
|
86
|
+
/**
|
|
87
|
+
* `screen`: every query bound to `document.body`, plus Testing Library's
|
|
88
|
+
* `debug()` and `logTestingPlaygroundURL()`.
|
|
89
|
+
*/
|
|
90
|
+
export type LynxScreen = BoundAccessibilityLabelQueries & Screen<LynxQueries>;
|
|
91
|
+
/**
|
|
92
|
+
* Testing Library's `screen` with the Lynx queries in place of its own
|
|
93
|
+
* `*ByRole`, and the `*ByAccessibilityLabel` queries added.
|
|
94
|
+
*/
|
|
95
|
+
export declare const screen: LynxScreen;
|
|
96
|
+
type Within = {
|
|
97
|
+
(element: HTMLElement): BoundLynxQueries;
|
|
98
|
+
<Q extends Queries>(element: HTMLElement, queriesToBind: Q): BoundFunctions<Q>;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Binds every query (or the ones you pass) to `element`, like Testing
|
|
102
|
+
* Library's `within()`, so a test can look inside one part of the tree.
|
|
103
|
+
*/
|
|
104
|
+
export declare const within: Within;
|
|
105
|
+
/**
|
|
106
|
+
* The same as `within()`, under Testing Library's other name for it.
|
|
107
|
+
*/
|
|
108
|
+
export declare const getQueriesForElement: Within;
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EAEX,OAAO,IAAI,UAAU,EAEtB,MAAM,sBAAsB,CAAC;AAoC9B;;;;GAIG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAoC,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG,OAAO,OAAO,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,8BAA8B,GAAG;IAC3C,4BAA4B,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,GACzD,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,yBAAyB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC3D,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3D,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,0BAA0B,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC5D,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,GACzD,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,uBAAuB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACzD,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,GACzD,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,2BAA2B,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC7D,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,GAC7D,UAAU,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,wBAAwB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC1D,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,GAC1D,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,8BAA8B,GAC3D,cAAc,CAAC,WAAW,CAAC,CAAC;AAE9B;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,8BAA8B,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;AAc9E;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,UAGN,CAAC;AAEhB,KAAK,MAAM,GAAG;IACZ,CAAC,OAAO,EAAE,WAAW,GAAG,gBAAgB,CAAC;IACzC,CAAC,CAAC,SAAS,OAAO,EAChB,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,CAAC,GACf,cAAc,CAAC,CAAC,CAAC,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,EAG+C,MAAM,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAS,CAAC"}
|
package/dist/queries.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The queries a test gets from `render()`, `screen` and `within()`: Testing
|
|
3
|
+
* Library's, with `*ByRole` replaced by the Lynx-aware version and the
|
|
4
|
+
* `*ByAccessibilityLabel` queries added.
|
|
5
|
+
*/
|
|
6
|
+
import { getQueriesForElement as getDomQueriesForElement, queries as domQueries, screen as domScreen, } from '@testing-library/dom';
|
|
7
|
+
import { findAllByAccessibilityLabel, findByAccessibilityLabel, getAllByAccessibilityLabel, getByAccessibilityLabel, queryAllByAccessibilityLabel, queryByAccessibilityLabel, } from './accessibility-label-queries.js';
|
|
8
|
+
import { findAllByRole, findByRole, getAllByRole, getByRole, queryAllByRole, queryByRole, } from './role-queries.js';
|
|
9
|
+
/**
|
|
10
|
+
* The queries this library adds to Testing Library's or replaces in them.
|
|
11
|
+
*/
|
|
12
|
+
const lynxQueries = {
|
|
13
|
+
queryAllByRole,
|
|
14
|
+
queryByRole,
|
|
15
|
+
getAllByRole,
|
|
16
|
+
getByRole,
|
|
17
|
+
findAllByRole,
|
|
18
|
+
findByRole,
|
|
19
|
+
queryAllByAccessibilityLabel,
|
|
20
|
+
queryByAccessibilityLabel,
|
|
21
|
+
getAllByAccessibilityLabel,
|
|
22
|
+
getByAccessibilityLabel,
|
|
23
|
+
findAllByAccessibilityLabel,
|
|
24
|
+
findByAccessibilityLabel,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Every query, unbound: Testing Library's, with the Lynx-aware `*ByRole` and
|
|
28
|
+
* the `*ByAccessibilityLabel` queries. Spread it into your own queries to
|
|
29
|
+
* keep these next to them, as with Testing Library's `queries`.
|
|
30
|
+
*/
|
|
31
|
+
export const queries = { ...domQueries, ...lynxQueries };
|
|
32
|
+
/**
|
|
33
|
+
* Binds `toBind` to `document.body`, looked up on each call rather than when
|
|
34
|
+
* this module loads, so importing the library doesn't need a document.
|
|
35
|
+
*/
|
|
36
|
+
const bindToBody = (toBind) => Object.fromEntries(Object.entries(toBind).map(([key, query]) => [
|
|
37
|
+
key,
|
|
38
|
+
(...args) => query(document.body, ...args),
|
|
39
|
+
]));
|
|
40
|
+
/**
|
|
41
|
+
* Testing Library's `screen` with the Lynx queries in place of its own
|
|
42
|
+
* `*ByRole`, and the `*ByAccessibilityLabel` queries added.
|
|
43
|
+
*/
|
|
44
|
+
export const screen = {
|
|
45
|
+
...domScreen,
|
|
46
|
+
...bindToBody(lynxQueries),
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Binds every query (or the ones you pass) to `element`, like Testing
|
|
50
|
+
* Library's `within()`, so a test can look inside one part of the tree.
|
|
51
|
+
*/
|
|
52
|
+
export const within = ((element, queriesToBind = queries) => getDomQueriesForElement(element, queriesToBind));
|
|
53
|
+
/**
|
|
54
|
+
* The same as `within()`, under Testing Library's other name for it.
|
|
55
|
+
*/
|
|
56
|
+
export const getQueriesForElement = within;
|
package/dist/render.d.ts
CHANGED
|
@@ -10,20 +10,22 @@
|
|
|
10
10
|
* → Angular renders tree → __CreateView/Text/… → JSDOM children
|
|
11
11
|
* → LynxRendererFactory2.end() → __FlushElementTree() (no-op in tests)
|
|
12
12
|
* 3. appRef.tick() forces synchronous CD so all signal-driven updates land
|
|
13
|
-
* 4. Return JSDOM container + @testing-library/dom
|
|
13
|
+
* 4. Return JSDOM container + queries (@testing-library/dom's, with the
|
|
14
|
+
* Lynx-aware *ByRole and *ByAccessibilityLabel; see queries.ts)
|
|
14
15
|
*/
|
|
15
16
|
import { type ComponentRef, type EnvironmentProviders, type Provider, type Type } from '@angular/core';
|
|
16
|
-
import { type BoundFunctions, type Queries
|
|
17
|
-
|
|
17
|
+
import { type BoundFunctions, type Queries } from '@testing-library/dom';
|
|
18
|
+
import { type BoundLynxQueries, type LynxQueries } from './queries.js';
|
|
19
|
+
export type RenderOptions<Q extends Queries = LynxQueries> = {
|
|
18
20
|
providers?: (Provider | EnvironmentProviders)[];
|
|
19
21
|
/**
|
|
20
|
-
* Custom query helpers to merge with the
|
|
22
|
+
* Custom query helpers to merge with the library's queries (see queries.ts).
|
|
21
23
|
* Each helper receives the container as its first argument when bound.
|
|
22
24
|
* Workaround if omitted: call `getQueriesForElement(container, customQueries)` directly.
|
|
23
25
|
*/
|
|
24
26
|
queries?: Q;
|
|
25
27
|
};
|
|
26
|
-
export type RenderResult<Q extends Queries =
|
|
28
|
+
export type RenderResult<Q extends Queries = LynxQueries> = {
|
|
27
29
|
/**
|
|
28
30
|
* The JSDOM root element (page element) containing all rendered children.
|
|
29
31
|
*/
|
|
@@ -55,9 +57,9 @@ export type RenderResult<Q extends Queries = typeof domQueries> = {
|
|
|
55
57
|
* expose writable signals on the component instance instead.
|
|
56
58
|
*/
|
|
57
59
|
componentRef: ComponentRef<unknown>;
|
|
58
|
-
} &
|
|
60
|
+
} & BoundLynxQueries & BoundFunctions<Q>;
|
|
59
61
|
export declare const cleanup: () => void;
|
|
60
|
-
export declare const render: <Q extends Queries =
|
|
62
|
+
export declare const render: <Q extends Queries = LynxQueries>(rootComponent: Type<unknown>, options?: RenderOptions<Q>) => Promise<RenderResult<Q>>;
|
|
61
63
|
/**
|
|
62
64
|
* Wait for all pending Angular reactive updates to flush through the pipeline.
|
|
63
65
|
* Call this after programmatic signal changes that happen outside event handlers.
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EAEb,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,OAAO,EAEb,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAW,MAAM,cAAc,CAAC;AAEhF,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,WAAW,IAAI;IAC3D,SAAS,CAAC,EAAE,CAAC,QAAQ,GAAG,oBAAoB,CAAC,EAAE,CAAC;IAChD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,GAAG,WAAW,IAAI;IAC1D;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,EAC9B,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EACxB,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,KACvB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B;;OAEG;IACH,UAAU,EAAE,MAAM,gBAAgB,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,YAAY,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;CACrC,GAAG,gBAAgB,GAClB,cAAc,CAAC,CAAC,CAAC,CAAC;AAIpB,eAAO,MAAM,OAAO,QAAO,IAM1B,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,CAAC,SAAS,OAAO,GAAG,WAAW,EAC1D,eAAe,IAAI,CAAC,OAAO,CAAC,EAC5B,UAAS,aAAa,CAAC,CAAC,CAA0B,KACjD,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAkDzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,IAAI,CAKlD,CAAC"}
|
package/dist/render.js
CHANGED
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
* → Angular renders tree → __CreateView/Text/… → JSDOM children
|
|
11
11
|
* → LynxRendererFactory2.end() → __FlushElementTree() (no-op in tests)
|
|
12
12
|
* 3. appRef.tick() forces synchronous CD so all signal-driven updates land
|
|
13
|
-
* 4. Return JSDOM container + @testing-library/dom
|
|
13
|
+
* 4. Return JSDOM container + queries (@testing-library/dom's, with the
|
|
14
|
+
* Lynx-aware *ByRole and *ByAccessibilityLabel; see queries.ts)
|
|
14
15
|
*/
|
|
15
16
|
import { provideZonelessChangeDetection, } from '@angular/core';
|
|
16
17
|
import { bootstrapApplication } from '@angular/platform-browser';
|
|
17
|
-
import {
|
|
18
|
+
import { getQueriesForElement, } from '@testing-library/dom';
|
|
18
19
|
import { provideRenderer } from '@blotch/angular-lynx';
|
|
20
|
+
import { queries } from './queries.js';
|
|
19
21
|
let currentAppRef = null;
|
|
20
22
|
export const cleanup = () => {
|
|
21
23
|
if (currentAppRef) {
|
|
@@ -57,7 +59,7 @@ export const render = async (rootComponent, options = {}) => {
|
|
|
57
59
|
asFragment: () => document.createRange().createContextualFragment(container.outerHTML),
|
|
58
60
|
componentRef,
|
|
59
61
|
...getQueriesForElement(container, {
|
|
60
|
-
...
|
|
62
|
+
...queries,
|
|
61
63
|
...options.queries,
|
|
62
64
|
}),
|
|
63
65
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `*ByRole` queries that read Lynx's accessibility attributes.
|
|
3
|
+
*
|
|
4
|
+
* Testing Library works out an element's role from `role` and HTML, and its
|
|
5
|
+
* name and state from `aria-*` attributes. A Lynx element has none of those,
|
|
6
|
+
* so on a Lynx tree Testing Library's `getByRole` found almost nothing: a
|
|
7
|
+
* `<view accessibility-traits="button">` has no ARIA role. These queries add
|
|
8
|
+
* what Lynx gives screen readers, read the way Lynx's native code reads it,
|
|
9
|
+
* to what Testing Library finds (a Lynx `<input>` is still a
|
|
10
|
+
* textbox):
|
|
11
|
+
*
|
|
12
|
+
* - Role: each trait in `accessibility-traits` that names a kind of element
|
|
13
|
+
* (`TRAIT_ROLES`), or the element's own default: an `<image>` is an image.
|
|
14
|
+
* - Exposed: a screen reader reaches an element only when it's an
|
|
15
|
+
* accessibility element. `<text>` and `<image>` are by default; anything
|
|
16
|
+
* else needs `accessibility-element="true"`. The exception is a tab bar,
|
|
17
|
+
* whose trait VoiceOver reads from the container around the tabs.
|
|
18
|
+
* `accessibility-elements-hidden` hides an element and everything inside
|
|
19
|
+
* it. With `hidden: false`, the default, the rest are left out, as Testing
|
|
20
|
+
* Library leaves out what's off the accessibility tree.
|
|
21
|
+
* - Name: `accessibility-label`, or a `<text>`'s own text when it has none.
|
|
22
|
+
* Lynx gives no other element a name from the text inside it: iOS reads an
|
|
23
|
+
* accessibility element's label and skips its children.
|
|
24
|
+
* - State: the `selected` trait is `selected: true`, and `accessibility-value`
|
|
25
|
+
* is `value: { text }`. Lynx has no other ARIA state.
|
|
26
|
+
*
|
|
27
|
+
* They take Testing Library's ByRoleOptions and throw its error messages.
|
|
28
|
+
*/
|
|
29
|
+
import { queries as domQueries } from '@testing-library/dom';
|
|
30
|
+
declare const queryAll: typeof domQueries.queryAllByRole;
|
|
31
|
+
declare const query: typeof domQueries.queryByRole;
|
|
32
|
+
declare const getAll: typeof domQueries.getAllByRole;
|
|
33
|
+
declare const get: typeof domQueries.getByRole;
|
|
34
|
+
declare const findAll: typeof domQueries.findAllByRole;
|
|
35
|
+
declare const find: typeof domQueries.findByRole;
|
|
36
|
+
export { queryAll as queryAllByRole, query as queryByRole, getAll as getAllByRole, get as getByRole, findAll as findAllByRole, find as findByRole, };
|
|
37
|
+
//# sourceMappingURL=role-queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-queries.d.ts","sourceRoot":"","sources":["../src/role-queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAQL,OAAO,IAAI,UAAU,EACtB,MAAM,sBAAsB,CAAC;AAmW9B,QAAA,MAAM,QAAQ,EAAqB,OAAO,UAAU,CAAC,cAAc,CAAC;AACpE,QAAA,MAAM,KAAK,EAAc,OAAO,UAAU,CAAC,WAAW,CAAC;AACvD,QAAA,MAAM,MAAM,EAAe,OAAO,UAAU,CAAC,YAAY,CAAC;AAC1D,QAAA,MAAM,GAAG,EAAY,OAAO,UAAU,CAAC,SAAS,CAAC;AACjD,QAAA,MAAM,OAAO,EAAgB,OAAO,UAAU,CAAC,aAAa,CAAC;AAC7D,QAAA,MAAM,IAAI,EAAa,OAAO,UAAU,CAAC,UAAU,CAAC;AAEpD,OAAO,EACL,QAAQ,IAAI,cAAc,EAC1B,KAAK,IAAI,WAAW,EACpB,MAAM,IAAI,YAAY,EACtB,GAAG,IAAI,SAAS,EAChB,OAAO,IAAI,aAAa,EACxB,IAAI,IAAI,UAAU,GACnB,CAAC"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `*ByRole` queries that read Lynx's accessibility attributes.
|
|
3
|
+
*
|
|
4
|
+
* Testing Library works out an element's role from `role` and HTML, and its
|
|
5
|
+
* name and state from `aria-*` attributes. A Lynx element has none of those,
|
|
6
|
+
* so on a Lynx tree Testing Library's `getByRole` found almost nothing: a
|
|
7
|
+
* `<view accessibility-traits="button">` has no ARIA role. These queries add
|
|
8
|
+
* what Lynx gives screen readers, read the way Lynx's native code reads it,
|
|
9
|
+
* to what Testing Library finds (a Lynx `<input>` is still a
|
|
10
|
+
* textbox):
|
|
11
|
+
*
|
|
12
|
+
* - Role: each trait in `accessibility-traits` that names a kind of element
|
|
13
|
+
* (`TRAIT_ROLES`), or the element's own default: an `<image>` is an image.
|
|
14
|
+
* - Exposed: a screen reader reaches an element only when it's an
|
|
15
|
+
* accessibility element. `<text>` and `<image>` are by default; anything
|
|
16
|
+
* else needs `accessibility-element="true"`. The exception is a tab bar,
|
|
17
|
+
* whose trait VoiceOver reads from the container around the tabs.
|
|
18
|
+
* `accessibility-elements-hidden` hides an element and everything inside
|
|
19
|
+
* it. With `hidden: false`, the default, the rest are left out, as Testing
|
|
20
|
+
* Library leaves out what's off the accessibility tree.
|
|
21
|
+
* - Name: `accessibility-label`, or a `<text>`'s own text when it has none.
|
|
22
|
+
* Lynx gives no other element a name from the text inside it: iOS reads an
|
|
23
|
+
* accessibility element's label and skips its children.
|
|
24
|
+
* - State: the `selected` trait is `selected: true`, and `accessibility-value`
|
|
25
|
+
* is `value: { text }`. Lynx has no other ARIA state.
|
|
26
|
+
*
|
|
27
|
+
* They take Testing Library's ByRoleOptions and throw its error messages.
|
|
28
|
+
*/
|
|
29
|
+
import { buildQueries, getConfig, getRoles, isInaccessible, prettyDOM, queries as domQueries, } from '@testing-library/dom';
|
|
30
|
+
/**
|
|
31
|
+
* The ARIA role of each Lynx trait that names a kind of element, as
|
|
32
|
+
* Testing Library spells it. iOS turns each trait into a UIAccessibilityTraits
|
|
33
|
+
* flag (`LynxConverter+UI.m`): `image` is UIAccessibilityTraitImage, the
|
|
34
|
+
* `img` role; `header` is a heading; `search`, a search field, is a searchbox;
|
|
35
|
+
* `adjustable`, a control swiped up and down through values, is a slider;
|
|
36
|
+
* and `tabbar`, a tab bar, is a tablist. The other traits describe state (`selected`,
|
|
37
|
+
* `disabled`, `updating`), behavior (`playable`, `keyboard`, `summary`), or
|
|
38
|
+
* plain text (`text`), which has no ARIA role.
|
|
39
|
+
*/
|
|
40
|
+
const TRAIT_ROLES = new Map([
|
|
41
|
+
['button', 'button'],
|
|
42
|
+
['link', 'link'],
|
|
43
|
+
['image', 'img'],
|
|
44
|
+
['header', 'heading'],
|
|
45
|
+
['search', 'searchbox'],
|
|
46
|
+
['adjustable', 'slider'],
|
|
47
|
+
['tabbar', 'tablist'],
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* The roles a screen reader reads from a container, not from an
|
|
51
|
+
* accessibility element. VoiceOver reads the tab bar trait from the view
|
|
52
|
+
* around the tabs, which can't be an accessibility element itself: that
|
|
53
|
+
* would hide the tabs inside it.
|
|
54
|
+
*/
|
|
55
|
+
const CONTAINER_ROLES = new Set(['tablist']);
|
|
56
|
+
/**
|
|
57
|
+
* Testing Library refuses a state that ARIA doesn't define for the role, such
|
|
58
|
+
* as `selected` on a button. Lynx has `selected` (a trait) and a value
|
|
59
|
+
* (`accessibility-value`) on any element, so asking for them isn't a mistake
|
|
60
|
+
* here. It only means no element can match by ARIA.
|
|
61
|
+
*/
|
|
62
|
+
const UNSUPPORTED_ARIA_STATE = /^"aria-(selected|valuetext)" is not supported on role /;
|
|
63
|
+
/**
|
|
64
|
+
* Whether `text` matches `matcher`. Text the element doesn't have (null)
|
|
65
|
+
* matches nothing.
|
|
66
|
+
*/
|
|
67
|
+
const matchesText = (text, element, matcher) => {
|
|
68
|
+
if (text === null)
|
|
69
|
+
return false;
|
|
70
|
+
if (typeof matcher === 'function')
|
|
71
|
+
return matcher(text, element);
|
|
72
|
+
if (matcher instanceof RegExp) {
|
|
73
|
+
const match = matcher.test(text);
|
|
74
|
+
// A global or sticky RegExp carries on from where the last test() left
|
|
75
|
+
// off, so the next element is tested from the start, as Testing Library
|
|
76
|
+
// does.
|
|
77
|
+
matcher.lastIndex = 0;
|
|
78
|
+
return match;
|
|
79
|
+
}
|
|
80
|
+
return text === String(matcher);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The element's traits. iOS splits `accessibility-traits` on commas and
|
|
84
|
+
* doesn't trim them, so `"button, selected"` isn't selected there: they're
|
|
85
|
+
* read the same way here. Android and HarmonyOS read the attribute as one
|
|
86
|
+
* trait, so a list only works on iOS. Without the attribute, an `<image>` has
|
|
87
|
+
* the image trait, Lynx's default for it (`LynxUIImage`), which
|
|
88
|
+
* `accessibility-traits="none"` takes away.
|
|
89
|
+
*/
|
|
90
|
+
const traitsOf = (element) => element.getAttribute('accessibility-traits')?.split(',') ??
|
|
91
|
+
(element.localName === 'image' ? ['image'] : []);
|
|
92
|
+
/**
|
|
93
|
+
* The ARIA roles the element's traits give it.
|
|
94
|
+
*/
|
|
95
|
+
const lynxRolesOf = (element) => traitsOf(element).flatMap((trait) => TRAIT_ROLES.get(trait) ?? []);
|
|
96
|
+
/**
|
|
97
|
+
* Whether the element is an accessibility element, one stop for a screen
|
|
98
|
+
* reader. Lynx makes `<text>` and `<image>` accessibility elements by default
|
|
99
|
+
* and nothing else, so a `<view>` is one only with
|
|
100
|
+
* `accessibility-element="true"`. The attribute holds a string, so any value
|
|
101
|
+
* but "false" is true, as the element directives' boolean inputs read it.
|
|
102
|
+
*/
|
|
103
|
+
const isAccessibilityElement = (element) => {
|
|
104
|
+
const flag = element.getAttribute('accessibility-element');
|
|
105
|
+
return flag === null
|
|
106
|
+
? element.localName === 'text' || element.localName === 'image'
|
|
107
|
+
: flag !== 'false';
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Whether a screen reader reads `role` on the element: from an accessibility
|
|
111
|
+
* element, or from a container for a container role, as long as nothing
|
|
112
|
+
* hides it.
|
|
113
|
+
*/
|
|
114
|
+
const exposesRole = (element, role) => (isAccessibilityElement(element) || CONTAINER_ROLES.has(role)) &&
|
|
115
|
+
element.closest('[accessibility-elements-hidden]:not([accessibility-elements-hidden="false"])') === null &&
|
|
116
|
+
// Testing Library's own check catches what Lynx doesn't draw either, such
|
|
117
|
+
// as an element styled display: none.
|
|
118
|
+
!isInaccessible(element);
|
|
119
|
+
/**
|
|
120
|
+
* What a screen reader reads as the element's name: its
|
|
121
|
+
* `accessibility-label`, or for a `<text>`, the text it shows (iOS and
|
|
122
|
+
* Android read that when the label is missing or empty).
|
|
123
|
+
*/
|
|
124
|
+
const lynxNameOf = (element) => element.getAttribute('accessibility-label') ||
|
|
125
|
+
(element.localName === 'text'
|
|
126
|
+
? element.textContent.replace(/\s+/g, ' ').trim()
|
|
127
|
+
: '');
|
|
128
|
+
/**
|
|
129
|
+
* Whether the element is in the states `options` asks for. Lynx has two:
|
|
130
|
+
* the `selected` trait, and the value in `accessibility-value`. For the rest
|
|
131
|
+
* it answers as Testing Library does for an element without the `aria-*`
|
|
132
|
+
* attribute: not busy and not current, and never pressed, expanded, at a
|
|
133
|
+
* level or at a number. `checked` never gets here: Testing Library refuses
|
|
134
|
+
* it on every role Lynx gives.
|
|
135
|
+
*/
|
|
136
|
+
const matchesState = (element, traits, { selected, busy, pressed, current, expanded, level, value = {}, }) => (selected === undefined || selected === traits.includes('selected')) &&
|
|
137
|
+
!busy &&
|
|
138
|
+
(current === undefined || current === false) &&
|
|
139
|
+
pressed === undefined &&
|
|
140
|
+
expanded === undefined &&
|
|
141
|
+
level === undefined &&
|
|
142
|
+
value.now === undefined &&
|
|
143
|
+
value.min === undefined &&
|
|
144
|
+
value.max === undefined &&
|
|
145
|
+
(value.text === undefined ||
|
|
146
|
+
matchesText(element.getAttribute('accessibility-value'), element, value.text));
|
|
147
|
+
/**
|
|
148
|
+
* The elements Lynx gives `role`.
|
|
149
|
+
*/
|
|
150
|
+
const queryAllByLynxRole = (container, role, options) => {
|
|
151
|
+
const { hidden = getConfig().defaultHidden, name, description } = options;
|
|
152
|
+
return Array.from(container.querySelectorAll('*')).filter((element) => {
|
|
153
|
+
const traits = traitsOf(element);
|
|
154
|
+
return (traits.some((trait) => TRAIT_ROLES.get(trait) === role) &&
|
|
155
|
+
(hidden || exposesRole(element, role)) &&
|
|
156
|
+
matchesState(element, traits, options) &&
|
|
157
|
+
(name === undefined ||
|
|
158
|
+
matchesText(lynxNameOf(element), element, name)) &&
|
|
159
|
+
// Lynx has no accessible description.
|
|
160
|
+
(description === undefined || matchesText('', element, description)));
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* The elements Testing Library gives `role`, from `role` attributes and HTML
|
|
165
|
+
* elements. An `accessibility-label` still names them: Lynx reads it on any
|
|
166
|
+
* element, an `<input>` included.
|
|
167
|
+
*/
|
|
168
|
+
const queryAllByAriaRole = (container, role, options) => {
|
|
169
|
+
const { name } = options;
|
|
170
|
+
try {
|
|
171
|
+
return domQueries.queryAllByRole(container, role, {
|
|
172
|
+
...options,
|
|
173
|
+
name: name === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: (accessibleName, element) => matchesText(element.getAttribute('accessibility-label') || accessibleName, element, name),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (UNSUPPORTED_ARIA_STATE.test(error.message))
|
|
180
|
+
return [];
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Every element in `container` with `role`, whether Lynx or Testing Library
|
|
186
|
+
* gives it the role, in document order. Testing Library's query runs first,
|
|
187
|
+
* so a container that isn't an element fails with its message.
|
|
188
|
+
*/
|
|
189
|
+
const queryAllByRole = (container, role, options = {}) => {
|
|
190
|
+
const found = new Set([
|
|
191
|
+
...queryAllByAriaRole(container, role, options),
|
|
192
|
+
...queryAllByLynxRole(container, role, options),
|
|
193
|
+
]);
|
|
194
|
+
return Array.from(container.querySelectorAll('*')).filter((element) => found.has(element));
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Describes a name or description matcher for an error message, as Testing
|
|
198
|
+
* Library does: a string in quotes, anything else in backticks.
|
|
199
|
+
*/
|
|
200
|
+
const describeMatcher = (label, matcher) => {
|
|
201
|
+
if (matcher === undefined)
|
|
202
|
+
return '';
|
|
203
|
+
return typeof matcher === 'string'
|
|
204
|
+
? ` and ${label} "${matcher}"`
|
|
205
|
+
: ` and ${label} \`${String(matcher)}\``;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Lists the roles in `container` with each element's name and markup, as
|
|
209
|
+
* Testing Library's error does, including the roles Lynx gives.
|
|
210
|
+
*/
|
|
211
|
+
const prettyRoles = (container, hidden) => {
|
|
212
|
+
// A Set per role lists an element once, even when both Lynx and Testing
|
|
213
|
+
// Library give it the role.
|
|
214
|
+
const elementsByRole = new Map();
|
|
215
|
+
const add = (role, element) => {
|
|
216
|
+
elementsByRole.set(role, (elementsByRole.get(role) ?? new Set()).add(element));
|
|
217
|
+
};
|
|
218
|
+
for (const element of Array.from(container.querySelectorAll('*'))) {
|
|
219
|
+
for (const role of lynxRolesOf(element)) {
|
|
220
|
+
if (hidden || exposesRole(element, role))
|
|
221
|
+
add(role, element);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// getRoles() takes the hidden option, though its types leave it out.
|
|
225
|
+
const getAriaRoles = getRoles;
|
|
226
|
+
// Testing Library lists each child's roles, which leaves out the container.
|
|
227
|
+
for (const child of Array.from(container.children)) {
|
|
228
|
+
for (const [role, elements] of Object.entries(getAriaRoles(child, { hidden }))) {
|
|
229
|
+
// Testing Library leaves the generic role out of its list too.
|
|
230
|
+
if (role !== 'generic')
|
|
231
|
+
elements.forEach((el) => add(role, el));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return Array.from(elementsByRole)
|
|
235
|
+
.map(([role, elements]) => {
|
|
236
|
+
const described = Array.from(elements)
|
|
237
|
+
.map((element) => `Name "${lynxNameOf(element)}":\n${prettyDOM(element.cloneNode(false))}`)
|
|
238
|
+
.join('\n\n');
|
|
239
|
+
return `${role}:\n\n${described}\n\n${'-'.repeat(50)}`;
|
|
240
|
+
})
|
|
241
|
+
.join('\n');
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* The error message when no element has the role, worded as Testing
|
|
245
|
+
* Library's and followed by what Lynx does differently: a trait whose role
|
|
246
|
+
* has another name, or elements with the role that screen readers can't
|
|
247
|
+
* reach.
|
|
248
|
+
*/
|
|
249
|
+
const getMissingError = (container, role, options = {}) => {
|
|
250
|
+
const { hidden = getConfig().defaultHidden, name, description } = options;
|
|
251
|
+
const nameHint = describeMatcher('name', name);
|
|
252
|
+
const traitRole = TRAIT_ROLES.get(role);
|
|
253
|
+
const traitHint = traitRole !== undefined && traitRole !== role
|
|
254
|
+
? `\n\nLynx's ${role} trait is the "${traitRole}" role, so query by "${traitRole}".`
|
|
255
|
+
: '';
|
|
256
|
+
// waitFor() turns expensive diagnostics off while it retries a find*
|
|
257
|
+
// query, and Testing Library then says only which role it looked for.
|
|
258
|
+
if (getConfig()
|
|
259
|
+
._disableExpensiveErrorDiagnostics) {
|
|
260
|
+
return `Unable to find role="${role}"${nameHint}${traitHint}`;
|
|
261
|
+
}
|
|
262
|
+
const searched = container;
|
|
263
|
+
const roles = prettyRoles(searched, hidden);
|
|
264
|
+
let roleMessage;
|
|
265
|
+
if (roles.length === 0) {
|
|
266
|
+
roleMessage = hidden
|
|
267
|
+
? 'There are no available roles.'
|
|
268
|
+
: 'There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole';
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
roleMessage =
|
|
272
|
+
`Here are the ${hidden ? 'available' : 'accessible'} roles:\n\n ${roles
|
|
273
|
+
.replace(/\n/g, '\n ')
|
|
274
|
+
.replace(/\n\s\s\n/g, '\n\n')}`.trim();
|
|
275
|
+
}
|
|
276
|
+
const unexposedHint = !hidden &&
|
|
277
|
+
queryAllByLynxRole(searched, role, { hidden: true }).some((element) => !exposesRole(element, role))
|
|
278
|
+
? `\n\nSome elements with the role "${role}" are hidden from screen readers. Lynx reaches an element other than <text> and <image> only when it sets accessibility-element="true", and nothing inside accessibility-elements-hidden="true". Set the \`hidden\` option to \`true\` to include them.`
|
|
279
|
+
: '';
|
|
280
|
+
return `Unable to find an ${hidden ? '' : 'accessible '}element with the role "${role}"${nameHint}${describeMatcher('description', description)}\n\n${roleMessage}${traitHint}${unexposedHint}`;
|
|
281
|
+
};
|
|
282
|
+
const [queryBy, getAllBy, getBy, findAllBy, findBy] = buildQueries(queryAllByRole, (_container, role, { name } = {}) => `Found multiple elements with the role "${role}"${describeMatcher('name', name)}`, getMissingError);
|
|
283
|
+
// buildQueries returns HTMLElement-typed functions. These take the generic
|
|
284
|
+
// types of Testing Library's own role queries, so they stand in for them
|
|
285
|
+
// anywhere, getByRole<TextElement>(...) included.
|
|
286
|
+
const queryAll = queryAllByRole;
|
|
287
|
+
const query = queryBy;
|
|
288
|
+
const getAll = getAllBy;
|
|
289
|
+
const get = getBy;
|
|
290
|
+
const findAll = findAllBy;
|
|
291
|
+
const find = findBy;
|
|
292
|
+
export { queryAll as queryAllByRole, query as queryByRole, getAll as getAllByRole, get as getByRole, findAll as findAllByRole, find as findByRole, };
|
package/package.json
CHANGED