@sigx/lynx-testing 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/test-node.ts","../src/test-renderer.ts","../src/queries.ts","../src/render.ts","../src/fire-event.ts","../src/flush.ts"],"sourcesContent":["/**\n * TestNode — lightweight in-memory tree node for test rendering.\n * Replaces ShadowElement + Lynx PAPI for testing purposes.\n */\nexport class TestNode {\n type: string;\n props: Record<string, unknown> = {};\n children: TestNode[] = [];\n parent: TestNode | null = null;\n text?: string;\n\n /** Event handlers keyed by prop name (e.g. 'bindtap', 'bindtouchstart'). */\n _handlers: Map<string, Function> = new Map();\n\n /** Style object (last value from patchProp 'style'). */\n _style: Record<string, unknown> = {};\n\n /** Class string. */\n _class = '';\n\n constructor(type: string) {\n this.type = type;\n }\n\n // -- Tree queries --\n\n /** Find first descendant matching element type. */\n findByType(type: string): TestNode | null {\n for (const child of this.children) {\n if (child.type === type) return child;\n const found = child.findByType(type);\n if (found) return found;\n }\n return null;\n }\n\n /** Find all descendants matching element type. */\n findAllByType(type: string): TestNode[] {\n const results: TestNode[] = [];\n for (const child of this.children) {\n if (child.type === type) results.push(child);\n results.push(...child.findAllByType(type));\n }\n return results;\n }\n\n /** Find first descendant containing the given text. */\n findByText(text: string): TestNode | null {\n if (this.text !== undefined && String(this.text).includes(text)) return this;\n for (const child of this.children) {\n const found = child.findByText(text);\n if (found) return found;\n }\n return null;\n }\n\n /** Get all text content from this node and descendants. */\n textContent(): string {\n if (this.text !== undefined) return String(this.text);\n return this.children.map(c => c.textContent()).join('');\n }\n\n /** Debug: serialize tree to a readable string. */\n toDebugString(indent = 0): string {\n const pad = ' '.repeat(indent);\n if (this.type === '#text') return `${pad}${JSON.stringify(this.text)}`;\n if (this.type === '#comment') return `${pad}<!-- -->`;\n const attrs = Object.keys(this.props).length > 0\n ? ' ' + Object.entries(this.props).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(' ')\n : '';\n if (this.children.length === 0) return `${pad}<${this.type}${attrs} />`;\n const childStr = this.children.map(c => c.toDebugString(indent + 1)).join('\\n');\n return `${pad}<${this.type}${attrs}>\\n${childStr}\\n${pad}</${this.type}>`;\n }\n}\n","/**\n * Test renderer — implements RendererOptions<TestNode, TestNode> for\n * in-memory rendering without Lynx PAPI.\n */\nimport { createRenderer } from '@sigx/runtime-core/internals';\nimport type { RendererOptions } from '@sigx/runtime-core/internals';\nimport { TestNode } from './test-node.js';\n\nconst nodeOps: RendererOptions<TestNode, TestNode> = {\n createElement(type: string): TestNode {\n return new TestNode(type);\n },\n\n createText(text: string): TestNode {\n const node = new TestNode('#text');\n node.text = text;\n return node;\n },\n\n createComment(_text: string): TestNode {\n return new TestNode('#comment');\n },\n\n setText(node: TestNode, text: string): void {\n node.text = text;\n },\n\n setElementText(el: TestNode, text: string): void {\n el.children = [];\n const textNode = new TestNode('#text');\n textNode.text = text;\n textNode.parent = el;\n el.children.push(textNode);\n },\n\n insert(child: TestNode, parent: TestNode, anchor?: TestNode | null): void {\n // Remove from old parent\n if (child.parent) {\n const idx = child.parent.children.indexOf(child);\n if (idx !== -1) child.parent.children.splice(idx, 1);\n }\n child.parent = parent;\n\n if (anchor) {\n const anchorIdx = parent.children.indexOf(anchor);\n if (anchorIdx !== -1) {\n parent.children.splice(anchorIdx, 0, child);\n return;\n }\n }\n parent.children.push(child);\n },\n\n remove(child: TestNode): void {\n if (child.parent) {\n const idx = child.parent.children.indexOf(child);\n if (idx !== -1) child.parent.children.splice(idx, 1);\n child.parent = null;\n }\n },\n\n patchProp(\n el: TestNode,\n key: string,\n _prevValue: unknown,\n nextValue: unknown,\n ): void {\n if (key === 'style') {\n el._style = (nextValue as Record<string, unknown>) ?? {};\n el.props[key] = nextValue;\n } else if (key === 'class') {\n el._class = (nextValue as string) ?? '';\n el.props[key] = nextValue;\n } else if (\n key.startsWith('bind') ||\n key.startsWith('catch') ||\n key.startsWith('on') ||\n key.startsWith('main-thread-bind') ||\n key.startsWith('main-thread-catch') ||\n key.startsWith('global-')\n ) {\n // Event handler\n if (typeof nextValue === 'function') {\n el._handlers.set(key, nextValue as Function);\n } else {\n el._handlers.delete(key);\n }\n el.props[key] = nextValue;\n } else {\n if (nextValue != null) {\n el.props[key] = nextValue;\n } else {\n delete el.props[key];\n }\n }\n },\n\n parentNode(node: TestNode): TestNode | null {\n return node.parent;\n },\n\n nextSibling(node: TestNode): TestNode | null {\n if (!node.parent) return null;\n const idx = node.parent.children.indexOf(node);\n return node.parent.children[idx + 1] ?? null;\n },\n\n cloneNode(node: TestNode): TestNode {\n return new TestNode(node.type);\n },\n};\n\nexport const testRenderer = createRenderer<TestNode, TestNode>(nodeOps);\nexport { nodeOps as testNodeOps };\n","/**\n * Query helpers for finding nodes in a TestNode tree.\n */\nimport { TestNode } from './test-node.js';\n\nexport function getByType(container: TestNode, type: string): TestNode {\n const node = container.findByType(type);\n if (!node) throw new Error(`No element found with type \"${type}\"`);\n return node;\n}\n\nexport function getAllByType(container: TestNode, type: string): TestNode[] {\n return container.findAllByType(type);\n}\n\nexport function getByText(container: TestNode, text: string): TestNode {\n const node = container.findByText(text);\n if (!node) throw new Error(`No element found with text \"${text}\"`);\n return node;\n}\n\nexport function queryByType(container: TestNode, type: string): TestNode | null {\n return container.findByType(type);\n}\n\nexport function queryByText(container: TestNode, text: string): TestNode | null {\n return container.findByText(text);\n}\n\nexport function getByProp(\n container: TestNode,\n key: string,\n value: unknown,\n): TestNode {\n function find(node: TestNode): TestNode | null {\n if (node.props[key] === value) return node;\n for (const child of node.children) {\n const found = find(child);\n if (found) return found;\n }\n return null;\n }\n const node = find(container);\n if (!node) throw new Error(`No element found with ${key}=\"${value}\"`);\n return node;\n}\n","/**\n * render() — mount a sigx Lynx component into a TestNode tree for testing.\n */\nimport type { JSXElement, AppContext } from '@sigx/lynx';\nimport { testRenderer } from './test-renderer.js';\nimport { TestNode } from './test-node.js';\nimport * as queries from './queries.js';\n\nexport interface RenderResult {\n /** The root container node. */\n container: TestNode;\n /** Unmount the component and clean up. */\n unmount: () => void;\n /** Find first node by element type (throws if not found). */\n getByType: (type: string) => TestNode;\n /** Find all nodes by element type. */\n getAllByType: (type: string) => TestNode[];\n /** Find first node containing text (throws if not found). */\n getByText: (text: string) => TestNode;\n /** Find first node by element type (returns null if not found). */\n queryByType: (type: string) => TestNode | null;\n /** Find first node containing text (returns null if not found). */\n queryByText: (text: string) => TestNode | null;\n /** Find first node by prop key/value (throws if not found). */\n getByProp: (key: string, value: unknown) => TestNode;\n /** Debug print the tree. */\n debug: () => string;\n}\n\n/**\n * Render a JSX element into an in-memory TestNode tree.\n *\n * @example\n * ```tsx\n * const { getByText, container } = render(<MyComponent name=\"World\" />);\n * expect(getByText('Hello World')).toBeTruthy();\n * ```\n */\nexport function render(\n element: JSXElement,\n options?: { appContext?: AppContext },\n): RenderResult {\n const container = new TestNode('root');\n\n testRenderer.render(element, container, options?.appContext ?? undefined);\n\n const unmount = () => {\n testRenderer.render(null, container);\n };\n\n return {\n container,\n unmount,\n getByType: (type) => queries.getByType(container, type),\n getAllByType: (type) => queries.getAllByType(container, type),\n getByText: (text) => queries.getByText(container, text),\n queryByType: (type) => queries.queryByType(container, type),\n queryByText: (text) => queries.queryByText(container, text),\n getByProp: (key, value) => queries.getByProp(container, key, value),\n debug: () => container.toDebugString(),\n };\n}\n","/**\n * Fire synthetic events on TestNode elements.\n */\nimport { TestNode } from './test-node.js';\n\ninterface SyntheticTouch {\n identifier?: number;\n x?: number;\n y?: number;\n pageX?: number;\n pageY?: number;\n clientX?: number;\n clientY?: number;\n}\n\ninterface SyntheticTouchEvent {\n touches?: SyntheticTouch[];\n changedTouches?: SyntheticTouch[];\n}\n\ninterface SyntheticScrollEvent {\n detail?: {\n scrollTop?: number;\n scrollLeft?: number;\n scrollHeight?: number;\n scrollWidth?: number;\n deltaX?: number;\n deltaY?: number;\n };\n}\n\ninterface SyntheticInputEvent {\n detail?: {\n value?: string;\n };\n}\n\n/** Create a normalized touch object with defaults. */\nexport function touch(\n pageX: number,\n pageY: number,\n identifier = 1,\n): SyntheticTouch {\n return { identifier, x: pageX, y: pageY, pageX, pageY, clientX: pageX, clientY: pageY };\n}\n\nfunction dispatchHandler(node: TestNode, handlerKey: string, event: unknown): void {\n const handler = node._handlers.get(handlerKey);\n if (handler) handler(event);\n}\n\nfunction normalizeTouchEvent(data?: SyntheticTouchEvent): object {\n return {\n type: 'touch',\n timestamp: Date.now(),\n touches: data?.touches ?? [],\n changedTouches: data?.changedTouches ?? data?.touches ?? [],\n target: { id: '', dataset: {}, uid: 0 },\n currentTarget: { id: '', dataset: {}, uid: 0 },\n detail: {},\n };\n}\n\nexport const fireEvent = {\n /** Fire bindtap / onTap event. */\n tap(node: TestNode, data?: { x?: number; y?: number }): void {\n const x = data?.x ?? 0;\n const y = data?.y ?? 0;\n const event = {\n type: 'tap',\n timestamp: Date.now(),\n target: { id: '', dataset: {}, uid: 0 },\n currentTarget: { id: '', dataset: {}, uid: 0 },\n detail: { x, y },\n touches: [touch(x, y)],\n changedTouches: [touch(x, y)],\n };\n dispatchHandler(node, 'bindtap', event);\n dispatchHandler(node, 'onTap', event);\n },\n\n /** Fire bindtouchstart event. */\n touchStart(node: TestNode, data?: SyntheticTouchEvent): void {\n dispatchHandler(node, 'bindtouchstart', normalizeTouchEvent(data));\n },\n\n /** Fire bindtouchmove event. */\n touchMove(node: TestNode, data?: SyntheticTouchEvent): void {\n dispatchHandler(node, 'bindtouchmove', normalizeTouchEvent(data));\n },\n\n /** Fire bindtouchend event. */\n touchEnd(node: TestNode, data?: SyntheticTouchEvent): void {\n dispatchHandler(node, 'bindtouchend', normalizeTouchEvent(data));\n },\n\n /** Fire bindtouchcancel event. */\n touchCancel(node: TestNode, data?: SyntheticTouchEvent): void {\n dispatchHandler(node, 'bindtouchcancel', normalizeTouchEvent(data));\n },\n\n /** Fire bindscroll event. */\n scroll(node: TestNode, data?: SyntheticScrollEvent): void {\n const event = {\n type: 'scroll',\n timestamp: Date.now(),\n target: { id: '', dataset: {}, uid: 0 },\n currentTarget: { id: '', dataset: {}, uid: 0 },\n detail: {\n scrollTop: 0,\n scrollLeft: 0,\n scrollHeight: 0,\n scrollWidth: 0,\n deltaX: 0,\n deltaY: 0,\n ...data?.detail,\n },\n };\n dispatchHandler(node, 'bindscroll', event);\n dispatchHandler(node, 'onScroll', event);\n },\n\n /** Fire bindinput event. */\n input(node: TestNode, data?: SyntheticInputEvent): void {\n const event = {\n type: 'input',\n timestamp: Date.now(),\n target: { id: '', dataset: {}, uid: 0 },\n currentTarget: { id: '', dataset: {}, uid: 0 },\n detail: { value: '', ...data?.detail },\n };\n dispatchHandler(node, 'bindinput', event);\n dispatchHandler(node, 'onInput', event);\n },\n\n /** Fire bindlongpress event. */\n longPress(node: TestNode): void {\n const event = {\n type: 'longpress',\n timestamp: Date.now(),\n target: { id: '', dataset: {}, uid: 0 },\n currentTarget: { id: '', dataset: {}, uid: 0 },\n detail: {},\n };\n dispatchHandler(node, 'bindlongpress', event);\n dispatchHandler(node, 'onLongpress', event);\n },\n};\n","/**\n * Utilities for waiting on reactive updates in tests.\n */\n\n/**\n * Wait for all pending reactive effects and microtasks to complete.\n * Use after mutating signal state to ensure the rendered tree is updated.\n *\n * @example\n * ```ts\n * state.count = 5;\n * await waitForUpdate();\n * expect(getByText(container, '5')).toBeTruthy();\n * ```\n */\nexport function waitForUpdate(): Promise<void> {\n return new Promise<void>((resolve) => {\n // Microtask (Promise.resolve) runs reactive effects,\n // then setTimeout(0) ensures any scheduled flushes complete.\n Promise.resolve()\n .then(() => new Promise<void>(r => setTimeout(r, 0)))\n .then(resolve);\n });\n}\n\n/**\n * Run a callback and wait for reactive effects to flush.\n *\n * @example\n * ```ts\n * await act(() => {\n * state.count++;\n * state.name = 'Alice';\n * });\n * // Tree is now updated\n * ```\n */\nexport async function act(fn: () => void | Promise<void>): Promise<void> {\n await fn();\n await waitForUpdate();\n}\n"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Public JS API for the main-thread (MT) gesture/worklet test harness.
3
+ *
4
+ * Pair with `@sigx/lynx-testing/mt/setup` in your vitest setupFiles. See
5
+ * the package README for a full example.
6
+ *
7
+ * The harness lets you write tests that:
8
+ * 1. Compile a `.tsx` source file with `transformReactLynxSync`
9
+ * (target: `LEPUS`) to extract the `registerWorkletInternal(...)`
10
+ * calls the SWC transform emits for `'main thread'` worklets.
11
+ * 2. Eval those registrations into the upstream worklet runtime that
12
+ * `setup.ts` boots, populating `lynxWorkletImpl._workletMap`.
13
+ * 3. Call each worklet directly with a fabricated `_c` capture and a
14
+ * synthetic gesture event, asserting the worklet's MT-side state
15
+ * mutations and any `setStyleProperties` / `runOnBackground` calls.
16
+ *
17
+ * Use `compileMTWorklets()` for the common case (one source file, get
18
+ * the registered worklets back). Drop down to `extractRegistrations()`
19
+ * if you need finer control.
20
+ */
21
+ /**
22
+ * Synthetic `MainThreadRef` shape — `{ current, _wvid }`. Worklets read
23
+ * `ref.current.value` and may mutate `ref.current.value`.
24
+ */
25
+ export declare function makeRef<T>(current: T, id?: number): {
26
+ current: T;
27
+ _wvid: number;
28
+ };
29
+ /**
30
+ * Fabricate a Lynx pan-gesture event payload as the iOS arena would
31
+ * deliver it. The payload is nested under `e.params` (NOT top-level on
32
+ * `e`) — your worklet should read pageX/pageY from `e.params.pageX`,
33
+ * mirroring real device behaviour.
34
+ *
35
+ * Verified against `LynxBaseGestureHandler.m::eventParamsFromTouchEvent`
36
+ * + `LynxPanGestureHandler.m::eventParamsInActive` on iOS Lynx 3.5.
37
+ */
38
+ export declare function fabricatePanEvent(opts: {
39
+ pageX: number;
40
+ pageY?: number;
41
+ }): MTGestureEvent;
42
+ /**
43
+ * Fabricate a Lynx tap-gesture event payload. Same shape as the pan event
44
+ * minus the scroll-related fields the pan handler adds; tests rarely read
45
+ * either, so the difference is mostly cosmetic.
46
+ */
47
+ export declare function fabricateTapEvent(opts?: {
48
+ pageX?: number;
49
+ pageY?: number;
50
+ }): MTGestureEvent;
51
+ /**
52
+ * Common shape for the events the iOS gesture arena delivers to MT
53
+ * worklets. Top-level keys are dispatch metadata; the actual touch data
54
+ * lives under `params` (and a duplicate `detail`).
55
+ */
56
+ export interface MTGestureEvent {
57
+ type: string;
58
+ timestamp: number;
59
+ currentTarget: {
60
+ element: null;
61
+ };
62
+ target: {
63
+ element: null;
64
+ };
65
+ params: Record<string, unknown>;
66
+ detail: Record<string, unknown>;
67
+ }
68
+ /**
69
+ * Extract `registerWorkletInternal(...)` calls from a LEPUS-target
70
+ * transform output. Bracket-depth counting handles nested braces in the
71
+ * function body. Mirror of the lynx-plugin internal so tests don't pull
72
+ * a build-time package in as a runtime dep.
73
+ */
74
+ export declare function extractRegistrations(lepusCode: string): string;
75
+ /**
76
+ * Get the live `lynxWorkletImpl._workletMap` populated by the upstream
77
+ * worklet runtime that `mt/setup.ts` bootstrapped. Each entry is a
78
+ * `_wkltId` → callable mapping. After `compileMTWorklets()` evals new
79
+ * registrations, this map will include them.
80
+ *
81
+ * Throws if `mt/setup.ts` didn't run — typically because the consumer
82
+ * forgot to add it to `setupFiles`.
83
+ */
84
+ export declare function getWorkletMap(): Record<string, Function>;
85
+ /**
86
+ * Compile a `.tsx` source file as a LEPUS worklet bundle, eval the
87
+ * resulting `registerWorkletInternal(...)` calls into the live runtime,
88
+ * and return the worklets that were just registered (in source order).
89
+ *
90
+ * The returned array indexes into `lynxWorkletImpl._workletMap`'s newest
91
+ * entries — i.e. `result[0]` is the first worklet registered by this
92
+ * compile. For most tests this is enough; for cross-test sharing or
93
+ * per-`_wkltId` access, fall back to `getWorkletMap()`.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * import { readFileSync } from 'fs';
98
+ * import { compileMTWorklets, fabricatePanEvent, makeRef } from '@sigx/lynx-testing/mt';
99
+ *
100
+ * const SRC = path.resolve(__dirname, '../../src/components/Draggable.tsx');
101
+ * const worklets = compileMTWorklets({
102
+ * filename: SRC,
103
+ * source: readFileSync(SRC, 'utf8'),
104
+ * });
105
+ * // Source-order: onBegin (:1), onStart (:2), onUpdate (:3), onEnd (:4)
106
+ * const onUpdate = worklets[2]!;
107
+ *
108
+ * const drag = { startPageX: 100, startPageY: 50, ... };
109
+ * onUpdate.call({ _c: { drag: makeRef(drag, 1), ... } }, fabricatePanEvent({ pageX: 130, pageY: 55 }));
110
+ * expect(drag.startPageX).toBe(100);
111
+ * ```
112
+ */
113
+ export declare function compileMTWorklets(opts: {
114
+ filename: string;
115
+ source: string;
116
+ /**
117
+ * Override the runtime package name passed to the SWC transform.
118
+ * Defaults to `@sigx/lynx-runtime-main`, which matches what the
119
+ * production build uses.
120
+ */
121
+ runtimePkg?: string;
122
+ }): Function[];
123
+ /**
124
+ * Read the JS-context spy that `mt/setup.ts` installed on the lynx mock.
125
+ * Useful for asserting `runOnBackground` / `Lynx.Sigx.AvPublish` event
126
+ * dispatches from within a worklet.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * import { getJsContext } from '@sigx/lynx-testing/mt';
131
+ *
132
+ * onUpdate.call(ctx, fabricatePanEvent({ pageX: 130 }));
133
+ * const ctx = getJsContext();
134
+ * expect(ctx.dispatchEvent).toHaveBeenCalledWith(
135
+ * expect.objectContaining({ type: 'Lynx.Sigx.AvPublish' })
136
+ * );
137
+ * ```
138
+ */
139
+ export declare function getJsContext(): {
140
+ addEventListener: Function;
141
+ dispatchEvent: Function;
142
+ };
143
+ /**
144
+ * Wipe the JS-context spy between tests so dispatchEvent / addEventListener
145
+ * call counts don't bleed across cases.
146
+ */
147
+ export declare function resetJsContextSpy(): void;
148
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mt/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,SAAI,GAAG;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAuBzF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,cAAc,CAmB/F;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC;IACjC,MAAM,EAAE;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA2B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAaxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,QAAQ,EAAE,CAuDb;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,IAAI;IAAE,gBAAgB,EAAE,QAAQ,CAAC;IAAC,aAAa,EAAE,QAAQ,CAAA;CAAE,CAEtF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -0,0 +1,114 @@
1
+ import { n as e, t } from "../setup-BUzWSuVx.js";
2
+ import { transformReactLynxSync as n } from "@lynx-js/react/transform";
3
+ //#region src/mt/index.ts
4
+ function r(e, t = 1) {
5
+ return {
6
+ current: e,
7
+ _wvid: t
8
+ };
9
+ }
10
+ function i(e) {
11
+ let t = {
12
+ timestamp: Date.now(),
13
+ type: "touchmove",
14
+ x: e.pageX,
15
+ y: e.pageY ?? 0,
16
+ pageX: e.pageX,
17
+ pageY: e.pageY ?? 0,
18
+ clientX: e.pageX,
19
+ clientY: e.pageY ?? 0,
20
+ scrollX: 0,
21
+ scrollY: 0,
22
+ isAtStart: 0,
23
+ isAtEnd: 0
24
+ };
25
+ return {
26
+ type: "onUpdate",
27
+ timestamp: Date.now(),
28
+ currentTarget: { element: null },
29
+ target: { element: null },
30
+ params: t,
31
+ detail: t
32
+ };
33
+ }
34
+ function a(e = {}) {
35
+ let t = {
36
+ timestamp: Date.now(),
37
+ type: "touchend",
38
+ x: e.pageX ?? 0,
39
+ y: e.pageY ?? 0,
40
+ pageX: e.pageX ?? 0,
41
+ pageY: e.pageY ?? 0,
42
+ clientX: e.pageX ?? 0,
43
+ clientY: e.pageY ?? 0
44
+ };
45
+ return {
46
+ type: "onStart",
47
+ timestamp: Date.now(),
48
+ currentTarget: { element: null },
49
+ target: { element: null },
50
+ params: t,
51
+ detail: t
52
+ };
53
+ }
54
+ function o(e) {
55
+ let t = [], n = 0;
56
+ for (;;) {
57
+ let r = e.indexOf("registerWorkletInternal(", n);
58
+ if (r === -1) break;
59
+ let i = 0, a = r + 24 - 1;
60
+ for (; a < e.length; a++) {
61
+ let t = e[a];
62
+ if (t === "(") i++;
63
+ else if (t === ")" && (i--, i === 0)) break;
64
+ }
65
+ let o = a + 1;
66
+ o < e.length && e[o] === ";" && o++, t.push(e.slice(r, o)), n = o;
67
+ }
68
+ return t.join("\n");
69
+ }
70
+ function s() {
71
+ let e = globalThis.lynxWorkletImpl;
72
+ if (!e) throw Error("[lynx-testing/mt] lynxWorkletImpl is not initialized — add `@sigx/lynx-testing/mt/setup` to your vitest config's `setupFiles` array.");
73
+ return e._workletMap;
74
+ }
75
+ function c(e) {
76
+ let { filename: t, source: r, runtimePkg: i = "@sigx/lynx-runtime-main" } = e, a = n(r, {
77
+ pluginName: "sigx:test",
78
+ filename: t,
79
+ sourcemap: !1,
80
+ cssScope: !1,
81
+ shake: !1,
82
+ compat: !1,
83
+ refresh: !1,
84
+ defineDCE: !1,
85
+ directiveDCE: !1,
86
+ snapshot: !1,
87
+ worklet: {
88
+ target: "LEPUS",
89
+ filename: t,
90
+ runtimePkg: i
91
+ }
92
+ });
93
+ if (a.errors && a.errors.length > 0) throw Error("[lynx-testing/mt] LEPUS transform errors for " + t + ":\n" + a.errors.map((e) => " - " + (e.text ?? "<unknown>")).join("\n"));
94
+ let c = o(a.code);
95
+ Function(c)();
96
+ let l = /registerWorkletInternal\(\s*"main-thread"\s*,\s*"([^"]+)"/g, u = [], d;
97
+ for (; (d = l.exec(c)) !== null;) u.push(d[1]);
98
+ let f = s();
99
+ return u.map((e) => {
100
+ let t = f[e];
101
+ if (!t) throw Error("[lynx-testing/mt] worklet `" + e + "` was registered by the compile but is missing from lynxWorkletImpl._workletMap. The upstream worklet runtime may not have evaluated correctly.");
102
+ return t;
103
+ });
104
+ }
105
+ function l() {
106
+ return t();
107
+ }
108
+ function u() {
109
+ e();
110
+ }
111
+ //#endregion
112
+ export { c as compileMTWorklets, o as extractRegistrations, i as fabricatePanEvent, a as fabricateTapEvent, l as getJsContext, s as getWorkletMap, r as makeRef, u as resetJsContextSpy };
113
+
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/mt/index.ts"],"sourcesContent":["/**\n * Public JS API for the main-thread (MT) gesture/worklet test harness.\n *\n * Pair with `@sigx/lynx-testing/mt/setup` in your vitest setupFiles. See\n * the package README for a full example.\n *\n * The harness lets you write tests that:\n * 1. Compile a `.tsx` source file with `transformReactLynxSync`\n * (target: `LEPUS`) to extract the `registerWorkletInternal(...)`\n * calls the SWC transform emits for `'main thread'` worklets.\n * 2. Eval those registrations into the upstream worklet runtime that\n * `setup.ts` boots, populating `lynxWorkletImpl._workletMap`.\n * 3. Call each worklet directly with a fabricated `_c` capture and a\n * synthetic gesture event, asserting the worklet's MT-side state\n * mutations and any `setStyleProperties` / `runOnBackground` calls.\n *\n * Use `compileMTWorklets()` for the common case (one source file, get\n * the registered worklets back). Drop down to `extractRegistrations()`\n * if you need finer control.\n */\n\nimport { transformReactLynxSync } from '@lynx-js/react/transform';\nimport { _getJsContext, _resetJsContextSpy } from './setup.js';\n\n/**\n * Synthetic `MainThreadRef` shape — `{ current, _wvid }`. Worklets read\n * `ref.current.value` and may mutate `ref.current.value`.\n */\nexport function makeRef<T>(current: T, id = 1): { current: T; _wvid: number } {\n return { current, _wvid: id };\n}\n\n/**\n * Fabricate a Lynx pan-gesture event payload as the iOS arena would\n * deliver it. The payload is nested under `e.params` (NOT top-level on\n * `e`) — your worklet should read pageX/pageY from `e.params.pageX`,\n * mirroring real device behaviour.\n *\n * Verified against `LynxBaseGestureHandler.m::eventParamsFromTouchEvent`\n * + `LynxPanGestureHandler.m::eventParamsInActive` on iOS Lynx 3.5.\n */\nexport function fabricatePanEvent(opts: { pageX: number; pageY?: number }): MTGestureEvent {\n const params = {\n timestamp: Date.now(),\n type: 'touchmove',\n x: opts.pageX,\n y: opts.pageY ?? 0,\n pageX: opts.pageX,\n pageY: opts.pageY ?? 0,\n clientX: opts.pageX,\n clientY: opts.pageY ?? 0,\n scrollX: 0,\n scrollY: 0,\n isAtStart: 0,\n isAtEnd: 0,\n };\n return {\n type: 'onUpdate',\n timestamp: Date.now(),\n currentTarget: { element: null },\n target: { element: null },\n params,\n detail: params,\n };\n}\n\n/**\n * Fabricate a Lynx tap-gesture event payload. Same shape as the pan event\n * minus the scroll-related fields the pan handler adds; tests rarely read\n * either, so the difference is mostly cosmetic.\n */\nexport function fabricateTapEvent(opts: { pageX?: number; pageY?: number } = {}): MTGestureEvent {\n const params = {\n timestamp: Date.now(),\n type: 'touchend',\n x: opts.pageX ?? 0,\n y: opts.pageY ?? 0,\n pageX: opts.pageX ?? 0,\n pageY: opts.pageY ?? 0,\n clientX: opts.pageX ?? 0,\n clientY: opts.pageY ?? 0,\n };\n return {\n type: 'onStart',\n timestamp: Date.now(),\n currentTarget: { element: null },\n target: { element: null },\n params,\n detail: params,\n };\n}\n\n/**\n * Common shape for the events the iOS gesture arena delivers to MT\n * worklets. Top-level keys are dispatch metadata; the actual touch data\n * lives under `params` (and a duplicate `detail`).\n */\nexport interface MTGestureEvent {\n type: string;\n timestamp: number;\n currentTarget: { element: null };\n target: { element: null };\n params: Record<string, unknown>;\n detail: Record<string, unknown>;\n}\n\n/**\n * Extract `registerWorkletInternal(...)` calls from a LEPUS-target\n * transform output. Bracket-depth counting handles nested braces in the\n * function body. Mirror of the lynx-plugin internal so tests don't pull\n * a build-time package in as a runtime dep.\n */\nexport function extractRegistrations(lepusCode: string): string {\n const out: string[] = [];\n const marker = 'registerWorkletInternal(';\n let from = 0;\n\n while (true) {\n const idx = lepusCode.indexOf(marker, from);\n if (idx === -1) break;\n\n let depth = 0;\n let i = idx + marker.length - 1;\n for (; i < lepusCode.length; i++) {\n const ch = lepusCode[i];\n if (ch === '(') depth++;\n else if (ch === ')') {\n depth--;\n if (depth === 0) break;\n }\n }\n\n let end = i + 1;\n if (end < lepusCode.length && lepusCode[end] === ';') end++;\n out.push(lepusCode.slice(idx, end));\n from = end;\n }\n\n return out.join('\\n');\n}\n\n/**\n * Get the live `lynxWorkletImpl._workletMap` populated by the upstream\n * worklet runtime that `mt/setup.ts` bootstrapped. Each entry is a\n * `_wkltId` → callable mapping. After `compileMTWorklets()` evals new\n * registrations, this map will include them.\n *\n * Throws if `mt/setup.ts` didn't run — typically because the consumer\n * forgot to add it to `setupFiles`.\n */\nexport function getWorkletMap(): Record<string, Function> {\n interface WorkletImpl {\n _workletMap: Record<string, Function>;\n }\n const impl = (globalThis as { lynxWorkletImpl?: WorkletImpl }).lynxWorkletImpl;\n if (!impl) {\n throw new Error(\n '[lynx-testing/mt] lynxWorkletImpl is not initialized — add ' +\n '`@sigx/lynx-testing/mt/setup` to your vitest config\\'s ' +\n '`setupFiles` array.'\n );\n }\n return impl._workletMap;\n}\n\n/**\n * Compile a `.tsx` source file as a LEPUS worklet bundle, eval the\n * resulting `registerWorkletInternal(...)` calls into the live runtime,\n * and return the worklets that were just registered (in source order).\n *\n * The returned array indexes into `lynxWorkletImpl._workletMap`'s newest\n * entries — i.e. `result[0]` is the first worklet registered by this\n * compile. For most tests this is enough; for cross-test sharing or\n * per-`_wkltId` access, fall back to `getWorkletMap()`.\n *\n * @example\n * ```ts\n * import { readFileSync } from 'fs';\n * import { compileMTWorklets, fabricatePanEvent, makeRef } from '@sigx/lynx-testing/mt';\n *\n * const SRC = path.resolve(__dirname, '../../src/components/Draggable.tsx');\n * const worklets = compileMTWorklets({\n * filename: SRC,\n * source: readFileSync(SRC, 'utf8'),\n * });\n * // Source-order: onBegin (:1), onStart (:2), onUpdate (:3), onEnd (:4)\n * const onUpdate = worklets[2]!;\n *\n * const drag = { startPageX: 100, startPageY: 50, ... };\n * onUpdate.call({ _c: { drag: makeRef(drag, 1), ... } }, fabricatePanEvent({ pageX: 130, pageY: 55 }));\n * expect(drag.startPageX).toBe(100);\n * ```\n */\nexport function compileMTWorklets(opts: {\n filename: string;\n source: string;\n /**\n * Override the runtime package name passed to the SWC transform.\n * Defaults to `@sigx/lynx-runtime-main`, which matches what the\n * production build uses.\n */\n runtimePkg?: string;\n}): Function[] {\n const { filename, source, runtimePkg = '@sigx/lynx-runtime-main' } = opts;\n\n const result = transformReactLynxSync(source, {\n pluginName: 'sigx:test',\n filename,\n sourcemap: false,\n cssScope: false,\n shake: false,\n compat: false,\n refresh: false,\n defineDCE: false,\n directiveDCE: false,\n snapshot: false,\n worklet: { target: 'LEPUS', filename, runtimePkg },\n });\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(\n '[lynx-testing/mt] LEPUS transform errors for ' + filename + ':\\n' +\n result.errors.map((e) => ' - ' + (e.text ?? '<unknown>')).join('\\n')\n );\n }\n\n // Eval the registrations against `globalThis.registerWorkletInternal`\n // (installed by `setup.ts`). SWC produces deterministic `_wkltId`s\n // from the source content hash + index, so re-compiling the same\n // source overwrites the same map entries — we can't diff by\n // map-presence to find what this call registered. Instead parse the\n // IDs directly out of the registration source.\n const registrations = extractRegistrations(result.code);\n new Function(registrations)();\n\n // Each registration looks like:\n // registerWorkletInternal(\"main-thread\", \"<wkltId>\", function(...) {...});\n // Extract the wkltId from the second string literal in source order.\n const idRegex = /registerWorkletInternal\\(\\s*\"main-thread\"\\s*,\\s*\"([^\"]+)\"/g;\n const ids: string[] = [];\n let m: RegExpExecArray | null;\n while ((m = idRegex.exec(registrations)) !== null) {\n ids.push(m[1]!);\n }\n\n const map = getWorkletMap();\n return ids.map(id => {\n const fn = map[id];\n if (!fn) {\n throw new Error(\n '[lynx-testing/mt] worklet `' + id + '` was registered by the ' +\n 'compile but is missing from lynxWorkletImpl._workletMap. The ' +\n 'upstream worklet runtime may not have evaluated correctly.'\n );\n }\n return fn;\n });\n}\n\n/**\n * Read the JS-context spy that `mt/setup.ts` installed on the lynx mock.\n * Useful for asserting `runOnBackground` / `Lynx.Sigx.AvPublish` event\n * dispatches from within a worklet.\n *\n * @example\n * ```ts\n * import { getJsContext } from '@sigx/lynx-testing/mt';\n *\n * onUpdate.call(ctx, fabricatePanEvent({ pageX: 130 }));\n * const ctx = getJsContext();\n * expect(ctx.dispatchEvent).toHaveBeenCalledWith(\n * expect.objectContaining({ type: 'Lynx.Sigx.AvPublish' })\n * );\n * ```\n */\nexport function getJsContext(): { addEventListener: Function; dispatchEvent: Function } {\n return _getJsContext();\n}\n\n/**\n * Wipe the JS-context spy between tests so dispatchEvent / addEventListener\n * call counts don't bleed across cases.\n */\nexport function resetJsContextSpy(): void {\n _resetJsContextSpy();\n}\n"],"mappings":";;;AA4BA,SAAgB,EAAW,GAAY,IAAK,GAAkC;CAC1E,OAAO;EAAE;EAAS,OAAO;EAAI;;AAYjC,SAAgB,EAAkB,GAAyD;CACvF,IAAM,IAAS;EACX,WAAW,KAAK,KAAK;EACrB,MAAM;EACN,GAAG,EAAK;EACR,GAAG,EAAK,SAAS;EACjB,OAAO,EAAK;EACZ,OAAO,EAAK,SAAS;EACrB,SAAS,EAAK;EACd,SAAS,EAAK,SAAS;EACvB,SAAS;EACT,SAAS;EACT,WAAW;EACX,SAAS;EACZ;CACD,OAAO;EACH,MAAM;EACN,WAAW,KAAK,KAAK;EACrB,eAAe,EAAE,SAAS,MAAM;EAChC,QAAQ,EAAE,SAAS,MAAM;EACzB;EACA,QAAQ;EACX;;AAQL,SAAgB,EAAkB,IAA2C,EAAE,EAAkB;CAC7F,IAAM,IAAS;EACX,WAAW,KAAK,KAAK;EACrB,MAAM;EACN,GAAG,EAAK,SAAS;EACjB,GAAG,EAAK,SAAS;EACjB,OAAO,EAAK,SAAS;EACrB,OAAO,EAAK,SAAS;EACrB,SAAS,EAAK,SAAS;EACvB,SAAS,EAAK,SAAS;EAC1B;CACD,OAAO;EACH,MAAM;EACN,WAAW,KAAK,KAAK;EACrB,eAAe,EAAE,SAAS,MAAM;EAChC,QAAQ,EAAE,SAAS,MAAM;EACzB;EACA,QAAQ;EACX;;AAuBL,SAAgB,EAAqB,GAA2B;CAC5D,IAAM,IAAgB,EAAE,EAEpB,IAAO;CAEX,SAAa;EACT,IAAM,IAAM,EAAU,QAAQ,4BAAQ,EAAK;EAC3C,IAAI,MAAQ,IAAI;EAEhB,IAAI,IAAQ,GACR,IAAI,IAAM,KAAgB;EAC9B,OAAO,IAAI,EAAU,QAAQ,KAAK;GAC9B,IAAM,IAAK,EAAU;GACrB,IAAI,MAAO,KAAK;QACX,IAAI,MAAO,QACZ,KACI,MAAU,IAAG;;EAIzB,IAAI,IAAM,IAAI;EAGd,AAFI,IAAM,EAAU,UAAU,EAAU,OAAS,OAAK,KACtD,EAAI,KAAK,EAAU,MAAM,GAAK,EAAI,CAAC,EACnC,IAAO;;CAGX,OAAO,EAAI,KAAK,KAAK;;AAYzB,SAAgB,IAA0C;CAItD,IAAM,IAAQ,WAAiD;CAC/D,IAAI,CAAC,GACD,MAAU,MACN,uIAGH;CAEL,OAAO,EAAK;;AA+BhB,SAAgB,EAAkB,GASnB;CACX,IAAM,EAAE,aAAU,WAAQ,gBAAa,8BAA8B,GAE/D,IAAS,EAAuB,GAAQ;EAC1C,YAAY;EACZ;EACA,WAAW;EACX,UAAU;EACV,OAAO;EACP,QAAQ;EACR,SAAS;EACT,WAAW;EACX,cAAc;EACd,UAAU;EACV,SAAS;GAAE,QAAQ;GAAS;GAAU;GAAY;EACrD,CAAC;CAEF,IAAI,EAAO,UAAU,EAAO,OAAO,SAAS,GACxC,MAAU,MACN,kDAAkD,IAAW,QAC7D,EAAO,OAAO,KAAK,MAAM,UAAU,EAAE,QAAQ,aAAa,CAAC,KAAK,KAAK,CACxE;CASL,IAAM,IAAgB,EAAqB,EAAO,KAAK;CACvD,AAAI,SAAS,EAAc,EAAE;CAK7B,IAAM,IAAU,8DACV,IAAgB,EAAE,EACpB;CACJ,QAAQ,IAAI,EAAQ,KAAK,EAAc,MAAM,OACzC,EAAI,KAAK,EAAE,GAAI;CAGnB,IAAM,IAAM,GAAe;CAC3B,OAAO,EAAI,KAAI,MAAM;EACjB,IAAM,IAAK,EAAI;EACf,IAAI,CAAC,GACD,MAAU,MACN,gCAAgC,IAAK,kJAGxC;EAEL,OAAO;GACT;;AAmBN,SAAgB,IAAwE;CACpF,OAAO,GAAe;;AAO1B,SAAgB,IAA0B;CACtC,GAAoB"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Side-effect bootstrap for the main-thread (MT) gesture/worklet test
3
+ * harness. Consumers list this in their `vitest.mt.config.ts`'s
4
+ * `setupFiles` array; it must run before any test that drives a worklet
5
+ * directly.
6
+ *
7
+ * ```ts
8
+ * // vitest.mt.config.ts
9
+ * import { defineConfig } from 'vitest/config';
10
+ *
11
+ * export default defineConfig({
12
+ * test: {
13
+ * environment: 'happy-dom',
14
+ * globals: true,
15
+ * include: ['__tests__/**\/*.mt.test.ts'],
16
+ * setupFiles: ['@sigx/lynx-testing/mt/setup'],
17
+ * },
18
+ * });
19
+ * ```
20
+ *
21
+ * Order matters and is encoded here:
22
+ * 1. Stub PAPI globals (`__SetAttribute` etc.) + `globalThis.lynx` +
23
+ * `globalThis.SystemInfo`. The worklet-runtime IIFE reads
24
+ * `SystemInfo.lynxSdkVersion` at init and reassigns lynx.setTimeout /
25
+ * lynx.requestAnimationFrame onto globalThis, so both must exist
26
+ * before its IIFE evaluates.
27
+ * 2. Side-effect import `@sigx/lynx-runtime-main` — its entry-main
28
+ * module installs `sigxRunOnMT`, `runOnBackground`, etc. (no PAPI
29
+ * calls fire at module init — only inside the renderPage /
30
+ * sigxPatchUpdate handlers we never invoke from these tests).
31
+ * 3. Side-effect import `@lynx-js/react/worklet-runtime` — IIFE that
32
+ * installs `globalThis.lynxWorkletImpl`, `registerWorkletInternal`,
33
+ * `runWorklet`.
34
+ * 4. Side-effect import `@sigx/lynx-runtime-main/install-hybrid-worklet`
35
+ * — registers the hybrid dispatcher into the now-populated
36
+ * `_workletMap`.
37
+ *
38
+ * The mocks are installed once per worker. `resetJsContextSpy()` from the
39
+ * companion `@sigx/lynx-testing/mt` module wipes the dispatchEvent /
40
+ * addEventListener spies between tests when needed.
41
+ */
42
+ import { vi } from 'vitest';
43
+ interface JSContextMock {
44
+ addEventListener: ReturnType<typeof vi.fn>;
45
+ dispatchEvent: ReturnType<typeof vi.fn>;
46
+ }
47
+ export declare function _getJsContext(): JSContextMock;
48
+ export declare function _resetJsContextSpy(): void;
49
+ export {};
50
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/mt/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B,UAAU,aAAa;IACnB,gBAAgB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,aAAa,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CAC3C;AAwDD,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CASzC"}
@@ -0,0 +1,2 @@
1
+ import { n as e, t } from "../setup-BUzWSuVx.js";
2
+ export { t as _getJsContext, e as _resetJsContextSpy };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Query helpers for finding nodes in a TestNode tree.
3
+ */
4
+ import { TestNode } from './test-node.js';
5
+ export declare function getByType(container: TestNode, type: string): TestNode;
6
+ export declare function getAllByType(container: TestNode, type: string): TestNode[];
7
+ export declare function getByText(container: TestNode, text: string): TestNode;
8
+ export declare function queryByType(container: TestNode, type: string): TestNode | null;
9
+ export declare function queryByText(container: TestNode, text: string): TestNode | null;
10
+ export declare function getByProp(container: TestNode, key: string, value: unknown): TestNode;
11
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,wBAAgB,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAIrE;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAE1E;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAIrE;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAE9E;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAE9E;AAED,wBAAgB,SAAS,CACvB,SAAS,EAAE,QAAQ,EACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,GACb,QAAQ,CAYV"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * render() — mount a sigx Lynx component into a TestNode tree for testing.
3
+ */
4
+ import type { JSXElement, AppContext } from '@sigx/lynx';
5
+ import { TestNode } from './test-node.js';
6
+ export interface RenderResult {
7
+ /** The root container node. */
8
+ container: TestNode;
9
+ /** Unmount the component and clean up. */
10
+ unmount: () => void;
11
+ /** Find first node by element type (throws if not found). */
12
+ getByType: (type: string) => TestNode;
13
+ /** Find all nodes by element type. */
14
+ getAllByType: (type: string) => TestNode[];
15
+ /** Find first node containing text (throws if not found). */
16
+ getByText: (text: string) => TestNode;
17
+ /** Find first node by element type (returns null if not found). */
18
+ queryByType: (type: string) => TestNode | null;
19
+ /** Find first node containing text (returns null if not found). */
20
+ queryByText: (text: string) => TestNode | null;
21
+ /** Find first node by prop key/value (throws if not found). */
22
+ getByProp: (key: string, value: unknown) => TestNode;
23
+ /** Debug print the tree. */
24
+ debug: () => string;
25
+ }
26
+ /**
27
+ * Render a JSX element into an in-memory TestNode tree.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const { getByText, container } = render(<MyComponent name="World" />);
32
+ * expect(getByText('Hello World')).toBeTruthy();
33
+ * ```
34
+ */
35
+ export declare function render(element: JSXElement, options?: {
36
+ appContext?: AppContext;
37
+ }): RenderResult;
38
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,SAAS,EAAE,QAAQ,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IACtC,sCAAsC;IACtC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;IAC3C,6DAA6D;IAC7D,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IACtC,mEAAmE;IACnE,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;IAC/C,mEAAmE;IACnE,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;IAC/C,+DAA+D;IAC/D,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,QAAQ,CAAC;IACrD,4BAA4B;IAC5B,KAAK,EAAE,MAAM,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,UAAU,CAAA;CAAE,GACpC,YAAY,CAoBd"}
@@ -0,0 +1,55 @@
1
+ import { vi as e } from "vitest";
2
+ //#region src/mt/setup.ts
3
+ var t = {
4
+ addEventListener: e.fn(),
5
+ dispatchEvent: e.fn()
6
+ }, n = {
7
+ SystemInfo: { lynxSdkVersion: "3.5.0" },
8
+ getJSContext: () => t,
9
+ getCoreContext: () => t,
10
+ setTimeout: globalThis.setTimeout.bind(globalThis),
11
+ setInterval: globalThis.setInterval.bind(globalThis),
12
+ clearTimeout: globalThis.clearTimeout.bind(globalThis),
13
+ clearInterval: globalThis.clearInterval.bind(globalThis),
14
+ requestAnimationFrame: (e) => globalThis.setTimeout(() => e(Date.now()), 16),
15
+ cancelAnimationFrame: (e) => globalThis.clearTimeout(e),
16
+ querySelector: (e) => null,
17
+ querySelectorAll: (e) => []
18
+ };
19
+ globalThis.lynx = n, globalThis.SystemInfo = { lynxSdkVersion: "3.5.0" };
20
+ var r = e.fn();
21
+ for (let e of [
22
+ "__CreatePage",
23
+ "__CreateView",
24
+ "__SetCSSId",
25
+ "__AppendElement",
26
+ "__GetElementUniqueID",
27
+ "__SetInlineStyles",
28
+ "__SetStyle",
29
+ "__AddInlineStyle",
30
+ "__SetAttribute",
31
+ "__AddEvent",
32
+ "__GetAttributeByName",
33
+ "__GetAttributeNames",
34
+ "__GetComputedStyleByKey",
35
+ "__QuerySelector",
36
+ "__QuerySelectorAll",
37
+ "__InvokeUIMethod",
38
+ "__FlushElementTree",
39
+ "__GetPageElement",
40
+ "__ElementAnimate"
41
+ ]) globalThis[e] = r;
42
+ await import("@sigx/lynx-runtime-main"), await import("@lynx-js/react/worklet-runtime"), await import("@sigx/lynx-runtime-main/install-hybrid-worklet");
43
+ function i() {
44
+ return t;
45
+ }
46
+ function a() {
47
+ t = {
48
+ addEventListener: e.fn(),
49
+ dispatchEvent: e.fn()
50
+ };
51
+ }
52
+ //#endregion
53
+ export { a as n, i as t };
54
+
55
+ //# sourceMappingURL=setup-BUzWSuVx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-BUzWSuVx.js","names":[],"sources":["../src/mt/setup.ts"],"sourcesContent":["/**\n * Side-effect bootstrap for the main-thread (MT) gesture/worklet test\n * harness. Consumers list this in their `vitest.mt.config.ts`'s\n * `setupFiles` array; it must run before any test that drives a worklet\n * directly.\n *\n * ```ts\n * // vitest.mt.config.ts\n * import { defineConfig } from 'vitest/config';\n *\n * export default defineConfig({\n * test: {\n * environment: 'happy-dom',\n * globals: true,\n * include: ['__tests__/**\\/*.mt.test.ts'],\n * setupFiles: ['@sigx/lynx-testing/mt/setup'],\n * },\n * });\n * ```\n *\n * Order matters and is encoded here:\n * 1. Stub PAPI globals (`__SetAttribute` etc.) + `globalThis.lynx` +\n * `globalThis.SystemInfo`. The worklet-runtime IIFE reads\n * `SystemInfo.lynxSdkVersion` at init and reassigns lynx.setTimeout /\n * lynx.requestAnimationFrame onto globalThis, so both must exist\n * before its IIFE evaluates.\n * 2. Side-effect import `@sigx/lynx-runtime-main` — its entry-main\n * module installs `sigxRunOnMT`, `runOnBackground`, etc. (no PAPI\n * calls fire at module init — only inside the renderPage /\n * sigxPatchUpdate handlers we never invoke from these tests).\n * 3. Side-effect import `@lynx-js/react/worklet-runtime` — IIFE that\n * installs `globalThis.lynxWorkletImpl`, `registerWorkletInternal`,\n * `runWorklet`.\n * 4. Side-effect import `@sigx/lynx-runtime-main/install-hybrid-worklet`\n * — registers the hybrid dispatcher into the now-populated\n * `_workletMap`.\n *\n * The mocks are installed once per worker. `resetJsContextSpy()` from the\n * companion `@sigx/lynx-testing/mt` module wipes the dispatchEvent /\n * addEventListener spies between tests when needed.\n */\n\nimport { vi } from 'vitest';\n\ninterface JSContextMock {\n addEventListener: ReturnType<typeof vi.fn>;\n dispatchEvent: ReturnType<typeof vi.fn>;\n}\n\nlet jsContext: JSContextMock = {\n addEventListener: vi.fn(),\n dispatchEvent: vi.fn(),\n};\n\nconst lynxMock = {\n SystemInfo: { lynxSdkVersion: '3.5.0' },\n getJSContext: () => jsContext,\n getCoreContext: () => jsContext,\n setTimeout: globalThis.setTimeout.bind(globalThis),\n setInterval: globalThis.setInterval.bind(globalThis),\n clearTimeout: globalThis.clearTimeout.bind(globalThis),\n clearInterval: globalThis.clearInterval.bind(globalThis),\n requestAnimationFrame: (cb: FrameRequestCallback) => globalThis.setTimeout(() => cb(Date.now()), 16) as unknown as number,\n cancelAnimationFrame: (h: number) => globalThis.clearTimeout(h as unknown as ReturnType<typeof globalThis.setTimeout>),\n querySelector: (_sel: string) => null,\n querySelectorAll: (_sel: string) => [],\n};\n\n(globalThis as Record<string, unknown>)['lynx'] = lynxMock;\n(globalThis as Record<string, unknown>)['SystemInfo'] = { lynxSdkVersion: '3.5.0' };\n\nconst noopPapi = vi.fn();\nconst papiKeys = [\n '__CreatePage',\n '__CreateView',\n '__SetCSSId',\n '__AppendElement',\n '__GetElementUniqueID',\n '__SetInlineStyles',\n '__SetStyle',\n '__AddInlineStyle',\n '__SetAttribute',\n '__AddEvent',\n '__GetAttributeByName',\n '__GetAttributeNames',\n '__GetComputedStyleByKey',\n '__QuerySelector',\n '__QuerySelectorAll',\n '__InvokeUIMethod',\n '__FlushElementTree',\n '__GetPageElement',\n '__ElementAnimate',\n];\nfor (const k of papiKeys) {\n (globalThis as Record<string, unknown>)[k] = noopPapi;\n}\n\nawait import('@sigx/lynx-runtime-main');\nawait import('@lynx-js/react/worklet-runtime');\nawait import('@sigx/lynx-runtime-main/install-hybrid-worklet');\n\n// Internal — exposed through `@sigx/lynx-testing/mt` so tests can read or\n// reset the JS-context spy between cases without re-stubbing the global.\nexport function _getJsContext(): JSContextMock {\n return jsContext;\n}\n\nexport function _resetJsContextSpy(): void {\n jsContext = {\n addEventListener: vi.fn(),\n dispatchEvent: vi.fn(),\n };\n // Re-bind so `lynxMock.getJSContext()` / `getCoreContext()` return the\n // fresh spy instance (the closure captures `jsContext` by lexical\n // reference, so re-assigning it above is enough — but document this\n // here so future readers don't get confused if they refactor.)\n}\n"],"mappings":";;AAiDA,IAAI,IAA2B;CAC3B,kBAAkB,EAAG,IAAI;CACzB,eAAe,EAAG,IAAI;CACzB,EAEK,IAAW;CACb,YAAY,EAAE,gBAAgB,SAAS;CACvC,oBAAoB;CACpB,sBAAsB;CACtB,YAAY,WAAW,WAAW,KAAK,WAAW;CAClD,aAAa,WAAW,YAAY,KAAK,WAAW;CACpD,cAAc,WAAW,aAAa,KAAK,WAAW;CACtD,eAAe,WAAW,cAAc,KAAK,WAAW;CACxD,wBAAwB,MAA6B,WAAW,iBAAiB,EAAG,KAAK,KAAK,CAAC,EAAE,GAAG;CACpG,uBAAuB,MAAc,WAAW,aAAa,EAAyD;CACtH,gBAAgB,MAAiB;CACjC,mBAAmB,MAAiB,EAAE;CACzC;AAED,WAAwC,OAAU,GAClD,WAAwC,aAAgB,EAAE,gBAAgB,SAAS;AAEnF,IAAM,IAAW,EAAG,IAAI;AAsBxB,KAAK,IAAM,KAAK;CApBZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEY,EACZ,WAAwC,KAAK;AAGjD,MAAM,OAAO,4BACb,MAAM,OAAO,mCACb,MAAM,OAAO;AAIb,SAAgB,IAA+B;CAC3C,OAAO;;AAGX,SAAgB,IAA2B;CACvC,IAAY;EACR,kBAAkB,EAAG,IAAI;EACzB,eAAe,EAAG,IAAI;EACzB"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * TestNode — lightweight in-memory tree node for test rendering.
3
+ * Replaces ShadowElement + Lynx PAPI for testing purposes.
4
+ */
5
+ export declare class TestNode {
6
+ type: string;
7
+ props: Record<string, unknown>;
8
+ children: TestNode[];
9
+ parent: TestNode | null;
10
+ text?: string;
11
+ /** Event handlers keyed by prop name (e.g. 'bindtap', 'bindtouchstart'). */
12
+ _handlers: Map<string, Function>;
13
+ /** Style object (last value from patchProp 'style'). */
14
+ _style: Record<string, unknown>;
15
+ /** Class string. */
16
+ _class: string;
17
+ constructor(type: string);
18
+ /** Find first descendant matching element type. */
19
+ findByType(type: string): TestNode | null;
20
+ /** Find all descendants matching element type. */
21
+ findAllByType(type: string): TestNode[];
22
+ /** Find first descendant containing the given text. */
23
+ findByText(text: string): TestNode | null;
24
+ /** Get all text content from this node and descendants. */
25
+ textContent(): string;
26
+ /** Debug: serialize tree to a readable string. */
27
+ toDebugString(indent?: number): string;
28
+ }
29
+ //# sourceMappingURL=test-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-node.d.ts","sourceRoot":"","sources":["../src/test-node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,QAAQ;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IACpC,QAAQ,EAAE,QAAQ,EAAE,CAAM;IAC1B,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,4EAA4E;IAC5E,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAa;IAE7C,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IAErC,oBAAoB;IACpB,MAAM,SAAM;IAEZ,YAAY,IAAI,EAAE,MAAM,EAEvB;IAID,mDAAmD;IACnD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAOxC;IAED,kDAAkD;IAClD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAOtC;IAED,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAOxC;IAED,2DAA2D;IAC3D,WAAW,IAAI,MAAM,CAGpB;IAED,kDAAkD;IAClD,aAAa,CAAC,MAAM,SAAI,GAAG,MAAM,CAUhC;CACF"}
@@ -0,0 +1,6 @@
1
+ import type { RendererOptions } from '@sigx/runtime-core/internals';
2
+ import { TestNode } from './test-node.js';
3
+ declare const nodeOps: RendererOptions<TestNode, TestNode>;
4
+ export declare const testRenderer: import("@sigx/runtime-core/internals").Renderer<TestNode, TestNode>;
5
+ export { nodeOps as testNodeOps };
6
+ //# sourceMappingURL=test-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-renderer.d.ts","sourceRoot":"","sources":["../src/test-renderer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,QAAA,MAAM,OAAO,EAAE,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAsGhD,CAAC;AAEF,eAAO,MAAM,YAAY,qEAA8C,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,CAAC"}