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

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
@@ -5,27 +5,43 @@ Testing utilities for [AngularLynx](https://github.com/Blotch-Smart-Frames/Angul
5
5
  ## Installation
6
6
 
7
7
  ```sh
8
- npm install --save-dev @blotch/angular-lynx-testing-library \
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
- Add a `vitest.config.ts` to your project:
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
+ - Installs `@testing-library/jest-dom` matchers
24
+
25
+ `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.
26
+
27
+ ### Without Angular CLI
28
+
29
+ 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
30
 
19
31
  ```ts
20
32
  // vitest.config.ts
21
33
  import { defineConfig } from 'vitest/config';
22
- import path from 'node:path';
23
34
 
24
35
  export default defineConfig({
25
36
  define: {
26
37
  __DEV__: JSON.stringify(true),
27
38
  __MAIN_THREAD__: JSON.stringify(true),
39
+ __WEB__: JSON.stringify(false),
40
+ __PROFILE__: JSON.stringify(false),
41
+ __ENABLE_SSR__: JSON.stringify(false),
28
42
  },
43
+ // Angular's JIT compiler reads the metadata of legacy decorators.
44
+ oxc: { decorator: { legacy: true } },
29
45
  test: {
30
46
  environment: 'jsdom',
31
47
  globals: true,
@@ -34,14 +50,6 @@ export default defineConfig({
34
50
  });
35
51
  ```
36
52
 
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
53
  ## API
46
54
 
47
55
  ### `render(component, options?)`
@@ -144,10 +152,12 @@ console.log(Object.keys(eventMap));
144
152
 
145
153
  ```ts
146
154
  import { Component } from '@angular/core';
155
+ import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
147
156
  import { render } from '@blotch/angular-lynx-testing-library';
148
157
 
149
158
  @Component({
150
159
  selector: 'greeting',
160
+ imports: [LYNX_ELEMENTS],
151
161
  template: `<text>Hello, {{ name }}!</text>`,
152
162
  })
153
163
  class GreetingComponent {
@@ -163,13 +173,15 @@ it('renders the greeting', async () => {
163
173
  ### Injecting providers
164
174
 
165
175
  ```ts
166
- import { InjectionToken, inject } from '@angular/core';
176
+ import { Component, InjectionToken, inject } from '@angular/core';
177
+ import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
167
178
  import { render } from '@blotch/angular-lynx-testing-library';
168
179
 
169
180
  const GREETING = new InjectionToken<string>('GREETING');
170
181
 
171
182
  @Component({
172
183
  selector: 'test-greeting',
184
+ imports: [LYNX_ELEMENTS],
173
185
  template: `<text>{{ greeting() }}</text>`,
174
186
  })
175
187
  class GreetingComponent {
@@ -190,12 +202,14 @@ Define the signal outside the component so both the component and the test can a
190
202
 
191
203
  ```ts
192
204
  import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
205
+ import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
193
206
  import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
194
207
 
195
208
  const count = signal(0);
196
209
 
197
210
  @Component({
198
211
  selector: 'test-counter',
212
+ imports: [LYNX_ELEMENTS],
199
213
  template: `<text>{{ count() }}</text>`,
200
214
  changeDetection: ChangeDetectionStrategy.OnPush,
201
215
  })
@@ -220,6 +234,7 @@ AngularLynx event bindings use Lynx prefixes: `(bindtap)`, `(catchtap)`, `(bindi
220
234
 
221
235
  ```ts
222
236
  import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
237
+ import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
223
238
  import {
224
239
  render,
225
240
  fireEvent,
@@ -228,6 +243,7 @@ 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>
@@ -260,6 +276,7 @@ it('shows content conditionally', async () => {
260
276
 
261
277
  @Component({
262
278
  selector: 'test-if',
279
+ imports: [LYNX_ELEMENTS],
263
280
  template: `@if (visible()) {
264
281
  <text>Hello</text>
265
282
  }`,
@@ -286,6 +303,7 @@ it('renders a dynamic list', async () => {
286
303
 
287
304
  @Component({
288
305
  selector: 'test-for',
306
+ imports: [LYNX_ELEMENTS],
289
307
  template: `@for (item of items(); track item) {
290
308
  <text>{{ item }}</text>
291
309
  }`,
@@ -361,7 +379,7 @@ Template binding → PAPI registration → DOM listener key:
361
379
  | Auto-cleanup env var | `PTL_SKIP_AUTO_CLEANUP` | — | `ATL_SKIP_AUTO_CLEANUP` |
362
380
  | `renderHook()` | ✓ | — | — |
363
381
  | Snapshot serializer | ✓ (RefProxy) | — | — |
364
- | Vitest plugin | ✓ | — | — |
382
+ | Vitest plugin | ✓ | — | — (`ng test` sets up) |
365
383
  | Named fireEvent helpers | 30+ | 16 | 16 |
366
384
  | Thread-aware fireEvent | ✓ | ✓ | — (single thread) |
367
385
  | 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()` requires the full AOT pipeline to register
52
- * signal inputs (input()) or @Input() decorators in component metadata. In the
53
- * Vitest JIT environment this metadata is not available, so the recommended
54
- * pattern is to expose writable signals on the component instance instead.
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>;
@@ -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;;;;;;;;;;;OAWG;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;;;;;;;;;;;;;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
@@ -41,7 +41,48 @@ if (typeof Element.prototype.animate !== 'function') {
41
41
  Element.prototype.animate = () => ({ cancel: () => { }, finished: Promise.resolve() });
42
42
  }
43
43
  // ─── Install LynxTestingEnv ──────────────────────────────────────────────────
44
- const lynxTestingEnv = new LynxTestingEnv({ window: globalThis.window });
44
+ /**
45
+ * Turns the globals the testing environment is about to write into plain data
46
+ * properties, so the writes stay on the global object.
47
+ *
48
+ * Vitest's jsdom environment defines each jsdom window property on the global
49
+ * as an accessor. Since Vitest 5 its setter also writes the value through to
50
+ * the jsdom window. A thread switch copies every thread global onto the
51
+ * global, and two of those writes break the window: jsdom 30's
52
+ * `window.document` has no setter, so assigning it threw "Cannot set property
53
+ * document of [object Window] which has only a getter", and `_globalProxy` is
54
+ * the object jsdom returns as `dom.window`, so overwriting it made Vitest's
55
+ * teardown fail with "dom.window.close is not a function". The thread
56
+ * globals' values are the window's own (`document`, `Event`) or replace them
57
+ * only for the test (`requestAnimationFrame`), so keeping them on the global
58
+ * matches what Vitest 4 did.
59
+ */
60
+ const keepWritesOffWindow = (keys) => {
61
+ for (const key of keys) {
62
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, key);
63
+ if (!descriptor?.set)
64
+ continue;
65
+ Object.defineProperty(globalThis, key, {
66
+ value: globalThis[key],
67
+ writable: true,
68
+ configurable: true,
69
+ enumerable: descriptor.enumerable,
70
+ });
71
+ }
72
+ };
73
+ class AngularLynxTestingEnv extends LynxTestingEnv {
74
+ switchToMainThread() {
75
+ keepWritesOffWindow(Object.getOwnPropertyNames(this.mainThread.globalThis));
76
+ super.switchToMainThread();
77
+ }
78
+ switchToBackgroundThread() {
79
+ keepWritesOffWindow(Object.getOwnPropertyNames(this.backgroundThread.globalThis));
80
+ super.switchToBackgroundThread();
81
+ }
82
+ }
83
+ const lynxTestingEnv = new AngularLynxTestingEnv({
84
+ window: globalThis.window,
85
+ });
45
86
  globalThis.lynxTestingEnv = lynxTestingEnv;
46
87
  // Expose Node so @testing-library/dom detects element types correctly.
47
88
  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.0",
3
+ "version": "22.2.2",
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
  }