@blotch/angular-lynx-testing-library 22.2.1 → 22.2.3
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 +38 -23
- package/dist/render.d.ts +5 -4
- package/dist/render.d.ts.map +1 -1
- package/dist/setup.js +72 -1
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -5,27 +5,44 @@ Testing utilities for [AngularLynx](https://github.com/Blotch-Smart-Frames/Angul
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
9
|
-
@lynx-js/testing-environment \
|
|
10
|
-
@testing-library/dom \
|
|
11
|
-
@testing-library/jest-dom \
|
|
12
|
-
vitest jsdom
|
|
8
|
+
ng generate @blotch/angular-lynx:add-testing
|
|
13
9
|
```
|
|
14
10
|
|
|
11
|
+
It installs this package (and Vitest and jsdom, if the project doesn't have them; `ng new` adds both) and makes sure `ng test` runs through AngularLynx's unit-test builder. To install it by hand: `npm install --save-dev @blotch/angular-lynx-testing-library`.
|
|
12
|
+
|
|
15
13
|
## Setup
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
None. Tests run with `ng test`, whose builder (`@blotch/angular-lynx-build:unit-test`, which `ng add` sets up) loads this package's setup before the tests whenever the project has it. There is no Vitest config or setup file to write.
|
|
16
|
+
|
|
17
|
+
The setup:
|
|
18
|
+
|
|
19
|
+
- Enables Angular JIT compilation (`@angular/compiler`), for components compiled at runtime
|
|
20
|
+
- Installs `LynxTestingEnv` on the jsdom window
|
|
21
|
+
- Switches to the Lynx main thread (where PAPI functions live)
|
|
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
|
|
24
|
+
- Installs `@testing-library/jest-dom` matchers
|
|
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.
|
|
27
|
+
|
|
28
|
+
### Without Angular CLI
|
|
29
|
+
|
|
30
|
+
Under plain Vitest, load the setup yourself and define the compile-time flags the AngularLynx runtime reads (the rsbuild plugin defines them in the app, and `ng test` sets them too):
|
|
18
31
|
|
|
19
32
|
```ts
|
|
20
33
|
// vitest.config.ts
|
|
21
34
|
import { defineConfig } from 'vitest/config';
|
|
22
|
-
import path from 'node:path';
|
|
23
35
|
|
|
24
36
|
export default defineConfig({
|
|
25
37
|
define: {
|
|
26
38
|
__DEV__: JSON.stringify(true),
|
|
27
39
|
__MAIN_THREAD__: JSON.stringify(true),
|
|
40
|
+
__WEB__: JSON.stringify(false),
|
|
41
|
+
__PROFILE__: JSON.stringify(false),
|
|
42
|
+
__ENABLE_SSR__: JSON.stringify(false),
|
|
28
43
|
},
|
|
44
|
+
// Angular's JIT compiler reads the metadata of legacy decorators.
|
|
45
|
+
oxc: { decorator: { legacy: true } },
|
|
29
46
|
test: {
|
|
30
47
|
environment: 'jsdom',
|
|
31
48
|
globals: true,
|
|
@@ -34,14 +51,6 @@ export default defineConfig({
|
|
|
34
51
|
});
|
|
35
52
|
```
|
|
36
53
|
|
|
37
|
-
The setup file:
|
|
38
|
-
|
|
39
|
-
- Enables Angular JIT compilation (`@angular/compiler`)
|
|
40
|
-
- Installs `LynxTestingEnv` on the jsdom window
|
|
41
|
-
- Switches to the Lynx main thread (where PAPI functions live)
|
|
42
|
-
- Polyfills PAPI functions the testing environment doesn't implement (`__GetParent`, `__QuerySelector`, etc.)
|
|
43
|
-
- Installs `@testing-library/jest-dom` matchers
|
|
44
|
-
|
|
45
54
|
## API
|
|
46
55
|
|
|
47
56
|
### `render(component, options?)`
|
|
@@ -144,10 +153,12 @@ console.log(Object.keys(eventMap));
|
|
|
144
153
|
|
|
145
154
|
```ts
|
|
146
155
|
import { Component } from '@angular/core';
|
|
156
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
147
157
|
import { render } from '@blotch/angular-lynx-testing-library';
|
|
148
158
|
|
|
149
159
|
@Component({
|
|
150
160
|
selector: 'greeting',
|
|
161
|
+
imports: [LYNX_ELEMENTS],
|
|
151
162
|
template: `<text>Hello, {{ name }}!</text>`,
|
|
152
163
|
})
|
|
153
164
|
class GreetingComponent {
|
|
@@ -163,13 +174,15 @@ it('renders the greeting', async () => {
|
|
|
163
174
|
### Injecting providers
|
|
164
175
|
|
|
165
176
|
```ts
|
|
166
|
-
import { InjectionToken, inject } from '@angular/core';
|
|
177
|
+
import { Component, InjectionToken, inject } from '@angular/core';
|
|
178
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
167
179
|
import { render } from '@blotch/angular-lynx-testing-library';
|
|
168
180
|
|
|
169
181
|
const GREETING = new InjectionToken<string>('GREETING');
|
|
170
182
|
|
|
171
183
|
@Component({
|
|
172
184
|
selector: 'test-greeting',
|
|
185
|
+
imports: [LYNX_ELEMENTS],
|
|
173
186
|
template: `<text>{{ greeting() }}</text>`,
|
|
174
187
|
})
|
|
175
188
|
class GreetingComponent {
|
|
@@ -189,15 +202,16 @@ it('uses the injected value', async () => {
|
|
|
189
202
|
Define the signal outside the component so both the component and the test can access it:
|
|
190
203
|
|
|
191
204
|
```ts
|
|
192
|
-
import { Component, signal
|
|
205
|
+
import { Component, signal } from '@angular/core';
|
|
206
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
193
207
|
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
194
208
|
|
|
195
209
|
const count = signal(0);
|
|
196
210
|
|
|
197
211
|
@Component({
|
|
198
212
|
selector: 'test-counter',
|
|
213
|
+
imports: [LYNX_ELEMENTS],
|
|
199
214
|
template: `<text>{{ count() }}</text>`,
|
|
200
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
201
215
|
})
|
|
202
216
|
class CounterComponent {
|
|
203
217
|
count = count;
|
|
@@ -219,7 +233,8 @@ it('updates when the signal changes', async () => {
|
|
|
219
233
|
AngularLynx event bindings use Lynx prefixes: `(bindtap)`, `(catchtap)`, `(bindinput)`, etc.
|
|
220
234
|
|
|
221
235
|
```ts
|
|
222
|
-
import { Component, signal
|
|
236
|
+
import { Component, signal } from '@angular/core';
|
|
237
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
223
238
|
import {
|
|
224
239
|
render,
|
|
225
240
|
fireEvent,
|
|
@@ -228,12 +243,12 @@ import {
|
|
|
228
243
|
|
|
229
244
|
@Component({
|
|
230
245
|
selector: 'test-button',
|
|
246
|
+
imports: [LYNX_ELEMENTS],
|
|
231
247
|
template: `
|
|
232
248
|
<view (bindtap)="increment()">
|
|
233
249
|
<text>{{ count() }}</text>
|
|
234
250
|
</view>
|
|
235
251
|
`,
|
|
236
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
237
252
|
})
|
|
238
253
|
class ButtonComponent {
|
|
239
254
|
count = signal(0);
|
|
@@ -260,10 +275,10 @@ it('shows content conditionally', async () => {
|
|
|
260
275
|
|
|
261
276
|
@Component({
|
|
262
277
|
selector: 'test-if',
|
|
278
|
+
imports: [LYNX_ELEMENTS],
|
|
263
279
|
template: `@if (visible()) {
|
|
264
280
|
<text>Hello</text>
|
|
265
281
|
}`,
|
|
266
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
267
282
|
})
|
|
268
283
|
class IfComponent {
|
|
269
284
|
visible = visible;
|
|
@@ -286,10 +301,10 @@ it('renders a dynamic list', async () => {
|
|
|
286
301
|
|
|
287
302
|
@Component({
|
|
288
303
|
selector: 'test-for',
|
|
304
|
+
imports: [LYNX_ELEMENTS],
|
|
289
305
|
template: `@for (item of items(); track item) {
|
|
290
306
|
<text>{{ item }}</text>
|
|
291
307
|
}`,
|
|
292
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
293
308
|
})
|
|
294
309
|
class ListComponent {
|
|
295
310
|
items = items;
|
|
@@ -361,7 +376,7 @@ Template binding → PAPI registration → DOM listener key:
|
|
|
361
376
|
| Auto-cleanup env var | `PTL_SKIP_AUTO_CLEANUP` | — | `ATL_SKIP_AUTO_CLEANUP` |
|
|
362
377
|
| `renderHook()` | ✓ | — | — |
|
|
363
378
|
| Snapshot serializer | ✓ (RefProxy) | — | — |
|
|
364
|
-
| Vitest plugin | ✓ | — | —
|
|
379
|
+
| Vitest plugin | ✓ | — | — (`ng test` sets up) |
|
|
365
380
|
| Named fireEvent helpers | 30+ | 16 | 16 |
|
|
366
381
|
| Thread-aware fireEvent | ✓ | ✓ | — (single thread) |
|
|
367
382
|
| Async state helper | `waitSchedule()` | `waitForUpdate()` | `waitForUpdate()` |
|
package/dist/render.d.ts
CHANGED
|
@@ -48,10 +48,11 @@ export type RenderResult<Q extends Queries = typeof domQueries> = {
|
|
|
48
48
|
* - `componentRef.injector.get(Token)` — retrieve a service provided to the app.
|
|
49
49
|
* - `componentRef.instance.someSignal.set(v)` — drive writable signal state directly.
|
|
50
50
|
*
|
|
51
|
-
* Note: `componentRef.setInput()`
|
|
52
|
-
* signal inputs (input())
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* Note: `componentRef.setInput()` needs the input metadata that ahead-of-time
|
|
52
|
+
* compilation registers for signal inputs (input()) and @Input() decorators.
|
|
53
|
+
* `ng test` compiles tests that way, so it works there. Under plain Vitest,
|
|
54
|
+
* which compiles components just in time, that metadata isn't available;
|
|
55
|
+
* expose writable signals on the component instance instead.
|
|
55
56
|
*/
|
|
56
57
|
componentRef: ComponentRef<unknown>;
|
|
57
58
|
} & BoundFunctions<typeof domQueries> & BoundFunctions<Q>;
|
package/dist/render.d.ts.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/setup.js
CHANGED
|
@@ -40,8 +40,79 @@ 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
|
+
/**
|
|
75
|
+
* Turns the globals the testing environment is about to write into plain data
|
|
76
|
+
* properties, so the writes stay on the global object.
|
|
77
|
+
*
|
|
78
|
+
* Vitest's jsdom environment defines each jsdom window property on the global
|
|
79
|
+
* as an accessor. Since Vitest 5 its setter also writes the value through to
|
|
80
|
+
* the jsdom window. A thread switch copies every thread global onto the
|
|
81
|
+
* global, and two of those writes break the window: jsdom 30's
|
|
82
|
+
* `window.document` has no setter, so assigning it threw "Cannot set property
|
|
83
|
+
* document of [object Window] which has only a getter", and `_globalProxy` is
|
|
84
|
+
* the object jsdom returns as `dom.window`, so overwriting it made Vitest's
|
|
85
|
+
* teardown fail with "dom.window.close is not a function". The thread
|
|
86
|
+
* globals' values are the window's own (`document`, `Event`) or replace them
|
|
87
|
+
* only for the test (`requestAnimationFrame`), so keeping them on the global
|
|
88
|
+
* matches what Vitest 4 did.
|
|
89
|
+
*/
|
|
90
|
+
const keepWritesOffWindow = (keys) => {
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
const descriptor = Object.getOwnPropertyDescriptor(globalThis, key);
|
|
93
|
+
if (!descriptor?.set)
|
|
94
|
+
continue;
|
|
95
|
+
Object.defineProperty(globalThis, key, {
|
|
96
|
+
value: globalThis[key],
|
|
97
|
+
writable: true,
|
|
98
|
+
configurable: true,
|
|
99
|
+
enumerable: descriptor.enumerable,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
class AngularLynxTestingEnv extends LynxTestingEnv {
|
|
104
|
+
switchToMainThread() {
|
|
105
|
+
keepWritesOffWindow(Object.getOwnPropertyNames(this.mainThread.globalThis));
|
|
106
|
+
super.switchToMainThread();
|
|
107
|
+
}
|
|
108
|
+
switchToBackgroundThread() {
|
|
109
|
+
keepWritesOffWindow(Object.getOwnPropertyNames(this.backgroundThread.globalThis));
|
|
110
|
+
super.switchToBackgroundThread();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const lynxTestingEnv = new AngularLynxTestingEnv({
|
|
114
|
+
window: globalThis.window,
|
|
115
|
+
});
|
|
45
116
|
globalThis.lynxTestingEnv = lynxTestingEnv;
|
|
46
117
|
// Expose Node so @testing-library/dom detects element types correctly.
|
|
47
118
|
globalThis.Node = globalThis.window.Node;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blotch/angular-lynx-testing-library",
|
|
3
|
-
"version": "22.2.
|
|
3
|
+
"version": "22.2.3",
|
|
4
4
|
"description": "Testing utilities for AngularLynx apps",
|
|
5
5
|
"homepage": "https://github.com/Blotch-Smart-Frames/AngularLynx#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@lynx-js/testing-environment": "^0.3.4",
|
|
46
|
-
"@testing-library/dom": "^10.4.1"
|
|
46
|
+
"@testing-library/dom": "^10.4.1",
|
|
47
|
+
"@testing-library/jest-dom": "^6.9.1"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@angular/compiler": "^22.2.1",
|
|
50
51
|
"@angular/core": "^22.2.1",
|
|
51
52
|
"@angular/platform-browser": "^22.2.1",
|
|
52
53
|
"@blotch/angular-lynx": "*",
|
|
53
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
54
54
|
"@types/node": "^22.0.0",
|
|
55
55
|
"jsdom": "^25.0.0",
|
|
56
56
|
"typescript": "~6.0.0"
|
|
@@ -60,5 +60,14 @@
|
|
|
60
60
|
"@angular/core": "^22.2.0",
|
|
61
61
|
"@angular/platform-browser": "^22.2.0",
|
|
62
62
|
"@blotch/angular-lynx": "^22.2.0"
|
|
63
|
+
},
|
|
64
|
+
"ng-update": {
|
|
65
|
+
"packageGroup": [
|
|
66
|
+
"@blotch/angular-lynx",
|
|
67
|
+
"@blotch/angular-lynx-build",
|
|
68
|
+
"@blotch/angular-lynx-testing-library",
|
|
69
|
+
"@blotch/rsbuild-plugin-angular-lynx",
|
|
70
|
+
"@blotch/angular-lynx-native"
|
|
71
|
+
]
|
|
63
72
|
}
|
|
64
73
|
}
|