@blotch/angular-lynx-testing-library 22.2.2 → 22.2.4

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 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
 
@@ -20,6 +20,7 @@ The setup:
20
20
  - Installs `LynxTestingEnv` on the jsdom window
21
21
  - Switches to the Lynx main thread (where PAPI functions live)
22
22
  - Polyfills PAPI functions the testing environment doesn't implement (`__GetParent`, `__QuerySelector`, etc.)
23
+ - Makes `lynx.reportError` return for a warning, as Lynx does, instead of throwing it, so an `@boundary` shows its fallback with the default error handler
23
24
  - Installs `@testing-library/jest-dom` matchers
24
25
 
25
26
  `ng test` compiles the tests ahead of time, like the app, and checks templates strictly, as the Angular Language Service does: a component that uses Lynx elements imports `LYNX_ELEMENTS` from `@blotch/angular-lynx`. The examples below do.
@@ -74,11 +75,11 @@ const { container, getByText, queryByRole } = await render(MyComponent);
74
75
 
75
76
  **Return value:**
76
77
 
77
- | Property | Type | Description |
78
- | ------------ | ------------ | ----------------------------------------------------------------------------------------------------------- |
79
- | `container` | `Element` | The JSDOM `<page>` root element. All rendered children are descendants. |
80
- | `unmount` | `() => void` | Destroys the Angular app. |
81
- | `...queries` | — | All `@testing-library/dom` queries bound to `container` (`getByText`, `queryByRole`, `findByTestId`, etc.). |
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.). |
82
83
 
83
84
  ### `cleanup()`
84
85
 
@@ -142,9 +143,53 @@ console.log(Object.keys(eventMap));
142
143
  // ['tap', 'longtap', 'touchstart', ...]
143
144
  ```
144
145
 
145
- ### Re-exports from `@testing-library/dom`
146
+ ### `*ByAccessibilityLabel`
146
147
 
147
- `screen` · `within` · `getQueriesForElement`
148
+ 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.
149
+
150
+ ```ts
151
+ const { getByAccessibilityLabel } = await render(Toolbar);
152
+
153
+ fireEvent.tap(getByAccessibilityLabel('Close'));
154
+ ```
155
+
156
+ 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`).
157
+
158
+ ### `*ByRole`
159
+
160
+ 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`.
161
+
162
+ ```ts
163
+ const { getByRole } = await render(Toolbar);
164
+
165
+ fireEvent.tap(getByRole('button', { name: 'Save' }));
166
+ expect(getByRole('button', { name: 'Week', selected: true })).toBeTruthy();
167
+ ```
168
+
169
+ | `accessibility-traits` | Role |
170
+ | ---------------------- | ----------- |
171
+ | `button` | `button` |
172
+ | `link` | `link` |
173
+ | `image` | `img` |
174
+ | `header` | `heading` |
175
+ | `search` | `searchbox` |
176
+ | `adjustable` | `slider` |
177
+ | `tabbar` | `tablist` |
178
+
179
+ - **Role**: from each trait in the table. An `<image>` is an `img` unless it sets `accessibility-traits`.
180
+ - **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`.
181
+ - **`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.
182
+ - **`selected`**: the `selected` trait, as in `accessibility-traits="button,selected"`, which an active `routerLinkActive` element gets. iOS reads a comma-separated list. Android reads one trait (`button` or `image`) and treats a list as `none`.
183
+ - **`value: { text }`**: the `accessibility-value`.
184
+ - **Other ARIA states** (`checked`, `pressed`, `expanded`, `level`, …) have no Lynx attribute, so Lynx elements never match them.
185
+
186
+ ### `screen`, `within()` and `getQueriesForElement()`
187
+
188
+ Testing Library's, with the queries above: `screen` binds every query to `document.body`, and `within(element)` binds them to `element`. `getQueriesForElement` is `within`.
189
+
190
+ ### `queries`
191
+
192
+ 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')`.
148
193
 
149
194
  ## Usage
150
195
 
@@ -201,7 +246,7 @@ it('uses the injected value', async () => {
201
246
  Define the signal outside the component so both the component and the test can access it:
202
247
 
203
248
  ```ts
204
- import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
249
+ import { Component, signal } from '@angular/core';
205
250
  import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
206
251
  import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
207
252
 
@@ -211,7 +256,6 @@ const count = signal(0);
211
256
  selector: 'test-counter',
212
257
  imports: [LYNX_ELEMENTS],
213
258
  template: `<text>{{ count() }}</text>`,
214
- changeDetection: ChangeDetectionStrategy.OnPush,
215
259
  })
216
260
  class CounterComponent {
217
261
  count = count;
@@ -233,7 +277,7 @@ it('updates when the signal changes', async () => {
233
277
  AngularLynx event bindings use Lynx prefixes: `(bindtap)`, `(catchtap)`, `(bindinput)`, etc.
234
278
 
235
279
  ```ts
236
- import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
280
+ import { Component, signal } from '@angular/core';
237
281
  import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
238
282
  import {
239
283
  render,
@@ -249,7 +293,6 @@ import {
249
293
  <text>{{ count() }}</text>
250
294
  </view>
251
295
  `,
252
- changeDetection: ChangeDetectionStrategy.OnPush,
253
296
  })
254
297
  class ButtonComponent {
255
298
  count = signal(0);
@@ -280,7 +323,6 @@ it('shows content conditionally', async () => {
280
323
  template: `@if (visible()) {
281
324
  <text>Hello</text>
282
325
  }`,
283
- changeDetection: ChangeDetectionStrategy.OnPush,
284
326
  })
285
327
  class IfComponent {
286
328
  visible = visible;
@@ -307,7 +349,6 @@ it('renders a dynamic list', async () => {
307
349
  template: `@for (item of items(); track item) {
308
350
  <text>{{ item }}</text>
309
351
  }`,
310
- changeDetection: ChangeDetectionStrategy.OnPush,
311
352
  })
312
353
  class ListComponent {
313
354
  items = items;
@@ -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/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 '@testing-library/dom';
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
@@ -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,sBAAsB,CAAC"}
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 '@testing-library/dom';
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"}
@@ -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 queries
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, queries as domQueries } from '@testing-library/dom';
17
- export type RenderOptions<Q extends Queries = typeof domQueries> = {
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 standard `@testing-library/dom` queries.
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 = typeof domQueries> = {
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
- } & BoundFunctions<typeof domQueries> & BoundFunctions<Q>;
60
+ } & BoundLynxQueries & BoundFunctions<Q>;
59
61
  export declare const cleanup: () => void;
60
- export declare const render: <Q extends Queries = typeof domQueries>(rootComponent: Type<unknown>, options?: RenderOptions<Q>) => Promise<RenderResult<Q>>;
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.
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;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,EACZ,OAAO,IAAI,UAAU,EAEtB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,UAAU,IAAI;IACjE,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,OAAO,UAAU,IAAI;IAChE;;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,cAAc,CAAC,OAAO,UAAU,CAAC,GACnC,cAAc,CAAC,CAAC,CAAC,CAAC;AAIpB,eAAO,MAAM,OAAO,QAAO,IAM1B,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,CAAC,SAAS,OAAO,GAAG,OAAO,UAAU,EAChE,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"}
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 queries
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 { queries as domQueries, getQueriesForElement, } from '@testing-library/dom';
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
- ...domQueries,
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/dist/setup.js CHANGED
@@ -40,6 +40,36 @@ globalThis.fetch = (url, ...args) => {
40
40
  if (typeof Element.prototype.animate !== 'function') {
41
41
  Element.prototype.animate = () => ({ cancel: () => { }, finished: Promise.resolve() });
42
42
  }
43
+ // ─── lynx.reportError warnings ───────────────────────────────────────────────
44
+ /**
45
+ * Makes `lynx.reportError` return for a warning, as Lynx does, instead of
46
+ * throwing it.
47
+ *
48
+ * The testing environment's `lynx.reportError` throws whatever it's given, so
49
+ * a reported error fails the test the way it would stop the app on a device.
50
+ * On a device, though, a `warning`-level report doesn't interrupt anything,
51
+ * and that's the level `LynxErrorHandler.onViewError()` reports an error that
52
+ * an `@boundary` caught at. Angular calls onViewError() before it swaps in the
53
+ * `@error` block, so the throw stopped the swap, and a test using the default
54
+ * error handler saw neither the content nor its fallback. Other reports still
55
+ * throw.
56
+ *
57
+ * The environment gives each thread a new `lynx` whenever it injects its
58
+ * globals: once when it's created, and again on every reset(), which runs
59
+ * after each test (see index.ts). So this runs from its inject hooks, which
60
+ * must be set before the environment is created below.
61
+ */
62
+ const returnOnWarnings = (threadGlobals) => {
63
+ const { lynx } = threadGlobals;
64
+ const reportError = lynx.reportError;
65
+ lynx.reportError = (error, options) => {
66
+ if (options?.level === 'warning')
67
+ return;
68
+ reportError(error, options);
69
+ };
70
+ };
71
+ globalThis.onInjectMainThreadGlobals = returnOnWarnings;
72
+ globalThis.onInjectBackgroundThreadGlobals = returnOnWarnings;
43
73
  // ─── Install LynxTestingEnv ──────────────────────────────────────────────────
44
74
  /**
45
75
  * Turns the globals the testing environment is about to write into plain data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotch/angular-lynx-testing-library",
3
- "version": "22.2.2",
3
+ "version": "22.2.4",
4
4
  "description": "Testing utilities for AngularLynx apps",
5
5
  "homepage": "https://github.com/Blotch-Smart-Frames/AngularLynx#readme",
6
6
  "bugs": {