gesso-testing 0.4.2 → 0.5.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/CHANGELOG.md +79 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# gesso-testing
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b43ada6: A frame that throws now fails the test: `renderTest`'s `frame()` and `settle()` throw the error from the frame that ran it, where before the frame was abandoned as it is in an application and the test went on against the last good picture. `allowFrameErrors: true` keeps the old behaviour for a test about that recovery.
|
|
8
|
+
- b7c9514: A paste carries the clipboard's HTML along with its text. The shell read only the plain text, so a copy from a web page or a document arrived without its headings, lists and links. `UiBeforeInputEvent`, `UiPasteEvent` and an editing group's edit now have `html` (null when the clipboard had none); the field still inserts the plain text, and an editor that keeps structure can cancel that and convert the HTML. `fireEvent.paste` takes the HTML as a second argument.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies [5a27b40]
|
|
13
|
+
- Updated dependencies [f265910]
|
|
14
|
+
- Updated dependencies [d36a2fa]
|
|
15
|
+
- Updated dependencies [c38f97e]
|
|
16
|
+
- Updated dependencies [b90ecb2]
|
|
17
|
+
- Updated dependencies [8d25c04]
|
|
18
|
+
- Updated dependencies [20ac739]
|
|
19
|
+
- Updated dependencies [303e85a]
|
|
20
|
+
- Updated dependencies [96f4bdc]
|
|
21
|
+
- Updated dependencies [0f02fc2]
|
|
22
|
+
- Updated dependencies [88d93b3]
|
|
23
|
+
- Updated dependencies [dc7f199]
|
|
24
|
+
- Updated dependencies [8fb3607]
|
|
25
|
+
- Updated dependencies [4450c5c]
|
|
26
|
+
- Updated dependencies [53b4c46]
|
|
27
|
+
- Updated dependencies [d356006]
|
|
28
|
+
- Updated dependencies [101ea8a]
|
|
29
|
+
- Updated dependencies [839011e]
|
|
30
|
+
- Updated dependencies [f0ade22]
|
|
31
|
+
- Updated dependencies [29a36ac]
|
|
32
|
+
- Updated dependencies [7753bdc]
|
|
33
|
+
- Updated dependencies [47aba08]
|
|
34
|
+
- Updated dependencies [fac08c0]
|
|
35
|
+
- Updated dependencies [8c1b8ed]
|
|
36
|
+
- Updated dependencies [5b3508d]
|
|
37
|
+
- Updated dependencies [fe0c1e0]
|
|
38
|
+
- Updated dependencies [2ce079c]
|
|
39
|
+
- Updated dependencies [b502e0e]
|
|
40
|
+
- Updated dependencies [be3e274]
|
|
41
|
+
- Updated dependencies [2bfedcd]
|
|
42
|
+
- Updated dependencies [979053a]
|
|
43
|
+
- Updated dependencies [b2dddbc]
|
|
44
|
+
- Updated dependencies [ab0c1a6]
|
|
45
|
+
- Updated dependencies [80a3577]
|
|
46
|
+
- Updated dependencies [1dfb6c2]
|
|
47
|
+
- Updated dependencies [47aba08]
|
|
48
|
+
- Updated dependencies [99538fa]
|
|
49
|
+
- Updated dependencies [fb2a6d8]
|
|
50
|
+
- Updated dependencies [28f5b72]
|
|
51
|
+
- Updated dependencies [94a9f13]
|
|
52
|
+
- Updated dependencies [427ce99]
|
|
53
|
+
- Updated dependencies [b7c9514]
|
|
54
|
+
- Updated dependencies [d3ab865]
|
|
55
|
+
- Updated dependencies [0bef08b]
|
|
56
|
+
- Updated dependencies [aa33728]
|
|
57
|
+
- Updated dependencies [af33f45]
|
|
58
|
+
- Updated dependencies [6d51def]
|
|
59
|
+
- Updated dependencies [8c4f475]
|
|
60
|
+
- Updated dependencies [93d580b]
|
|
61
|
+
- Updated dependencies [1d61bae]
|
|
62
|
+
- Updated dependencies [68b01e0]
|
|
63
|
+
- Updated dependencies [5d67836]
|
|
64
|
+
- Updated dependencies [acad77f]
|
|
65
|
+
- Updated dependencies [444371c]
|
|
66
|
+
- Updated dependencies [62883e0]
|
|
67
|
+
- Updated dependencies [d617d34]
|
|
68
|
+
- Updated dependencies [6f03f61]
|
|
69
|
+
- Updated dependencies [2c572e3]
|
|
70
|
+
- Updated dependencies [84fe6d5]
|
|
71
|
+
- Updated dependencies [e73a5fc]
|
|
72
|
+
- Updated dependencies [1de054a]
|
|
73
|
+
- Updated dependencies [f02740f]
|
|
74
|
+
- Updated dependencies [9341d31]
|
|
75
|
+
- Updated dependencies [2e3e56d]
|
|
76
|
+
- Updated dependencies [cf3b16a]
|
|
77
|
+
- Updated dependencies [5b59d13]
|
|
78
|
+
- Updated dependencies [db1a6a1]
|
|
79
|
+
- gesso-core@0.5.0
|
|
80
|
+
- gesso-framework@0.5.0
|
|
81
|
+
|
|
3
82
|
## 0.4.2
|
|
4
83
|
|
|
5
84
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ interface FireEvent {
|
|
|
79
79
|
* proxy's `beforeinput` would deliver it.
|
|
80
80
|
*/
|
|
81
81
|
type(text: string): void;
|
|
82
|
-
paste(text: string): void;
|
|
82
|
+
paste(text: string, html?: string): void;
|
|
83
83
|
}
|
|
84
84
|
declare function createFireEvent(runtime: GessoRuntime): FireEvent;
|
|
85
85
|
/** What `pointerDown`, `pointerMove` and `pointerUp` take. */
|
|
@@ -248,6 +248,11 @@ interface RenderTestOptions extends Omit<Partial<GessoRuntimeOptions>, 'root' |
|
|
|
248
248
|
autoFrame?: boolean;
|
|
249
249
|
/** Runs after the runtime is built and before it starts. */
|
|
250
250
|
onCreate?: (runtime: GessoRuntime) => void;
|
|
251
|
+
/**
|
|
252
|
+
* Let a frame that throws be abandoned, as it is in an application,
|
|
253
|
+
* rather than failing the test with the error. Default false.
|
|
254
|
+
*/
|
|
255
|
+
allowFrameErrors?: boolean;
|
|
251
256
|
}
|
|
252
257
|
/**
|
|
253
258
|
* Mounts a component or element tree with no browser and no DOM, and
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ function createFireEvent(runtime) {
|
|
|
42
42
|
tab: () => runtime.input.focus.focusNext(),
|
|
43
43
|
shiftTab: () => runtime.input.focus.focusPrevious(),
|
|
44
44
|
type: (text) => void runtime.input.editing.insertText(text),
|
|
45
|
-
paste: (text) => void runtime.input.editing.paste(text)
|
|
45
|
+
paste: (text, html) => void runtime.input.editing.paste(text, html ?? null)
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
/** One device per kind, with the ids a browser gives them, so a press and its moves agree on who made them. */
|
|
@@ -143,7 +143,7 @@ const DEFAULT_MAX_FRAMES = 100;
|
|
|
143
143
|
* is built, laid out and described before the first query.
|
|
144
144
|
*/
|
|
145
145
|
function renderTest(root, options = {}) {
|
|
146
|
-
const { autoFrame = true, onCreate, ...runtimeOptions } = options;
|
|
146
|
+
const { autoFrame = true, onCreate, allowFrameErrors = false, ...runtimeOptions } = options;
|
|
147
147
|
const context = new RecordingCanvasContext();
|
|
148
148
|
const canvas = new FakeCanvasHost(context);
|
|
149
149
|
const width = runtimeOptions.width ?? DEFAULT_WIDTH;
|
|
@@ -163,12 +163,21 @@ function renderTest(root, options = {}) {
|
|
|
163
163
|
clock: (callback) => clock = new UiManualFrameClock(callback)
|
|
164
164
|
});
|
|
165
165
|
runtime.onFrame((metrics) => frames.push(metrics));
|
|
166
|
+
let thrown = null;
|
|
167
|
+
if (!allowFrameErrors) runtime.onFrameError((message, stack) => {
|
|
168
|
+
thrown ??= stack ?? message;
|
|
169
|
+
});
|
|
166
170
|
onCreate?.(runtime);
|
|
167
171
|
runtime.start();
|
|
168
172
|
let now = 0;
|
|
169
173
|
const frame = (time) => {
|
|
170
174
|
now = time ?? now + 16;
|
|
171
175
|
if (clock.isPending) clock.tick(now);
|
|
176
|
+
if (thrown !== null) {
|
|
177
|
+
const error = thrown;
|
|
178
|
+
thrown = null;
|
|
179
|
+
throw new Error(`A frame threw and was abandoned:\n${error}`);
|
|
180
|
+
}
|
|
172
181
|
};
|
|
173
182
|
const settle = async (settleOptions = {}) => {
|
|
174
183
|
const max = settleOptions.maxFrames ?? DEFAULT_MAX_FRAMES;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/fireEvent.ts","../src/debug.ts","../src/renderTest.ts","../src/channels.ts"],"sourcesContent":["import {\n MOUSE_POINTER,\n noKeyModifiers,\n UiEventType,\n UiPointerEvent,\n type UiKeyModifiers,\n type UiNode,\n type UiPointerDevice,\n type UiPointerKind\n} from 'gesso-core';\nimport type { GessoRuntime } from 'gesso-framework';\n\nexport interface PointAt {\n x?: number;\n y?: number;\n modifiers?: Partial<UiKeyModifiers>;\n}\n\n/**\n * The events a test sends, bound to one render.\n *\n * Bound rather than global — `@testing-library`'s `fireEvent` is a free\n * function because the DOM it acts on is a global, and a Gesso runtime\n * is not: two can be mounted in one file, in one worker, at once. So\n * this comes off the render (`ui.fireEvent.click(node)`), and\n * `createFireEvent(runtime)` is there for anyone driving a runtime this\n * library did not mount.\n *\n * Everything here goes through the same controllers a real pointer and\n * a real keyboard go through. That is the whole point: a test that\n * poked a component's handler directly would pass while the component\n * was unreachable by mouse, by keyboard, and by a screen reader.\n */\nexport interface FireEvent {\n /**\n * A click on a node, with no hit test.\n *\n * Addressed by node rather than by coordinate because that is what a\n * query returns, and because a component's job is to respond to a\n * click on it — whether the pixel at (x, y) lands on it is the hit\n * tester's business and `LayoutEngine`'s specs already cover it. Use\n * `pointerDown`/`pointerUp` for the coordinate path.\n */\n click(node: UiNode, at?: PointAt): void;\n /**\n * Two clicks on a node and the DoubleClick that follows them, in the\n * order a real pointer produces them. Use the coordinate path twice\n * to ask whether two presses *make* a double click; this is for\n * asking what a double click does.\n */\n doubleClick(node: UiNode, at?: PointAt): void;\n /** A request for a node's menu: the right button, or a held finger. */\n contextMenu(node: UiNode, at?: PointAt): void;\n /** A press on a node without the release, for hold and drag behaviour. */\n pressDown(node: UiNode, at?: PointAt): void;\n pressUp(node: UiNode, at?: PointAt): void;\n /** A press-and-move on a node: what grabbing a divider or a thumb is. */\n pan(node: UiNode, x: number, y: number): void;\n\n /**\n * The coordinate path, through the hit tester.\n *\n * `pointer` says what made the contact — a finger, a pen — for the\n * behaviours that are only a finger's: a pan that scrolls, a long\n * press that asks for a menu, a larger slop. A mouse when absent, as\n * everywhere else a device is not named.\n */\n pointerDown(x: number, y: number, options?: PointerOptions): void;\n pointerMove(x: number, y: number, options?: PointerOptions): void;\n pointerUp(x: number, y: number, options?: PointerOptions): void;\n wheel(options: {\n x?: number;\n y?: number;\n deltaX?: number;\n deltaY?: number;\n modifiers?: Partial<UiKeyModifiers>;\n }): void;\n\n /** A key press at whatever has focus. */\n keyDown(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n keyUp(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n /** Down then up, which is what pressing a key is. */\n press(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n\n focus(node: UiNode): boolean;\n blur(): void;\n tab(): boolean;\n shiftTab(): boolean;\n\n /**\n * Types into the focused editable, one insertion, as the editing\n * proxy's `beforeinput` would deliver it.\n */\n type(text: string): void;\n paste(text: string): void;\n}\n\nfunction modifiersOf(partial: Partial<UiKeyModifiers> | undefined): UiKeyModifiers {\n return { ...noKeyModifiers(), ...partial };\n}\n\nexport function createFireEvent(runtime: GessoRuntime): FireEvent {\n const dispatch = (type: UiEventType, node: UiNode, at: PointAt = {}): void => {\n runtime.input.dispatcher.dispatch(\n new UiPointerEvent(type, at.x ?? 0, at.y ?? 0, 1, modifiersOf(at.modifiers)),\n node\n );\n };\n\n return {\n click: (node, at) => dispatch(UiEventType.Click, node, at),\n doubleClick: (node, at) => {\n dispatch(UiEventType.Click, node, at);\n dispatch(UiEventType.Click, node, at);\n dispatch(UiEventType.DoubleClick, node, at);\n },\n contextMenu: (node, at) => dispatch(UiEventType.ContextMenu, node, at),\n pressDown: (node, at) => dispatch(UiEventType.PointerDown, node, at),\n pressUp: (node, at) => dispatch(UiEventType.PointerUp, node, at),\n pan: (node, x, y) => dispatch(UiEventType.PanMove, node, { x, y }),\n\n pointerDown: (x, y, options = {}) =>\n void runtime.input.pointer.pointerDown(\n x,\n y,\n options.buttons ?? 1,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n pointerMove: (x, y, options = {}) =>\n void runtime.input.pointer.pointerMove(\n x,\n y,\n options.buttons ?? 0,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n pointerUp: (x, y, options = {}) =>\n void runtime.input.pointer.pointerUp(\n x,\n y,\n options.buttons ?? 0,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n wheel: options =>\n void runtime.input.wheel.wheel(\n options.x ?? 0,\n options.y ?? 0,\n options.deltaX ?? 0,\n options.deltaY ?? 0,\n modifiersOf(options.modifiers)\n ),\n\n keyDown: (key, modifiers) => void runtime.input.keyboard.keyDown(key, modifiersOf(modifiers)),\n keyUp: (key, modifiers) => void runtime.input.keyboard.keyUp(key, modifiersOf(modifiers)),\n press: (key, modifiers) => {\n runtime.input.keyboard.keyDown(key, modifiersOf(modifiers));\n runtime.input.keyboard.keyUp(key, modifiersOf(modifiers));\n },\n\n focus: node => runtime.input.focus.focus(node),\n blur: () => runtime.input.focus.blur(),\n tab: () => runtime.input.focus.focusNext(),\n shiftTab: () => runtime.input.focus.focusPrevious(),\n\n type: text => void runtime.input.editing.insertText(text),\n paste: text => void runtime.input.editing.paste(text)\n };\n}\n\n/** What `pointerDown`, `pointerMove` and `pointerUp` take. */\nexport interface PointerOptions {\n readonly buttons?: number;\n readonly modifiers?: Partial<UiKeyModifiers>;\n readonly pointer?: UiPointerKind;\n}\n\n/** One device per kind, with the ids a browser gives them, so a press and its moves agree on who made them. */\nconst DEVICES: Readonly<Record<UiPointerKind, UiPointerDevice>> = {\n mouse: MOUSE_POINTER,\n pen: Object.freeze({ id: 3, kind: 'pen' as const }),\n touch: Object.freeze({ id: 2, kind: 'touch' as const })\n};\n\nfunction deviceOf(kind: UiPointerKind | undefined): UiPointerDevice {\n return DEVICES[kind ?? 'mouse'];\n}\n","import type { LayoutBox, UiNode } from 'gesso-core';\nimport type { GessoRuntime } from 'gesso-framework';\n\n/** `12,8 64×32`, rounded, because a test reader wants the shape not the sixteenths. */\nfunction formatBox(box: LayoutBox): string {\n const round = (value: number): number => Math.round(value * 100) / 100;\n return `${round(box.x)},${round(box.y)} ${round(box.width)}×${round(box.height)}`;\n}\n\n/**\n * The tree as text, one node per line.\n *\n * Printed with what a query would have matched on, in the order a\n * query would have considered it: the node's type and id, then the\n * role and accessible name its semantics record carries, then its\n * states, then the text it draws, then its box. A missed query prints\n * this, and the answer to \"why did `getByRole('switch')` not find it\"\n * is usually on the line for the node that turned out to be a\n * `checkbox`.\n *\n * Nodes with no semantics record are printed too, dimmer in content if\n * not in colour: a `Row` that exists only for layout is exactly the\n * thing a reader needs to see is *not* in the tree an assistive\n * technology reads.\n */\nexport function formatTree(runtime: GessoRuntime, root: UiNode): string {\n const semantics = runtime.semanticsTree();\n const lines: string[] = [];\n\n const visit = (node: UiNode, depth: number): void => {\n const parts: string[] = [`${node.type}#${node.id}`];\n const record = semantics.get(node.id);\n if (record !== undefined) {\n if (record.role !== undefined) {\n parts.push(`role=${record.role}`);\n }\n if (record.label !== undefined) {\n parts.push(`name=${JSON.stringify(record.label)}`);\n }\n if (record.states !== undefined && record.states.length > 0) {\n parts.push(`states=[${record.states.join(',')}]`);\n }\n if (record.disabled === true) {\n parts.push('disabled');\n }\n if (record.valueNow !== undefined) {\n parts.push(`value=${record.valueNow}`);\n }\n }\n const text = node.properties.get('text');\n if (typeof text === 'string' && text.length > 0 && record?.label !== text) {\n parts.push(`text=${JSON.stringify(text)}`);\n }\n if (node.properties.get('visible') === false) {\n parts.push('hidden');\n }\n let box: string;\n try {\n box = formatBox(runtime.debugLayoutBox(node));\n } catch {\n // A node built this frame and not yet laid out has no box; that\n // is worth saying rather than worth throwing from a debug print.\n box = 'not laid out';\n }\n parts.push(`[${box}]`);\n lines.push(`${' '.repeat(depth)}${parts.join(' ')}`);\n for (let child = node.firstChild; child !== null; child = child.nextSibling) {\n visit(child, depth + 1);\n }\n };\n\n visit(root, 0);\n return lines.join('\\n');\n}\n","import {\n CharacterCountTextMeasurer,\n formatExplanation,\n UiManualFrameClock,\n type CanvasHost,\n type LayoutBox,\n type LayoutExplanation,\n type TextMeasurer,\n type UiNode,\n type UiSemanticsMap,\n type UiSemanticsRecord\n} from 'gesso-core';\nimport { FakeCanvasHost, RecordingCanvasContext, type RecordedCall } from 'gesso-core/testing';\nimport { GessoRuntime, type FrameMetrics, type FrameworkChild, type GessoRuntimeOptions } from 'gesso-framework';\n\nimport { createFireEvent, type FireEvent } from './fireEvent';\nimport { formatTree } from './debug';\nimport { createQueries, type Queries } from './queries';\nimport { registerRendered } from './registry';\n\n/**\n * The pieces of a mounted tree that are not queries.\n *\n * Split out so the queries can be mixed in without either half having\n * to know the other's shape.\n */\nexport interface RenderedBase {\n /** The runtime itself, for anything this library does not wrap. */\n readonly runtime: GessoRuntime;\n /** The clock frames are driven from; `frame()` is the usual way in. */\n readonly clock: UiManualFrameClock;\n /** Metrics for every frame that has run, in order. */\n readonly frames: readonly FrameMetrics[];\n /** Every canvas call since the last `clearDraws()`. An escape hatch. */\n readonly draws: readonly RecordedCall[];\n /** The event senders, bound to this runtime. */\n readonly fireEvent: FireEvent;\n\n /**\n * Runs the pending frame, if there is one.\n *\n * Without a time it advances 16 ms per call, so a test can drive\n * frames without keeping a clock of its own.\n */\n frame(time?: number): void;\n\n /**\n * Runs frames until nothing is pending, then resolves.\n *\n * Awaits the microtask queue between frames, which is what makes it\n * the right thing after anything asynchronous: a channel patch, an\n * image resolving, a promise a component awaited. Throws rather than\n * spinning forever if the tree never goes quiet.\n */\n settle(options?: { maxFrames?: number }): Promise<void>;\n\n /** The node's border box in layout-root coordinates, before scrolling. */\n getLayout(node: UiNode): LayoutBox;\n /**\n * Where the node is *seen*: scroll offsets and sticky shifts applied.\n *\n * `getLayout` answers where a node was laid out, which for anything\n * inside a scroll container is not where it is. Nothing about\n * scrolling or sticky could be asserted without this — a sticky\n * header's laid-out box is at the top of the content whether or not\n * sticky works at all, so a spec written against `getLayout` passes\n * for a header that scrolled away.\n */\n getVisibleBox(node: UiNode): LayoutBox;\n /** Why the node has the size it has, as `LayoutEngine.explain` gives it. */\n explain(node: UiNode): LayoutExplanation;\n /** The same explanation as the sentences `formatExplanation` prints. */\n explainText(node: UiNode): string;\n\n /**\n * The semantics record for a node: what a screen reader would\n * announce for it. Throws, with the tree, when the node has none —\n * which is nearly always the finding rather than an inconvenience.\n */\n getSemantics(node: UiNode): UiSemanticsRecord;\n /** The same, returning null instead of throwing. */\n querySemantics(node: UiNode): UiSemanticsRecord | null;\n /** The whole semantics tree, as the accessibility mirror has it. */\n semanticsTree(): UiSemanticsMap;\n\n /** The tree as text: type, role, name, states and box, one node per line. */\n debug(node?: UiNode): string;\n\n /** Empties the recorded canvas calls. */\n clearDraws(): void;\n /** Stops the runtime and releases what it holds. */\n unmount(): void;\n}\n\nexport type Rendered = RenderedBase & Queries;\n\nexport interface RenderTestOptions extends Omit<Partial<GessoRuntimeOptions>, 'root' | 'canvas' | 'clock'> {\n /**\n * Whether to run the first frame before returning. Default true.\n *\n * Turn it off to watch the first frame happen — a listener attached\n * in `onCreate` sees it either way, but a spec asserting on what the\n * first frame *did* needs to be holding the result when it runs.\n */\n autoFrame?: boolean;\n /** Runs after the runtime is built and before it starts. */\n onCreate?: (runtime: GessoRuntime) => void;\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 600;\nconst DEFAULT_MAX_FRAMES = 100;\n\n/**\n * Mounts a component or element tree with no browser and no DOM, and\n * hands back the queries, the events and the layout answers.\n *\n * ```ts\n * const ui = renderTest(createComponent(Checkbox, { label: 'Wrap lines' }));\n * ui.fireEvent.click(ui.getByRole('checkbox'));\n * ui.frame();\n * expect(ui.getSemantics(ui.getByRole('checkbox')).states).toEqual(['checked']);\n * ```\n *\n * Three things are decided here rather than by the caller, because\n * getting any of them wrong makes a test that passes for the wrong\n * reason:\n *\n * - **The clock is manual.** Frames happen when the test says so, so\n * an assertion never races a scheduler.\n * - **Text is measured by `CharacterCountTextMeasurer`**, not by the\n * canvas double. A double's `measureText` answers the same width\n * whatever the font size, so a heading and its caption would come\n * out the same size; the deterministic measurer is proportional to\n * the font size and identical on every machine.\n * - **A first frame has run** by the time this returns, so the graph\n * is built, laid out and described before the first query.\n */\nexport function renderTest(root: FrameworkChild, options: RenderTestOptions = {}): Rendered {\n const { autoFrame = true, onCreate, ...runtimeOptions } = options;\n const context = new RecordingCanvasContext();\n const canvas: CanvasHost = new FakeCanvasHost(context);\n const width = runtimeOptions.width ?? DEFAULT_WIDTH;\n const height = runtimeOptions.height ?? DEFAULT_HEIGHT;\n canvas.width = width;\n canvas.height = height;\n\n const measurer: TextMeasurer = runtimeOptions.textMeasurer ?? new CharacterCountTextMeasurer();\n const frames: FrameMetrics[] = [];\n let clock!: UiManualFrameClock;\n const runtime = new GessoRuntime({\n ...runtimeOptions,\n width,\n height,\n root,\n canvas,\n textMeasurer: measurer,\n clock: callback => (clock = new UiManualFrameClock(callback))\n });\n runtime.onFrame(metrics => frames.push(metrics));\n onCreate?.(runtime);\n runtime.start();\n\n let now = 0;\n const frame = (time?: number): void => {\n now = time ?? now + 16;\n if (clock.isPending) {\n clock.tick(now);\n }\n };\n\n const settle = async (settleOptions: { maxFrames?: number } = {}): Promise<void> => {\n const max = settleOptions.maxFrames ?? DEFAULT_MAX_FRAMES;\n for (let index = 0; index <= max; index++) {\n // A macrotask, so a resolved fetch/decode chain gets to run too,\n // not just the microtasks a queued promise leaves behind.\n await new Promise(resolve => setTimeout(resolve, 0));\n if (!clock.isPending) {\n return;\n }\n frame();\n }\n throw new Error(\n `settle() ran ${max} frames and the tree was still asking for another. ` +\n `Something is scheduling a frame every frame; drive it with frame() instead.`\n );\n };\n\n const base: RenderedBase = {\n runtime,\n get clock() {\n return clock;\n },\n frames,\n get draws() {\n return context.calls;\n },\n fireEvent: createFireEvent(runtime),\n frame,\n settle,\n getLayout: node => runtime.debugLayoutBox(node),\n getVisibleBox: node => runtime.debugVisibleBox(node),\n explain: node => runtime.explain(node),\n explainText: node => formatExplanation(runtime.explain(node)),\n getSemantics: node => {\n const record = runtime.semanticsTree().get(node.id);\n if (record === undefined) {\n throw new Error(\n `'${node.id}' is not in the semantics tree: nothing gives it a role or a label, ` +\n `so no assistive technology can see it.\\n\\n${formatTree(runtime, runtime.layoutRoot())}`\n );\n }\n return record;\n },\n querySemantics: node => runtime.semanticsTree().get(node.id) ?? null,\n semanticsTree: () => runtime.semanticsTree(),\n debug: node => formatTree(runtime, node ?? runtime.layoutRoot()),\n clearDraws: () => {\n context.calls.length = 0;\n },\n unmount: () => runtime.dispose()\n };\n\n const rendered: Rendered = { ...base, ...createQueries(base) };\n registerRendered(runtime.layoutRoot(), rendered);\n if (autoFrame) {\n frame(0);\n }\n return rendered;\n}\n","import {\n createChannelRegistry,\n type ChannelRegistry,\n type ChannelReplica,\n type ChannelToken,\n type ServedChannel\n} from 'gesso-framework';\n\n/** What `serveForTest` hands back: the replicas a screen would bind to, and a way to wait for patches. */\nexport interface ServedForTest {\n /** The replica of one served channel, as `ctx.channel(token)` would return it. */\n get<V extends object, C extends object>(token: ChannelToken<V, C>): ChannelReplica<V, C>;\n /**\n * The registry, for `renderTest(component, { channels: served.registry })`.\n *\n * Without it there is no supported way to render a component against\n * served channels: `serveForTest` answers \"did the wiring work\" and\n * `renderTest` answers \"what does it look like\", and a screen driven\n * by a worker is exactly the case that needs both at once.\n */\n readonly registry: ChannelRegistry;\n /**\n * Waits for the patch stream to deliver. With a condition, until it\n * holds; without one, until two turns of the event loop have passed\n * with the ports drained, which is enough for a command's effect to\n * come back as a patch.\n */\n settle(until?: () => boolean, timeoutMs?: number): Promise<void>;\n /** Every error a served channel reported, in order. */\n readonly errors: readonly string[];\n dispose(): void;\n}\n\n/**\n * Serves channels the way an application worker does, over a real patch\n * stream, with no worker: the same `serveChannels` data goes in, and the\n * replicas a screen would read come out.\n *\n * This is the spec for a worker's *wiring*, which the domain classes'\n * own specs do not cover: that a command reaches its handler, that its\n * effect comes back as a patch to the right key, that a view key is\n * plain data. The domain classes stay as testable as they were; this\n * tests the lines between them and the barrier.\n *\n * const served = serveForTest(transitionsChannels(catalogue, queue));\n * const view = served.get(Queue);\n * view.send.play({ playlistId: '1' });\n * await served.settle(() => view.view.current.value !== null);\n */\nexport function serveForTest(channels: readonly ServedChannel[]): ServedForTest {\n const errors: string[] = [];\n const handle = createChannelRegistry(\n channels.map(served => ({ token: served.token, source: served.source })),\n (_name, message) => errors.push(message)\n );\n return {\n errors,\n registry: handle.registry,\n get: token => handle.registry.get(token),\n async settle(until, timeoutMs = 2000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n if (until === undefined) {\n for (let turn = 0; turn < 2; turn++) {\n await new Promise(resolve => setTimeout(resolve, 0));\n }\n return;\n }\n while (!until()) {\n if (Date.now() > deadline) {\n throw new Error(`serveForTest: timed out after ${timeoutMs} ms waiting for the condition to hold.`);\n }\n await new Promise(resolve => setTimeout(resolve, 5));\n }\n },\n dispose: () => handle.dispose()\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/fireEvent.ts","../src/debug.ts","../src/renderTest.ts","../src/channels.ts"],"sourcesContent":["import {\n MOUSE_POINTER,\n noKeyModifiers,\n UiEventType,\n UiPointerEvent,\n type UiKeyModifiers,\n type UiNode,\n type UiPointerDevice,\n type UiPointerKind\n} from 'gesso-core';\nimport type { GessoRuntime } from 'gesso-framework';\n\nexport interface PointAt {\n x?: number;\n y?: number;\n modifiers?: Partial<UiKeyModifiers>;\n}\n\n/**\n * The events a test sends, bound to one render.\n *\n * Bound rather than global — `@testing-library`'s `fireEvent` is a free\n * function because the DOM it acts on is a global, and a Gesso runtime\n * is not: two can be mounted in one file, in one worker, at once. So\n * this comes off the render (`ui.fireEvent.click(node)`), and\n * `createFireEvent(runtime)` is there for anyone driving a runtime this\n * library did not mount.\n *\n * Everything here goes through the same controllers a real pointer and\n * a real keyboard go through. That is the whole point: a test that\n * poked a component's handler directly would pass while the component\n * was unreachable by mouse, by keyboard, and by a screen reader.\n */\nexport interface FireEvent {\n /**\n * A click on a node, with no hit test.\n *\n * Addressed by node rather than by coordinate because that is what a\n * query returns, and because a component's job is to respond to a\n * click on it — whether the pixel at (x, y) lands on it is the hit\n * tester's business and `LayoutEngine`'s specs already cover it. Use\n * `pointerDown`/`pointerUp` for the coordinate path.\n */\n click(node: UiNode, at?: PointAt): void;\n /**\n * Two clicks on a node and the DoubleClick that follows them, in the\n * order a real pointer produces them. Use the coordinate path twice\n * to ask whether two presses *make* a double click; this is for\n * asking what a double click does.\n */\n doubleClick(node: UiNode, at?: PointAt): void;\n /** A request for a node's menu: the right button, or a held finger. */\n contextMenu(node: UiNode, at?: PointAt): void;\n /** A press on a node without the release, for hold and drag behaviour. */\n pressDown(node: UiNode, at?: PointAt): void;\n pressUp(node: UiNode, at?: PointAt): void;\n /** A press-and-move on a node: what grabbing a divider or a thumb is. */\n pan(node: UiNode, x: number, y: number): void;\n\n /**\n * The coordinate path, through the hit tester.\n *\n * `pointer` says what made the contact — a finger, a pen — for the\n * behaviours that are only a finger's: a pan that scrolls, a long\n * press that asks for a menu, a larger slop. A mouse when absent, as\n * everywhere else a device is not named.\n */\n pointerDown(x: number, y: number, options?: PointerOptions): void;\n pointerMove(x: number, y: number, options?: PointerOptions): void;\n pointerUp(x: number, y: number, options?: PointerOptions): void;\n wheel(options: {\n x?: number;\n y?: number;\n deltaX?: number;\n deltaY?: number;\n modifiers?: Partial<UiKeyModifiers>;\n }): void;\n\n /** A key press at whatever has focus. */\n keyDown(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n keyUp(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n /** Down then up, which is what pressing a key is. */\n press(key: string, modifiers?: Partial<UiKeyModifiers>): void;\n\n focus(node: UiNode): boolean;\n blur(): void;\n tab(): boolean;\n shiftTab(): boolean;\n\n /**\n * Types into the focused editable, one insertion, as the editing\n * proxy's `beforeinput` would deliver it.\n */\n type(text: string): void;\n paste(text: string, html?: string): void;\n}\n\nfunction modifiersOf(partial: Partial<UiKeyModifiers> | undefined): UiKeyModifiers {\n return { ...noKeyModifiers(), ...partial };\n}\n\nexport function createFireEvent(runtime: GessoRuntime): FireEvent {\n const dispatch = (type: UiEventType, node: UiNode, at: PointAt = {}): void => {\n runtime.input.dispatcher.dispatch(\n new UiPointerEvent(type, at.x ?? 0, at.y ?? 0, 1, modifiersOf(at.modifiers)),\n node\n );\n };\n\n return {\n click: (node, at) => dispatch(UiEventType.Click, node, at),\n doubleClick: (node, at) => {\n dispatch(UiEventType.Click, node, at);\n dispatch(UiEventType.Click, node, at);\n dispatch(UiEventType.DoubleClick, node, at);\n },\n contextMenu: (node, at) => dispatch(UiEventType.ContextMenu, node, at),\n pressDown: (node, at) => dispatch(UiEventType.PointerDown, node, at),\n pressUp: (node, at) => dispatch(UiEventType.PointerUp, node, at),\n pan: (node, x, y) => dispatch(UiEventType.PanMove, node, { x, y }),\n\n pointerDown: (x, y, options = {}) =>\n void runtime.input.pointer.pointerDown(\n x,\n y,\n options.buttons ?? 1,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n pointerMove: (x, y, options = {}) =>\n void runtime.input.pointer.pointerMove(\n x,\n y,\n options.buttons ?? 0,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n pointerUp: (x, y, options = {}) =>\n void runtime.input.pointer.pointerUp(\n x,\n y,\n options.buttons ?? 0,\n modifiersOf(options.modifiers),\n deviceOf(options.pointer)\n ),\n wheel: options =>\n void runtime.input.wheel.wheel(\n options.x ?? 0,\n options.y ?? 0,\n options.deltaX ?? 0,\n options.deltaY ?? 0,\n modifiersOf(options.modifiers)\n ),\n\n keyDown: (key, modifiers) => void runtime.input.keyboard.keyDown(key, modifiersOf(modifiers)),\n keyUp: (key, modifiers) => void runtime.input.keyboard.keyUp(key, modifiersOf(modifiers)),\n press: (key, modifiers) => {\n runtime.input.keyboard.keyDown(key, modifiersOf(modifiers));\n runtime.input.keyboard.keyUp(key, modifiersOf(modifiers));\n },\n\n focus: node => runtime.input.focus.focus(node),\n blur: () => runtime.input.focus.blur(),\n tab: () => runtime.input.focus.focusNext(),\n shiftTab: () => runtime.input.focus.focusPrevious(),\n\n type: text => void runtime.input.editing.insertText(text),\n paste: (text, html) => void runtime.input.editing.paste(text, html ?? null)\n };\n}\n\n/** What `pointerDown`, `pointerMove` and `pointerUp` take. */\nexport interface PointerOptions {\n readonly buttons?: number;\n readonly modifiers?: Partial<UiKeyModifiers>;\n readonly pointer?: UiPointerKind;\n}\n\n/** One device per kind, with the ids a browser gives them, so a press and its moves agree on who made them. */\nconst DEVICES: Readonly<Record<UiPointerKind, UiPointerDevice>> = {\n mouse: MOUSE_POINTER,\n pen: Object.freeze({ id: 3, kind: 'pen' as const }),\n touch: Object.freeze({ id: 2, kind: 'touch' as const })\n};\n\nfunction deviceOf(kind: UiPointerKind | undefined): UiPointerDevice {\n return DEVICES[kind ?? 'mouse'];\n}\n","import type { LayoutBox, UiNode } from 'gesso-core';\nimport type { GessoRuntime } from 'gesso-framework';\n\n/** `12,8 64×32`, rounded, because a test reader wants the shape not the sixteenths. */\nfunction formatBox(box: LayoutBox): string {\n const round = (value: number): number => Math.round(value * 100) / 100;\n return `${round(box.x)},${round(box.y)} ${round(box.width)}×${round(box.height)}`;\n}\n\n/**\n * The tree as text, one node per line.\n *\n * Printed with what a query would have matched on, in the order a\n * query would have considered it: the node's type and id, then the\n * role and accessible name its semantics record carries, then its\n * states, then the text it draws, then its box. A missed query prints\n * this, and the answer to \"why did `getByRole('switch')` not find it\"\n * is usually on the line for the node that turned out to be a\n * `checkbox`.\n *\n * Nodes with no semantics record are printed too, dimmer in content if\n * not in colour: a `Row` that exists only for layout is exactly the\n * thing a reader needs to see is *not* in the tree an assistive\n * technology reads.\n */\nexport function formatTree(runtime: GessoRuntime, root: UiNode): string {\n const semantics = runtime.semanticsTree();\n const lines: string[] = [];\n\n const visit = (node: UiNode, depth: number): void => {\n const parts: string[] = [`${node.type}#${node.id}`];\n const record = semantics.get(node.id);\n if (record !== undefined) {\n if (record.role !== undefined) {\n parts.push(`role=${record.role}`);\n }\n if (record.label !== undefined) {\n parts.push(`name=${JSON.stringify(record.label)}`);\n }\n if (record.states !== undefined && record.states.length > 0) {\n parts.push(`states=[${record.states.join(',')}]`);\n }\n if (record.disabled === true) {\n parts.push('disabled');\n }\n if (record.valueNow !== undefined) {\n parts.push(`value=${record.valueNow}`);\n }\n }\n const text = node.properties.get('text');\n if (typeof text === 'string' && text.length > 0 && record?.label !== text) {\n parts.push(`text=${JSON.stringify(text)}`);\n }\n if (node.properties.get('visible') === false) {\n parts.push('hidden');\n }\n let box: string;\n try {\n box = formatBox(runtime.debugLayoutBox(node));\n } catch {\n // A node built this frame and not yet laid out has no box; that\n // is worth saying rather than worth throwing from a debug print.\n box = 'not laid out';\n }\n parts.push(`[${box}]`);\n lines.push(`${' '.repeat(depth)}${parts.join(' ')}`);\n for (let child = node.firstChild; child !== null; child = child.nextSibling) {\n visit(child, depth + 1);\n }\n };\n\n visit(root, 0);\n return lines.join('\\n');\n}\n","import {\n CharacterCountTextMeasurer,\n formatExplanation,\n UiManualFrameClock,\n type CanvasHost,\n type LayoutBox,\n type LayoutExplanation,\n type TextMeasurer,\n type UiNode,\n type UiSemanticsMap,\n type UiSemanticsRecord\n} from 'gesso-core';\nimport { FakeCanvasHost, RecordingCanvasContext, type RecordedCall } from 'gesso-core/testing';\nimport { GessoRuntime, type FrameMetrics, type FrameworkChild, type GessoRuntimeOptions } from 'gesso-framework';\n\nimport { createFireEvent, type FireEvent } from './fireEvent';\nimport { formatTree } from './debug';\nimport { createQueries, type Queries } from './queries';\nimport { registerRendered } from './registry';\n\n/**\n * The pieces of a mounted tree that are not queries.\n *\n * Split out so the queries can be mixed in without either half having\n * to know the other's shape.\n */\nexport interface RenderedBase {\n /** The runtime itself, for anything this library does not wrap. */\n readonly runtime: GessoRuntime;\n /** The clock frames are driven from; `frame()` is the usual way in. */\n readonly clock: UiManualFrameClock;\n /** Metrics for every frame that has run, in order. */\n readonly frames: readonly FrameMetrics[];\n /** Every canvas call since the last `clearDraws()`. An escape hatch. */\n readonly draws: readonly RecordedCall[];\n /** The event senders, bound to this runtime. */\n readonly fireEvent: FireEvent;\n\n /**\n * Runs the pending frame, if there is one.\n *\n * Without a time it advances 16 ms per call, so a test can drive\n * frames without keeping a clock of its own.\n */\n frame(time?: number): void;\n\n /**\n * Runs frames until nothing is pending, then resolves.\n *\n * Awaits the microtask queue between frames, which is what makes it\n * the right thing after anything asynchronous: a channel patch, an\n * image resolving, a promise a component awaited. Throws rather than\n * spinning forever if the tree never goes quiet.\n */\n settle(options?: { maxFrames?: number }): Promise<void>;\n\n /** The node's border box in layout-root coordinates, before scrolling. */\n getLayout(node: UiNode): LayoutBox;\n /**\n * Where the node is *seen*: scroll offsets and sticky shifts applied.\n *\n * `getLayout` answers where a node was laid out, which for anything\n * inside a scroll container is not where it is. Nothing about\n * scrolling or sticky could be asserted without this — a sticky\n * header's laid-out box is at the top of the content whether or not\n * sticky works at all, so a spec written against `getLayout` passes\n * for a header that scrolled away.\n */\n getVisibleBox(node: UiNode): LayoutBox;\n /** Why the node has the size it has, as `LayoutEngine.explain` gives it. */\n explain(node: UiNode): LayoutExplanation;\n /** The same explanation as the sentences `formatExplanation` prints. */\n explainText(node: UiNode): string;\n\n /**\n * The semantics record for a node: what a screen reader would\n * announce for it. Throws, with the tree, when the node has none —\n * which is nearly always the finding rather than an inconvenience.\n */\n getSemantics(node: UiNode): UiSemanticsRecord;\n /** The same, returning null instead of throwing. */\n querySemantics(node: UiNode): UiSemanticsRecord | null;\n /** The whole semantics tree, as the accessibility mirror has it. */\n semanticsTree(): UiSemanticsMap;\n\n /** The tree as text: type, role, name, states and box, one node per line. */\n debug(node?: UiNode): string;\n\n /** Empties the recorded canvas calls. */\n clearDraws(): void;\n /** Stops the runtime and releases what it holds. */\n unmount(): void;\n}\n\nexport type Rendered = RenderedBase & Queries;\n\nexport interface RenderTestOptions extends Omit<Partial<GessoRuntimeOptions>, 'root' | 'canvas' | 'clock'> {\n /**\n * Whether to run the first frame before returning. Default true.\n *\n * Turn it off to watch the first frame happen — a listener attached\n * in `onCreate` sees it either way, but a spec asserting on what the\n * first frame *did* needs to be holding the result when it runs.\n */\n autoFrame?: boolean;\n /** Runs after the runtime is built and before it starts. */\n onCreate?: (runtime: GessoRuntime) => void;\n /**\n * Let a frame that throws be abandoned, as it is in an application,\n * rather than failing the test with the error. Default false.\n */\n allowFrameErrors?: boolean;\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 600;\nconst DEFAULT_MAX_FRAMES = 100;\n\n/**\n * Mounts a component or element tree with no browser and no DOM, and\n * hands back the queries, the events and the layout answers.\n *\n * ```ts\n * const ui = renderTest(createComponent(Checkbox, { label: 'Wrap lines' }));\n * ui.fireEvent.click(ui.getByRole('checkbox'));\n * ui.frame();\n * expect(ui.getSemantics(ui.getByRole('checkbox')).states).toEqual(['checked']);\n * ```\n *\n * Three things are decided here rather than by the caller, because\n * getting any of them wrong makes a test that passes for the wrong\n * reason:\n *\n * - **The clock is manual.** Frames happen when the test says so, so\n * an assertion never races a scheduler.\n * - **Text is measured by `CharacterCountTextMeasurer`**, not by the\n * canvas double. A double's `measureText` answers the same width\n * whatever the font size, so a heading and its caption would come\n * out the same size; the deterministic measurer is proportional to\n * the font size and identical on every machine.\n * - **A first frame has run** by the time this returns, so the graph\n * is built, laid out and described before the first query.\n */\nexport function renderTest(root: FrameworkChild, options: RenderTestOptions = {}): Rendered {\n const { autoFrame = true, onCreate, allowFrameErrors = false, ...runtimeOptions } = options;\n const context = new RecordingCanvasContext();\n const canvas: CanvasHost = new FakeCanvasHost(context);\n const width = runtimeOptions.width ?? DEFAULT_WIDTH;\n const height = runtimeOptions.height ?? DEFAULT_HEIGHT;\n canvas.width = width;\n canvas.height = height;\n\n const measurer: TextMeasurer = runtimeOptions.textMeasurer ?? new CharacterCountTextMeasurer();\n const frames: FrameMetrics[] = [];\n let clock!: UiManualFrameClock;\n const runtime = new GessoRuntime({\n ...runtimeOptions,\n width,\n height,\n root,\n canvas,\n textMeasurer: measurer,\n clock: callback => (clock = new UiManualFrameClock(callback))\n });\n runtime.onFrame(metrics => frames.push(metrics));\n // An application abandons a frame that throws and keeps drawing, which\n // in a test is a pass on a frame that never ran. The error is thrown\n // from the `frame()` that ran it instead.\n let thrown: string | null = null;\n if (!allowFrameErrors) {\n runtime.onFrameError((message, stack) => {\n thrown ??= stack ?? message;\n });\n }\n onCreate?.(runtime);\n runtime.start();\n\n let now = 0;\n const frame = (time?: number): void => {\n now = time ?? now + 16;\n if (clock.isPending) {\n clock.tick(now);\n }\n if (thrown !== null) {\n const error = thrown;\n thrown = null;\n throw new Error(`A frame threw and was abandoned:\\n${error}`);\n }\n };\n\n const settle = async (settleOptions: { maxFrames?: number } = {}): Promise<void> => {\n const max = settleOptions.maxFrames ?? DEFAULT_MAX_FRAMES;\n for (let index = 0; index <= max; index++) {\n // A macrotask, so a resolved fetch/decode chain gets to run too,\n // not just the microtasks a queued promise leaves behind.\n await new Promise(resolve => setTimeout(resolve, 0));\n if (!clock.isPending) {\n return;\n }\n frame();\n }\n throw new Error(\n `settle() ran ${max} frames and the tree was still asking for another. ` +\n `Something is scheduling a frame every frame; drive it with frame() instead.`\n );\n };\n\n const base: RenderedBase = {\n runtime,\n get clock() {\n return clock;\n },\n frames,\n get draws() {\n return context.calls;\n },\n fireEvent: createFireEvent(runtime),\n frame,\n settle,\n getLayout: node => runtime.debugLayoutBox(node),\n getVisibleBox: node => runtime.debugVisibleBox(node),\n explain: node => runtime.explain(node),\n explainText: node => formatExplanation(runtime.explain(node)),\n getSemantics: node => {\n const record = runtime.semanticsTree().get(node.id);\n if (record === undefined) {\n throw new Error(\n `'${node.id}' is not in the semantics tree: nothing gives it a role or a label, ` +\n `so no assistive technology can see it.\\n\\n${formatTree(runtime, runtime.layoutRoot())}`\n );\n }\n return record;\n },\n querySemantics: node => runtime.semanticsTree().get(node.id) ?? null,\n semanticsTree: () => runtime.semanticsTree(),\n debug: node => formatTree(runtime, node ?? runtime.layoutRoot()),\n clearDraws: () => {\n context.calls.length = 0;\n },\n unmount: () => runtime.dispose()\n };\n\n const rendered: Rendered = { ...base, ...createQueries(base) };\n registerRendered(runtime.layoutRoot(), rendered);\n if (autoFrame) {\n frame(0);\n }\n return rendered;\n}\n","import {\n createChannelRegistry,\n type ChannelRegistry,\n type ChannelReplica,\n type ChannelToken,\n type ServedChannel\n} from 'gesso-framework';\n\n/** What `serveForTest` hands back: the replicas a screen would bind to, and a way to wait for patches. */\nexport interface ServedForTest {\n /** The replica of one served channel, as `ctx.channel(token)` would return it. */\n get<V extends object, C extends object>(token: ChannelToken<V, C>): ChannelReplica<V, C>;\n /**\n * The registry, for `renderTest(component, { channels: served.registry })`.\n *\n * Without it there is no supported way to render a component against\n * served channels: `serveForTest` answers \"did the wiring work\" and\n * `renderTest` answers \"what does it look like\", and a screen driven\n * by a worker is exactly the case that needs both at once.\n */\n readonly registry: ChannelRegistry;\n /**\n * Waits for the patch stream to deliver. With a condition, until it\n * holds; without one, until two turns of the event loop have passed\n * with the ports drained, which is enough for a command's effect to\n * come back as a patch.\n */\n settle(until?: () => boolean, timeoutMs?: number): Promise<void>;\n /** Every error a served channel reported, in order. */\n readonly errors: readonly string[];\n dispose(): void;\n}\n\n/**\n * Serves channels the way an application worker does, over a real patch\n * stream, with no worker: the same `serveChannels` data goes in, and the\n * replicas a screen would read come out.\n *\n * This is the spec for a worker's *wiring*, which the domain classes'\n * own specs do not cover: that a command reaches its handler, that its\n * effect comes back as a patch to the right key, that a view key is\n * plain data. The domain classes stay as testable as they were; this\n * tests the lines between them and the barrier.\n *\n * const served = serveForTest(transitionsChannels(catalogue, queue));\n * const view = served.get(Queue);\n * view.send.play({ playlistId: '1' });\n * await served.settle(() => view.view.current.value !== null);\n */\nexport function serveForTest(channels: readonly ServedChannel[]): ServedForTest {\n const errors: string[] = [];\n const handle = createChannelRegistry(\n channels.map(served => ({ token: served.token, source: served.source })),\n (_name, message) => errors.push(message)\n );\n return {\n errors,\n registry: handle.registry,\n get: token => handle.registry.get(token),\n async settle(until, timeoutMs = 2000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n if (until === undefined) {\n for (let turn = 0; turn < 2; turn++) {\n await new Promise(resolve => setTimeout(resolve, 0));\n }\n return;\n }\n while (!until()) {\n if (Date.now() > deadline) {\n throw new Error(`serveForTest: timed out after ${timeoutMs} ms waiting for the condition to hold.`);\n }\n await new Promise(resolve => setTimeout(resolve, 5));\n }\n },\n dispose: () => handle.dispose()\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gesso-testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Mount a component without a browser, query it the way a screen reader would, and read back why a box is the size it is.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kevin Baker",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"LICENSE"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"gesso-core": "^0.
|
|
53
|
-
"gesso-framework": "^0.
|
|
52
|
+
"gesso-core": "^0.5.0",
|
|
53
|
+
"gesso-framework": "^0.5.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"rxjs": "^7.8.2",
|