react-x11 2.52.0 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
# Testing
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
afternoon on a jsdom shim, because it only makes the query fail differently.
|
|
3
|
+
Testing Library does not work here and no shim will fix it: its queries are
|
|
4
|
+
built on a DOM document. Its five failure modes are in the
|
|
5
|
+
[negative-results register](../ecosystem.md#render-time); a jsdom shim only
|
|
6
|
+
makes the query fail differently.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
The replacement ships in the box: **[`react-x11/test`](../testing.md)** —
|
|
10
9
|
`renderX11`, `screen`, `userEvent`, `waitFor` and pixel assertions, over a
|
|
11
|
-
real X server running in your test process. Read that page first
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
real X server running in your test process. Read that page first. Below is
|
|
11
|
+
the hand-rolled version, for a test that wants the connection itself, and
|
|
12
|
+
the third-party runners and tools that work alongside it.
|
|
14
13
|
|
|
15
14
|
## The harness, by hand {#harness}
|
|
16
15
|
|
|
17
|
-
node-x11 ships a pure-JS X server that runs in your test process, so a
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
needs to hold the pieces directly.
|
|
16
|
+
node-x11 ships a pure-JS X server that runs in your test process, so a test
|
|
17
|
+
needs no `$DISPLAY`, no Xvfb, and no native dependency. This is what
|
|
18
|
+
`renderX11` does, spelled out for a test that holds the pieces directly.
|
|
21
19
|
|
|
22
20
|
```jsx
|
|
23
21
|
// helpers/harness.jsx
|
|
@@ -72,19 +70,17 @@ test('paints a box', async () => {
|
|
|
72
70
|
});
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
**The `tick()` is not optional.** `render`'s callback resolves at commit
|
|
76
|
-
|
|
73
|
+
**The `tick()` is not optional.** `render`'s callback resolves at commit;
|
|
74
|
+
layout runs a task later on the frame clock, so every `abs` rect is
|
|
77
75
|
`{x: 0, y: 0, width: 0, height: 0}` until you yield once. Assert on `kind`
|
|
78
|
-
and `props` immediately
|
|
76
|
+
and `props` immediately, never on geometry.
|
|
79
77
|
|
|
80
78
|
`wnd._reactX11Node` is the root of the drawn tree. Each node carries `kind`
|
|
81
|
-
(the element name), `props`, `children`, and `abs`
|
|
82
|
-
|
|
83
|
-
be one. `ownerDocument` and `getClientRects()` do exist on nodes, but only
|
|
79
|
+
(the element name), `props`, `children`, and `abs` (its laid-out rect):
|
|
80
|
+
walk it. It is not a DOM; `ownerDocument` and `getClientRects()` exist only
|
|
84
81
|
because React DevTools' `measureHostInstance` dereferences them.
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
whose `.text` is the string. A collector is three lines:
|
|
83
|
+
A `<text>` node holds `textchunk` children whose `.text` is the string:
|
|
88
84
|
|
|
89
85
|
```js
|
|
90
86
|
export function textOf(node) {
|
|
@@ -97,19 +93,20 @@ export function textOf(node) {
|
|
|
97
93
|
}
|
|
98
94
|
```
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
instead of painting them
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
To run with no X server at all — faster, and enough for most component
|
|
97
|
+
tests — pass `createRoot({ app })` a mock ntk app that records canvas
|
|
98
|
+
operations instead of painting them. `react-x11/test` exports the one the
|
|
99
|
+
renderer's unit tests use, `createMockApp()`, and
|
|
100
|
+
`renderX11(el, { backend: 'mock' })` builds it for you
|
|
101
|
+
([mock backend](../testing.md#the-mock-backend)).
|
|
105
102
|
|
|
106
103
|
## Driving events {#driving-events}
|
|
107
104
|
|
|
108
|
-
`@testing-library/user-event` throws on `setup()`
|
|
109
|
-
[register](../ecosystem.md#render-time)
|
|
110
|
-
|
|
111
|
-
the
|
|
112
|
-
actions.
|
|
105
|
+
`@testing-library/user-event` throws on `setup()`
|
|
106
|
+
([register](../ecosystem.md#render-time)); use `react-x11/test`'s
|
|
107
|
+
`userEvent`, or emit the raw ntk event on the window. react-x11 subscribes
|
|
108
|
+
to the ntk window's emitter, so this exercises the whole synthetic-event
|
|
109
|
+
path: hit testing, capture, bubble, default actions.
|
|
113
110
|
|
|
114
111
|
```jsx
|
|
115
112
|
test('a synthetic click reaches the handler', async () => {
|
|
@@ -132,27 +129,25 @@ test('a synthetic click reaches the handler', async () => {
|
|
|
132
129
|
});
|
|
133
130
|
```
|
|
134
131
|
|
|
135
|
-
Keyboard input is the same shape — `wnd.emit('keydown', { keycode,
|
|
136
|
-
buttons })` — with the keysym registered in
|
|
137
|
-
renderer can resolve it.
|
|
138
|
-
`1`, not `0`.
|
|
132
|
+
Keyboard input is the same shape — `wnd.emit('keydown', { keycode,
|
|
133
|
+
codepoint, buttons })` — with the keysym registered in
|
|
134
|
+
`app.X.keycode2keysyms` so the renderer can resolve it. `button` is the X
|
|
135
|
+
button number: left is `1`, not `0`.
|
|
139
136
|
|
|
140
137
|
## Runners {#runners}
|
|
141
138
|
|
|
142
|
-
**`node:test` (built-in) is the baseline.** react-x11's own suite uses it,
|
|
143
|
-
|
|
139
|
+
**`node:test` (built-in) is the baseline.** react-x11's own suite uses it,
|
|
140
|
+
and it handles the ESM graph without configuration. Run `.jsx` files through
|
|
144
141
|
`tsx`: `node --import tsx --test 'test/**/*.test.jsx'`.
|
|
145
142
|
|
|
146
143
|
### Vitest {#vitest}
|
|
147
144
|
|
|
148
145
|
**Out of the box.** vitest@4.1.10.
|
|
149
146
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
The one setting that matters:
|
|
147
|
+
Native ESM, built-in JSX and TypeScript transform, watch mode, snapshots. A
|
|
148
|
+
`.test.jsx` that mounts a tree, drives a prop update and asserts on the node
|
|
149
|
+
tree and the paint-op log runs green with no flags. The one setting that
|
|
150
|
+
matters:
|
|
156
151
|
|
|
157
152
|
```js
|
|
158
153
|
// vitest.config.mjs
|
|
@@ -164,15 +159,14 @@ export default defineConfig({
|
|
|
164
159
|
});
|
|
165
160
|
```
|
|
166
161
|
|
|
167
|
-
**Never `environment: 'jsdom'`.**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
across.
|
|
162
|
+
**Never `environment: 'jsdom'`.** It _defines_ `window` and `document`,
|
|
163
|
+
which silently flips environment detection in third-party libraries —
|
|
164
|
+
`@tanstack/query-core` decides it is in a browser and changes retry and
|
|
165
|
+
refetch behaviour, for one. `'node'` is the default; do not copy a web
|
|
166
|
+
project's config across.
|
|
173
167
|
|
|
174
168
|
- Vitest runs test files in worker threads by default. The in-process X
|
|
175
|
-
server works there
|
|
169
|
+
server works there; `pool: 'forks'` is the escape hatch if long-lived
|
|
176
170
|
server streams misbehave.
|
|
177
171
|
- `vi.useFakeTimers()` shares the [sinon caveat](#sinon): pass an explicit
|
|
178
172
|
`toFake` list, or React's scheduling and the renderer's paint scheduling
|
|
@@ -182,14 +176,12 @@ across.
|
|
|
182
176
|
|
|
183
177
|
**Works, but only in real ESM mode.** jest@30.4.2.
|
|
184
178
|
|
|
185
|
-
Jest's default CJS path cannot work
|
|
179
|
+
Jest's default CJS path cannot work: `babel-jest` down-compiles to
|
|
186
180
|
CommonJS, and `react-x11/src/Reconciler.js` and `src/DevToolsIntegration.js`
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
call `createRequire(import.meta.url)`, which has no CJS equivalent, so Jest's
|
|
182
|
+
own suggestions lead to
|
|
189
183
|
`SyntaxError: Cannot use 'import.meta' outside a module`. Both dead ends are
|
|
190
|
-
in the [register](../ecosystem.md#install-and-import-time).
|
|
191
|
-
|
|
192
|
-
The configuration that does work:
|
|
184
|
+
in the [register](../ecosystem.md#install-and-import-time). What works:
|
|
193
185
|
|
|
194
186
|
```js
|
|
195
187
|
// jest.config.mjs — run with NODE_OPTIONS=--experimental-vm-modules
|
|
@@ -212,22 +204,19 @@ export default {
|
|
|
212
204
|
};
|
|
213
205
|
```
|
|
214
206
|
|
|
215
|
-
`modules: false` is the load-bearing part:
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
`modules: false` is the load-bearing part: with no CJS transform, Jest runs
|
|
208
|
+
the same ESM graph as everything else. `node:test` and Vitest need none of
|
|
209
|
+
this.
|
|
218
210
|
|
|
219
211
|
## msw {#msw}
|
|
220
212
|
|
|
221
213
|
**Out of the box — `msw/node`.** msw@2.15.0.
|
|
222
214
|
|
|
223
|
-
Request-level API mocking. In Node, `setupServer` patches
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
A react-x11 app's data layer is ordinary Node networking, so msw slots in
|
|
229
|
-
with zero adapter: components genuinely call `fetch`, msw answers, state
|
|
230
|
-
updates, the renderer repaints.
|
|
215
|
+
Request-level API mocking. In Node, `setupServer` patches `http`, `https`,
|
|
216
|
+
`fetch`/undici and `XMLHttpRequest`, so `http.get(url, resolver)` handlers
|
|
217
|
+
intercept real requests — no service worker, no DOM. A react-x11 app's data
|
|
218
|
+
layer is ordinary Node networking: components call `fetch`, msw answers, the
|
|
219
|
+
renderer repaints.
|
|
231
220
|
|
|
232
221
|
```jsx
|
|
233
222
|
import { setupServer } from 'msw/node';
|
|
@@ -256,27 +245,24 @@ function User() {
|
|
|
256
245
|
}
|
|
257
246
|
```
|
|
258
247
|
|
|
259
|
-
- The fetch → `setState` → commit round trip is asynchronous, and
|
|
248
|
+
- The fetch → `setState` → commit round trip is asynchronous, and one
|
|
260
249
|
`setImmediate` tick is **not** enough. Poll the tree with a deadline.
|
|
261
|
-
- `server.listen()` patches process-wide networking. Call `server.close()`
|
|
262
|
-
`after`, or the
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
assertions (`scope.done()`) on legacy `http.request`-based clients.
|
|
250
|
+
- `server.listen()` patches process-wide networking. Call `server.close()`
|
|
251
|
+
in `after`, or the harness and msw can leave process state between files.
|
|
252
|
+
- The in-process X server connection is a plain stream, not HTTP; msw never
|
|
253
|
+
touches it.
|
|
254
|
+
- `onUnhandledRequest: 'error'` makes any unmocked request a loud failure —
|
|
255
|
+
the right default for hermetic UI tests.
|
|
256
|
+
- nock also works; prefer it only for its scope-level assertions
|
|
257
|
+
(`scope.done()`) on legacy `http.request`-based clients.
|
|
270
258
|
|
|
271
259
|
## fast-check {#fast-check}
|
|
272
260
|
|
|
273
261
|
**Out of the box.** fast-check@4.9.0.
|
|
274
262
|
|
|
275
|
-
Property-based testing:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
This is a good fit because the renderer has oracles that cannot lie:
|
|
263
|
+
Property-based testing: generators plus a property, run against hundreds
|
|
264
|
+
of random inputs, with any failure _shrunk_ to a minimal counterexample and
|
|
265
|
+
a replay seed. The renderer has two oracles that cannot lie:
|
|
280
266
|
|
|
281
267
|
- **Convergence** — `mount(A)` then `update(A → B)` must produce the same
|
|
282
268
|
tree as a fresh `mount(B)`.
|
|
@@ -346,31 +332,28 @@ await fc.assert(
|
|
|
346
332
|
```
|
|
347
333
|
|
|
348
334
|
- Compare structure (`kind` plus `abs` rects), not paint-op logs: op logs
|
|
349
|
-
accumulate across paints on the update path, so they only match after
|
|
350
|
-
|
|
351
|
-
- Keep trees shallow
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
fast-check buys is shrinking and `{ seed, path }` replay, which is worth
|
|
358
|
-
one dev-dependency for a differential suite meant to run forever.
|
|
335
|
+
accumulate across paints on the update path, so they only match after a
|
|
336
|
+
forced full repaint on both sides.
|
|
337
|
+
- Keep trees shallow; each run is a real yoga layout pass.
|
|
338
|
+
- Include a mid-sequence unmount/remount and `key` permutations in the
|
|
339
|
+
generators; index keys exercise much less of `insertBefore`/`removeChild`
|
|
340
|
+
than keyed reorders.
|
|
341
|
+
- A seeded PRNG would also do with no dependency; fast-check buys shrinking
|
|
342
|
+
and `{ seed, path }` replay.
|
|
359
343
|
|
|
360
344
|
## sinon {#sinon}
|
|
361
345
|
|
|
362
346
|
**Out of the box, with one required restriction.** sinon@22.1.0.
|
|
363
347
|
|
|
364
|
-
Spies, stubs and `@sinonjs/fake-timers`. Fake timers work — a toast
|
|
365
|
-
|
|
366
|
-
|
|
348
|
+
Spies, stubs and `@sinonjs/fake-timers`. Fake timers work — a toast that
|
|
349
|
+
auto-dismisses via `setTimeout` dismisses on `clock.tick(1000)` — but the
|
|
350
|
+
default configuration is a trap.
|
|
367
351
|
|
|
368
352
|
**Never fake `setImmediate` or `queueMicrotask`.** Sinon's default `toFake`
|
|
369
353
|
list includes them; React's scheduler drives work with them in Node, and the
|
|
370
354
|
renderer schedules paints via `setImmediate` when there is no frame clock.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
`await`. Always pass an explicit list:
|
|
355
|
+
Faked, every render, effect flush and repaint freezes until a `clock.tick` —
|
|
356
|
+
usually a test that hangs at the first `await`. Pass an explicit list:
|
|
374
357
|
|
|
375
358
|
```js
|
|
376
359
|
const clock = sinon.useFakeTimers({
|
|
@@ -378,9 +361,8 @@ const clock = sinon.useFakeTimers({
|
|
|
378
361
|
});
|
|
379
362
|
```
|
|
380
363
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
transitions.
|
|
364
|
+
The renderer's animation clock reads `Date.now()` at call time, so a fake
|
|
365
|
+
`Date` reaches style transitions.
|
|
384
366
|
|
|
385
367
|
```jsx
|
|
386
368
|
test('toast auto-dismisses', async () => {
|
|
@@ -406,39 +388,34 @@ test('toast auto-dismisses', async () => {
|
|
|
406
388
|
});
|
|
407
389
|
```
|
|
408
390
|
|
|
409
|
-
- Two `setImmediate` ticks after `clock.tick`, not one — the timer
|
|
410
|
-
`setState` commits
|
|
391
|
+
- Two `setImmediate` ticks after `clock.tick`, not one — the timer
|
|
392
|
+
callback's `setState` commits a task later.
|
|
411
393
|
- `clock.restore()` in `finally`, every time. A leaked fake `Date` breaks
|
|
412
|
-
|
|
413
|
-
- The text-input caret blink uses `setInterval`; faking
|
|
414
|
-
|
|
394
|
+
later tests' transitions and timestamps in the same process.
|
|
395
|
+
- The text-input caret blink uses `setInterval`; faking it without ticking
|
|
396
|
+
leaves the caret timer pending — harmless, but it shows in
|
|
415
397
|
`clock.countTimers()`.
|
|
416
|
-
- **Versus `node:test` mock timers**:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
(the animation clock), `clock.countTimers()`, or spies and stubs.
|
|
398
|
+
- **Versus `node:test` mock timers**: `t.mock.timers.enable({ apis:
|
|
399
|
+
['setTimeout'] })` plus `t.mock.timers.tick(1000)` handles the plain-timer
|
|
400
|
+
case with no dependency and auto-restores. Prefer it; use sinon for fake
|
|
401
|
+
`Date` (the animation clock), `clock.countTimers()`, or spies and stubs.
|
|
421
402
|
|
|
422
403
|
## pixelmatch {#pixelmatch}
|
|
423
404
|
|
|
424
405
|
**Out of the box.** pixelmatch@7.2.0.
|
|
425
406
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
[register](../ecosystem.md#render-time)), but the pipeline underneath it is
|
|
434
|
-
four lines.
|
|
407
|
+
Perceptual image diff: two RGBA buffers plus an output buffer in,
|
|
408
|
+
changed-pixel count and a painted diff image out. Anti-aliasing aware, zero
|
|
409
|
+
dependencies. Render through the harness and read pixels back with
|
|
410
|
+
`getImageData` — no display needed. `jest-image-snapshot` cannot do this (it
|
|
411
|
+
wants a Buffer and receives a window object;
|
|
412
|
+
[register](../ecosystem.md#render-time)), but the pipeline under it is four
|
|
413
|
+
lines.
|
|
435
414
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
readback races the paint: poll a known pixel before taking the comparison
|
|
441
|
-
frame.
|
|
415
|
+
`ctx.getImageData(x, y, w, h)` resolves to a canvas `ImageData`; hand
|
|
416
|
+
pixelmatch its `.data`, already straight RGBA in a `Uint8ClampedArray` with
|
|
417
|
+
opaque pixels at alpha 255. Readback races the paint: poll a known pixel
|
|
418
|
+
before taking the comparison frame.
|
|
442
419
|
|
|
443
420
|
```jsx
|
|
444
421
|
import fs from 'node:fs';
|
|
@@ -457,44 +434,41 @@ const changed = pixelmatch(before, after, diff.data, 320, 240, {
|
|
|
457
434
|
if (changed > 0) fs.writeFileSync('diff.png', PNG.sync.write(diff));
|
|
458
435
|
```
|
|
459
436
|
|
|
460
|
-
- **Fonts are the determinism problem.** Glyph indices travel on the wire,
|
|
461
|
-
pin every family with ntk's `StaticFontSource
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
- `threshold` is a per-pixel colour distance, not a pass/fail budget
|
|
465
|
-
the returned count.
|
|
466
|
-
- Buffers must have identical dimensions
|
|
467
|
-
window resize
|
|
437
|
+
- **Fonts are the determinism problem.** Glyph indices travel on the wire,
|
|
438
|
+
so pin every family with ntk's `StaticFontSource`; never let a snapshot script fall through to
|
|
439
|
+
`FontconfigFontSource`. Pinned, two renders of the same tree diff to zero
|
|
440
|
+
pixels; unpinned, the diff is noise.
|
|
441
|
+
- `threshold` is a per-pixel colour distance, not a pass/fail budget; gate
|
|
442
|
+
on the returned count.
|
|
443
|
+
- Buffers must have identical dimensions, or pixelmatch throws. After a
|
|
444
|
+
window resize, re-read rather than crop.
|
|
468
445
|
- fontkit version bumps change hinting and advance rounding. Treat pixel
|
|
469
|
-
diffs as tolerance-based regression
|
|
470
|
-
assertions
|
|
471
|
-
-
|
|
472
|
-
|
|
473
|
-
JS server.
|
|
446
|
+
diffs as tolerance-based regression tests; gate CI exactly on tree or
|
|
447
|
+
op-log assertions.
|
|
448
|
+
- Under Xvfb, expect nonzero noise from the server's own rendering; the
|
|
449
|
+
zero-noise floor is specific to the in-process JS server.
|
|
474
450
|
|
|
475
451
|
## react-test-renderer {#react-test-renderer}
|
|
476
452
|
|
|
477
453
|
**Works, but do not build on it.** react-test-renderer@19.2.8.
|
|
478
454
|
|
|
479
|
-
React's
|
|
480
|
-
element, so react-x11 vocabulary passes through
|
|
481
|
-
|
|
455
|
+
React's headless renderer treats _any_ string element type as a host
|
|
456
|
+
element, so react-x11 vocabulary passes through and coarse JSON snapshots
|
|
457
|
+
work. But:
|
|
482
458
|
|
|
483
459
|
1. **It is deprecated.** Importing it prints
|
|
484
|
-
`react-test-renderer is deprecated
|
|
485
|
-
|
|
486
|
-
2. **It bypasses the actual renderer.** No prop validation,
|
|
487
|
-
resolution,
|
|
488
|
-
react-
|
|
489
|
-
|
|
490
|
-
react-x11.
|
|
460
|
+
`react-test-renderer is deprecated`; it still ships in 19.2.x, but React
|
|
461
|
+
discourages new usage.
|
|
462
|
+
2. **It bypasses the actual renderer.** No prop validation, style
|
|
463
|
+
resolution, yoga layout or paint, so a tree that snapshots fine can still
|
|
464
|
+
throw `react-x11: <box width=…> is a style property` or lay out wrong
|
|
465
|
+
under react-x11.
|
|
491
466
|
|
|
492
467
|
The harness above is barely more code and exercises the real
|
|
493
|
-
`createInstance`/`commitUpdate` path
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
resolution, so snapshots are of _input_, not of what would render.
|
|
468
|
+
`createInstance`/`commitUpdate` path with layout. Use react-test-renderer
|
|
469
|
+
only as a bridge when porting an existing suite.
|
|
470
|
+
|
|
471
|
+
- Set `globalThis.IS_REACT_ACT_ENVIRONMENT = true` in test setup, or every
|
|
472
|
+
`act` call warns.
|
|
473
|
+
- `toJSON()` records the raw props, so snapshots are of _input_, not of what
|
|
474
|
+
would render.
|