API reference
Every export of @sigx/lynx-testing v0.28.0.
The package has three entry points: @sigx/lynx-testing (the main render / query / event API), @sigx/lynx-testing/mt (the main-thread worklet harness) and @sigx/lynx-testing/mt/setup (a side-effect bootstrap for Vitest setupFiles). Exports tagged below with a subpath live under /mt.
Rendering
render
export function render(
element: JSXElement,
options?: { appContext?: AppContext },
): RenderResult
Mounts a sigx Lynx JSX element into an in-memory TestNode tree and returns query and teardown helpers. This is the background side only — there is no Lynx runtime or PAPI, and the worklet transform does not run. Pass an appContext via options to supply application context. Returns a RenderResult. Available on iOS and Android.
Queries
These standalone helpers operate on any TestNode container; the synchronous set is also returned, container-bound, from render, and within scopes all of them to a subtree. The get* helpers throw when no node matches (the error prints the searched tree via formatTree); the query* helpers return null; the find* helpers return a Promise that polls with waitFor until a match appears.
getByType
export function getByType(container: TestNode, type: string): TestNode
Returns the first descendant matching an element type. Throws if none found.
getAllByType
export function getAllByType(container: TestNode, type: string): TestNode[]
Returns all descendants matching an element type. Returns an empty array if none match.
getByText
export function getByText(container: TestNode, text: string): TestNode
Returns the first descendant whose text content includes the given substring. Throws if none found.
queryByType
export function queryByType(container: TestNode, type: string): TestNode | null
Returns the first descendant matching an element type, or null if none found.
queryByText
export function queryByText(container: TestNode, text: string): TestNode | null
Returns the first descendant whose text content includes the given substring, or null if none found.
getByProp
export function getByProp(container: TestNode, key: string, value: unknown): TestNode
Returns the first node (including the container) whose props[key] strictly equals (===) value. Throws if none found.
queryByProp
export function queryByProp(container: TestNode, key: string, value: unknown): TestNode | null
Returns the first node (including the container) whose props[key] strictly equals value, or null if none found.
findByType
export function findByType(container: TestNode, type: string, options?: WaitForOptions): Promise<TestNode>
waitFor plus getByType: resolves with the first descendant matching the element type once one exists. Rejects after options.timeout (default 1000 ms) with the getByType error, so the failure names the type and prints the tree.
findByText
export function findByText(container: TestNode, text: string, options?: WaitForOptions): Promise<TestNode>
findByProp
export function findByProp(container: TestNode, key: string, value: unknown, options?: WaitForOptions): Promise<TestNode>
within
export function within(node: TestNode): {
getByType; getAllByType; getByText; getByProp;
queryByType; queryByText; queryByProp;
findByType; findByText; findByProp;
}
Scopes all ten query helpers to a subtree. Each returned function has the standalone helper's signature minus the leading container argument. Without it, a query for a common node type finds the first one anywhere in the tree, which is rarely the one the assertion meant.
const row = getByProp(container, 'id', 'row-3');
expect(within(row).queryByText('Delete')).toBeTruthy();
formatTree
export function formatTree(node: TestNode): string
Pretty-prints a TestNode subtree, one node per line with its type, props and text. This is what a failing getBy* / findBy* call appends to its error message under Tree searched:.
Events
fireEvent
export const fireEvent: {
tap(node: TestNode, data?: { x?: number; y?: number }): void;
touchStart(node: TestNode, data?: SyntheticTouchEvent): void;
touchMove(node: TestNode, data?: SyntheticTouchEvent): void;
touchEnd(node: TestNode, data?: SyntheticTouchEvent): void;
touchCancel(node: TestNode, data?: SyntheticTouchEvent): void;
scroll(node: TestNode, data?: SyntheticScrollEvent): void;
input(node: TestNode, data?: SyntheticInputEvent): void;
longPress(node: TestNode): void;
}
Dispatches synthetic events to a TestNode's registered handlers. tap, scroll, input and longPress fire both the bind* element form (for example bindtap) and the camelCase onX component form, so either handler shape fires. touchStart / touchMove / touchEnd / touchCancel fire only the bind* form. Available on iOS and Android.
See Synthetic event shapes for the data interfaces.
touch
export function touch(pageX: number, pageY: number, identifier = 1): SyntheticTouch
Builds a normalized synthetic touch object — { identifier, x, y, pageX, pageY, clientX, clientY } — for passing into fireEvent.touch* via touches / changedTouches. Available on iOS and Android.
Flushing
act
export async function act(fn: () => void | Promise<void>): Promise<void>
Runs a callback (for example signal mutations or a fireEvent call) then awaits waitForUpdate, so the rendered tree commits before you assert. Available on iOS and Android.
waitForUpdate
export function waitForUpdate(): Promise<void>
A bare reactive flush: awaits a microtask then a setTimeout(0) so pending reactive effects and scheduled renderer commits complete before assertions. Available on iOS and Android.
act and waitForUpdate advance a fixed amount — one microtask plus one macrotask. That is right when the work completes in one turn: a signal write, a synchronous effect. Anything whose turn count you cannot know — a dynamic import, chained effects, a deferred callback — needs waitFor. Writing for (let i = 0; i < 5; i++) await act(…) or await new Promise(r => setTimeout(r, 60)) is a guess that holds until the CI runner is busy.
waitFor
export async function waitFor<T>(
condition: () => T | Promise<T>,
options?: WaitForOptions,
): Promise<T>
Polls condition until it returns a truthy value, then returns that value. A condition that throws counts as "not yet", which is what lets waitFor(() => getByText(container, 'Done')) read naturally — the getBy* queries throw when they find nothing. On timeout it rethrows the condition's last error if there was one (so the failure names what was missing and prints the tree); otherwise it throws [@sigx/lynx-testing] timed out after <ms>ms waiting for <description>. A non-finite or negative timeout / interval throws immediately rather than looping forever.
// Takes however many turns the hydration actually needs.
const tabBar = await waitFor(() => getByProp(container, 'role', 'tablist'));
WaitForOptions
export interface WaitForOptions {
/** Give up after this long, in milliseconds. Default 1000. */
timeout?: number;
/** Extra delay between attempts, in milliseconds. Default 0 — one turn. */
interval?: number;
/** Named in the timeout message, to say what was being waited for. */
description?: string;
}
Options for waitFor and the find* queries (which fill in description themselves).
Types
RenderResult
export interface RenderResult {
container: TestNode;
unmount: () => void;
getByType: (type: string) => TestNode;
getAllByType: (type: string) => TestNode[];
getByText: (text: string) => TestNode;
queryByType: (type: string) => TestNode | null;
queryByText: (text: string) => TestNode | null;
getByProp: (key: string, value: unknown) => TestNode;
debug: () => string;
}
The return value of render: the container root node, container-scoped query helpers, an unmount teardown function and a debug pretty-printer that returns the tree as a string.
TestNode
export class TestNode {
type: string;
props: Record<string, unknown>;
children: TestNode[];
parent: TestNode | null;
text?: string;
_handlers: Map<string, Function>;
_style: Record<string, unknown>;
_class: string;
constructor(type: string);
findByType(type: string): TestNode | null;
findAllByType(type: string): TestNode[];
findByText(text: string): TestNode | null;
textContent(): string;
toDebugString(indent?: number): string;
}
A lightweight in-memory tree node that replaces ShadowElement plus the Lynx PAPI. It holds the element type, props, children, optional text, registered event handlers (_handlers), resolved _style and _class, plus tree-query methods (findByType, findAllByType, findByText), a textContent() accessor and a toDebugString() pretty-printer.
Synthetic event shapes
The data arguments accepted by fireEvent. These interfaces are not exported; they are documented here for reference.
interface SyntheticTouch {
identifier?: number;
x?: number;
y?: number;
pageX?: number;
pageY?: number;
clientX?: number;
clientY?: number;
}
interface SyntheticTouchEvent {
touches?: SyntheticTouch[];
changedTouches?: SyntheticTouch[];
}
interface SyntheticScrollEvent {
detail?: {
scrollTop?: number;
scrollLeft?: number;
scrollHeight?: number;
scrollWidth?: number;
deltaX?: number;
deltaY?: number;
};
}
interface SyntheticInputEvent {
detail?: { value?: string };
}
SyntheticTouch is the shape returned by touch and accepted in touches / changedTouches arrays. SyntheticTouchEvent is the data arg for the touch* events, SyntheticScrollEvent for scroll and SyntheticInputEvent for input.
Main-thread worklet harness
The following exports live under the @sigx/lynx-testing/mt subpath. They compile and run main-thread worklets against a mocked gesture arena. Peer dependencies (@lynx-js/react, @sigx/lynx-runtime-main, vitest) must be installed, and @sigx/lynx-testing/mt/setup must run via Vitest setupFiles first — the compile and map functions throw if it did not.
compileMTWorklets
export function compileMTWorklets(opts: {
filename: string;
source: string;
runtimePkg?: string;
}): Function[]
@sigx/lynx-testing/mt — Compiles a .tsx source through the SWC LEPUS transform, evals the emitted registerWorkletInternal(...) calls into the live worklet runtime and returns the registered worklets in source order. For example, Gesture.Pan().onBegin().onStart().onUpdate().onEnd() maps to [0]=onBegin, [1]=onStart, [2]=onUpdate, [3]=onEnd. Call each worklet with .call(ctx, event), where ctx supplies the _c capture. runtimePkg defaults to '@sigx/lynx-runtime-main'. Available on iOS and Android.
extractRegistrations
export function extractRegistrations(lepusCode: string): string
@sigx/lynx-testing/mt — Extracts the registerWorkletInternal(...) call source from a LEPUS-target transform output via bracket-depth counting. Called internally by compileMTWorklets but exported for custom compile flows. Available on iOS and Android.
getWorkletMap
export function getWorkletMap(): Record<string, Function>
@sigx/lynx-testing/mt — Returns the live lynxWorkletImpl._workletMap (_wkltId to callable) populated by the upstream worklet runtime. Throws if mt/setup did not run. Available on iOS and Android.
makeRef
export function makeRef<T>(current: T, id = 1): { current: T; _wvid: number }
@sigx/lynx-testing/mt — Fabricates a synthetic MainThreadRef shape ({ current, _wvid }) that worklets read via ref.current.value and may mutate. Available on iOS and Android.
fabricatePanEvent
export function fabricatePanEvent(opts: { pageX: number; pageY?: number }): MTGestureEvent
@sigx/lynx-testing/mt — Fabricates a Lynx pan-gesture event payload matching the iOS arena shape: touch data is nested under e.params / e.detail, not top-level. Verified against the iOS Lynx 3.5 gesture handlers. iOS only.
fabricateTapEvent
export function fabricateTapEvent(opts: { pageX?: number; pageY?: number } = {}): MTGestureEvent
@sigx/lynx-testing/mt — Fabricates a Lynx tap-gesture event payload (the same params / detail-nested shape as the pan event, minus the scroll fields). iOS only.
getJsContext
export function getJsContext(): { addEventListener: Function; dispatchEvent: Function }
@sigx/lynx-testing/mt — Returns the JS-context spy installed by mt/setup on the lynx mock. Use it to assert runOnBackground / Lynx.Sigx.AvPublish dispatchEvent calls made from within a worklet. Available on iOS and Android.
resetJsContextSpy
export function resetJsContextSpy(): void
@sigx/lynx-testing/mt — Replaces the JS-context spy with fresh vi.fn() instances between tests, so dispatchEvent / addEventListener call counts do not bleed across cases. Available on iOS and Android.
MTGestureEvent
export interface MTGestureEvent {
type: string;
timestamp: number;
currentTarget: { element: null };
target: { element: null };
params: Record<string, unknown>;
detail: Record<string, unknown>;
}
@sigx/lynx-testing/mt — The shape of the events the iOS gesture arena delivers to main-thread worklets. The top-level keys are dispatch metadata; the actual touch data lives under params (duplicated in detail). Returned by fabricatePanEvent and fabricateTapEvent.
mt/setup (side-effect bootstrap)
import '@sigx/lynx-testing/mt/setup';
@sigx/lynx-testing/mt/setup — A side-effect-only module listed in Vitest setupFiles. It stubs the PAPI globals plus globalThis.lynx and globalThis.SystemInfo, then imports @sigx/lynx-runtime-main, @lynx-js/react/worklet-runtime and @sigx/lynx-runtime-main/install-hybrid-worklet to boot the worklet runtime. The ordering is load-bearing and the mocks install once per worker. Available on iOS and Android.
