Testing
Compile a real scene, resolve frames and assert on what they hold, with no renderer, browser or hand-built fixture.
at(frame) needs no renderer, so a test compiles a scene the way
an app does and reads frames from it. These tests use the Toast from
Creating actors.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene } from "@motionactor/core";
import { function afterAll<ExtraContext = object>(this: unknown, fn: AfterAllListener<ExtraContext>, timeout?: number): voidRegisters a callback function to be executed once after all tests within the current suite have completed.
This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
**Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.afterAll, const expect: ExpectStaticexpect, const it: TestAPIDefines a test case with a given name and test function. The test function can optionally be configured with test options.it } from "vitest";
import { class ToastToast } from "./toast";
const const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
toast: Toast;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
toast: Toast;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const toast: Toasttoast = ctx: SceneContextctx.CompileContext.spawn<Toast>(definition: ActorClass<Toast>, overrides: {
message: SignalInputValue<string>;
appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 23 more ...;
parent?: undefined;
}, options?: {
...;
} | undefined): Toast (+1 overload)
spawn(class ToastToast, {
message: SignalInputValue<string>message: "Saved",
appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined
appearance: { opacity?: SignalInputValue<number> | undefinedopacity: 0 },
});
yield* const toast: Toasttoast.Toast.show({ hold }: {
hold: Duration;
}): Generator<number, void, unknown>
show({ hold: numberhold: 2 });
return { toast: Toasttoast };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
// The file's tests share one build; it releases its cells after the last.
afterAll<object>(this: unknown, fn: AfterAllListener<object>, timeout?: number): voidRegisters a callback function to be executed once after all tests within the current suite have completed.
This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
**Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.afterAll(() => const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled[var Symbol: SymbolConstructorSymbol.SymbolConstructor.dispose: typeof Symbol.disposeA method that is used to release resources held by an object. Called by the semantics of the `using` statement.dispose]());
it<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)Defines a test case with a given name and test function. The test function can optionally be configured with test options.it("shrinks the bar during the countdown", () => {
expect<number | undefined>(actual: number | undefined, message?: string): Assertion<number | undefined> (+1 overload)expect(const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
toast: Toast;
}>
at(36).FrameState<{ ...; }>.scene: {
toast: ResolvedActorOfInstance<Toast> | undefined;
}
scene.toast: ResolvedActorOfInstance<Toast> | undefinedtoast?.barWidth: number | undefinedbarWidth).JestAssertion<number | undefined>.toBe: <number>(expected: number) => voidChecks that a value is what you expect. It calls `Object.is` to compare values.
Don't use `toBe` with floating-point numbers.toBe(180);
expect<number | undefined>(actual: number | undefined, message?: string): Assertion<number | undefined> (+1 overload)expect(const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
toast: Toast;
}>
at(66).FrameState<{ ...; }>.scene: {
toast: ResolvedActorOfInstance<Toast> | undefined;
}
scene.toast: ResolvedActorOfInstance<Toast> | undefinedtoast?.barWidth: number | undefinedbarWidth).JestAssertion<number | undefined>.toBe: <number>(expected: number) => voidChecks that a value is what you expect. It calls `Object.is` to compare values.
Don't use `toBe` with floating-point numbers.toBe(0);
});
it<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)Defines a test case with a given name and test function. The test function can optionally be configured with test options.it("fades out after the countdown", () => {
expect<number | undefined>(actual: number | undefined, message?: string): Assertion<number | undefined> (+1 overload)expect(const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
toast: Toast;
}>
at(66).FrameState<{ ...; }>.scene: {
toast: ResolvedActorOfInstance<Toast> | undefined;
}
scene.toast: ResolvedActorOfInstance<Toast> | undefinedtoast?.appearance: {
opacity: number;
}
appearance.opacity: number | undefinedopacity).JestAssertion<number | undefined>.toBe: <number>(expected: number) => voidChecks that a value is what you expect. It calls `Object.is` to compare values.
Don't use `toBe` with floating-point numbers.toBe(1);
expect<number | undefined>(actual: number | undefined, message?: string): Assertion<number | undefined> (+1 overload)expect(const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
toast: Toast;
}>
at(69).FrameState<{ ...; }>.scene: {
toast: ResolvedActorOfInstance<Toast> | undefined;
}
scene.toast: ResolvedActorOfInstance<Toast> | undefinedtoast?.appearance: {
opacity: number;
}
appearance.opacity: number | undefinedopacity).JestAssertion<number | undefined>.toBe: <number>(expected: number) => voidChecks that a value is what you expect. It calls `Object.is` to compare values.
Don't use `toBe` with floating-point numbers.toBe(0.5);
});
it<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)Defines a test case with a given name and test function. The test function can optionally be configured with test options.it("announces the dismissal once it has faded", () => {
const const announced: ActorEventRecord[]announced = const compiled: CompiledScene<{
toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.events: readonly TimelineEvent[]events.ReadonlyArray<TimelineEvent>.flatMap<ActorEventRecord, undefined>(callback: (this: undefined, value: TimelineEvent, index: number, array: TimelineEvent[]) => ActorEventRecord | readonly ActorEventRecord[], thisArg?: undefined): ActorEventRecord[]Calls a defined callback function on each element of an array. Then, flattens the result into
a new array.
This is identical to a map followed by flat with depth 1.flatMap((entry: TimelineEvententry) =>
entry: TimelineEvententry.kind: "signal" | "effect" | "spawn" | "actor-event" | "slot-reorder" | "despawn" | "scene-stage"kind === "actor-event" ? [entry: {
kind: "actor-event";
event: ActorEventRecord;
}
entry.event: ActorEventRecordevent] : [],
);
expect<(string | number)[][]>(actual: (string | number)[][], message?: string): Assertion<(string | number)[][]> (+1 overload)expect(const announced: ActorEventRecord[]announced.Array<ActorEventRecord>.map<(string | number)[]>(callbackfn: (value: ActorEventRecord, index: number, array: ActorEventRecord[]) => (string | number)[], thisArg?: any): (string | number)[][]Calls a defined callback function on each element of an array, and returns an array that contains the results.map(({ eventName: stringeventName, frame: numberframe }) => [eventName: stringeventName, frame: numberframe]))
.JestAssertion<(string | number)[][]>.toEqual: <(string | number)[][]>(expected: (string | number)[][]) => voidUsed when you want to check that two objects have the same value.
This matcher recursively checks the equality of all fields, rather than checking for object identity.toEqual([["dismissed", 72]]);
});
Frames and seconds
Scenes are authored in seconds and at takes whole frames, at the fps the test built the
scene at. Built at 30 fps, the toast’s 0.2 second fade is 6 frames and its 2 second countdown runs
from frame 6 to frame 66.
Assert inside a motion as well as at its ends. A tween that jumped straight to its target would pass a test of frames 6 and 66; frame 36 is the one that catches it.
compiled.totalDuration is the length in frames. A render draws frames 0 to
totalDuration - 1. Resolving totalDuration itself is useful for a final value, but an
actor that has exited by then is absent from the frame.
Reading actors
Return the actors a test needs from the scene, as the toast is here, and read them from
at(frame).scene. For an actor the scene does not return, look it up by class with
findActor(Toast), findActors(Toast) or getActor(id, Toast); the result is typed from
the class. Compiled scene lists the lookups.
Build every frame through the runtime. A hand-written resolved actor encodes the runtime’s shape on the day it was written, and the test keeps passing after that shape changes.
Values from the host
A channel reads what a host published. A test publishes its own value
through the resolved actor’s handle. Compile the scene inside the test with using, so the
publication goes with the build when the test ends:
import { function channel<TValue>(options: {
readonly default: TValue;
readonly record?: boolean;
readonly equals?: (previous: TValue, next: TValue) => boolean;
}): ChannelDefinition<TValue> & {
readonly hasDefault: true;
readonly default: TValue;
} (+1 overload)
A channel is a writable, scope-aware cell carrying an actor-channel brand.
Declared as an actor field it lives in the spawning build's store (keyed
`${actorId}:${name}` by the spawn walk, like a signal); declared free (in
`buildScene`, a script, or module top) it keys by its scope, in its build's
store or the module's, and is shared by reference. Either way resolution
reads it live.
The static return type stays `ChannelDefinition<TValue>` so the
`isChannelDefinition` predicate is unchanged; at runtime the handle is a
live cell.channel, function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const expect: ExpectStaticexpect, const it: TestAPIDefines a test case with a given name and test function. The test function can optionally be configured with test options.it } from "vitest";
class class MeterMeter extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Meter.level: ActorFieldSignal<number>level = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0.5);
Meter.trackWidth: ChannelDefinition<number> & {
readonly hasDefault: true;
readonly default: number;
}
trackWidth = channel<number>(options: {
readonly default: number;
readonly record?: boolean;
readonly equals?: ((previous: number, next: number) => boolean) | undefined;
}): ChannelDefinition<number> & {
readonly hasDefault: true;
readonly default: number;
} (+1 overload)
A channel is a writable, scope-aware cell carrying an actor-channel brand.
Declared as an actor field it lives in the spawning build's store (keyed
`${actorId}:${name}` by the spawn walk, like a signal); declared free (in
`buildScene`, a script, or module top) it keys by its scope, in its build's
store or the module's, and is shared by reference. Either way resolution
reads it live.
The static return type stays `ChannelDefinition<TValue>` so the
`isChannelDefinition` predicate is unchanged; at runtime the handle is a
live cell.channel({ default: numberdefault: 400 });
Meter.fillWidth: DerivedDefinition<number, unknown>fillWidth = derived<unknown, number>(fn: (this: unknown) => number): DerivedDefinition<number, unknown>derived(() => this.Meter.level: () => numberlevel() * this.Meter.trackWidth: () => numbertrackWidth());
}
it<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)Defines a test case with a given name and test function. The test function can optionally be configured with test options.it("fills half of a measured track", () => {
using using compiled: CompiledScene<{
meter: Meter;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
meter: Meter;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
meter: Meter;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const meter: Metermeter = ctx: SceneContextctx.CompileContext.spawn<Meter>(definition: ActorClass<Meter>, overrides?: {
measuredBox?: undefined;
level?: SignalInputValue<number> | undefined;
trackWidth?: undefined;
fillWidth?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Meter (+1 overload)
spawn(class MeterMeter);
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
return { meter: Metermeter };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
meter: Meter;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
using compiled: CompiledScene<{
meter: Meter;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
meter: Meter;
}>
at(0).FrameState<{ ...; }>.scene: {
meter: ResolvedActorOfInstance<Meter> | undefined;
}
scene.meter: ResolvedActorOfInstance<Meter> | undefinedmeter?.channels: ActorClassChannels<Omit<Meter, keyof Actor<any>>>channels.trackWidth: ResolvedChannelHandle<number>trackWidth.ResolvedChannelHandle<number>.write(value: number, frame?: number): voidPublish `value`. `frame` is the frame the producer's scene host committed;
a `record: true` channel files the value under it, so a later seek reads
the value observed at that frame. Without one the write is live only.write(800);
expect<number | undefined>(actual: number | undefined, message?: string): Assertion<number | undefined> (+1 overload)expect(using compiled: CompiledScene<{
meter: Meter;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
meter: Meter;
}>
at(0).FrameState<{ ...; }>.scene: {
meter: ResolvedActorOfInstance<Meter> | undefined;
}
scene.meter: ResolvedActorOfInstance<Meter> | undefinedmeter?.fillWidth: number | undefinedfillWidth).JestAssertion<number | undefined>.toBe: <number>(expected: number) => voidChecks that a value is what you expect. It calls `Object.is` to compare values.
Don't use `toBe` with floating-point numbers.toBe(400);
});
A publication stays for every later read of that build until
actor.channels.<name>.unpublish() removes it. A scene compiled at module scope is shared by
every test in the file, and so is what one of them publishes.
Drawing
A renderer test renders a frame to markup and checks what the renderer wrote:
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene } from "@motionactor/core";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime, const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer } from "@motionactor/renderers/react";
import { function renderToStaticMarkup(element: React.ReactNode, options?: ServerOptions): stringSimilar to `renderToString`, except this doesn't create extra DOM attributes
such as `data-reactid`, that React uses internally. This is useful if you want
to use React as a simple static page generator, as stripping away the extra
attributes can save lots of bytes.renderToStaticMarkup } from "react-dom/server";
import { function afterAll<ExtraContext = object>(this: unknown, fn: AfterAllListener<ExtraContext>, timeout?: number): voidRegisters a callback function to be executed once after all tests within the current suite have completed.
This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
**Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.afterAll, const expect: ExpectStaticexpect, const it: TestAPIDefines a test case with a given name and test function. The test function can optionally be configured with test options.it } from "vitest";
import { class ToastToast } from "./toast";
import { const toastRenderer: BackendRendererDefinition<readonly [typeof Toast], "dom">toastRenderer } from "./toast-renderer";
const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime({
backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom],
renderers?: readonly RendererRegistryEntry[] | undefinedFamily and project renderers, for any backend. Each routes to the backend it
names (`three.render(…)`); one naming a backend missing from
`backends` throws.renderers: [const toastRenderer: BackendRendererDefinition<readonly [typeof Toast], "dom">toastRenderer],
});
const const compiled: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>compiled = createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const toast: Toasttoast = ctx: SceneContextctx.CompileContext.spawn<Toast>(definition: ActorClass<Toast>, overrides: {
message: SignalInputValue<string>;
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<...> | undefined;
} | undefined;
... 22 more ...;
show?: undefined;
}, options?: {
...;
} | undefined): Toast (+1 overload)
spawn(class ToastToast, { message: SignalInputValue<string>message: "Saved" });
yield* const toast: Toasttoast.Toast.show({ hold }: {
hold: Duration;
}): Generator<number, void, unknown>
show({ hold: numberhold: 2 });
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
afterAll<object>(this: unknown, fn: AfterAllListener<object>, timeout?: number): voidRegisters a callback function to be executed once after all tests within the current suite have completed.
This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
**Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.afterAll(() => const compiled: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>compiled[var Symbol: SymbolConstructorSymbol.SymbolConstructor.dispose: typeof Symbol.disposeA method that is used to release resources held by an object. Called by the semantics of the `using` statement.dispose]());
it<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)Defines a test case with a given name and test function. The test function can optionally be configured with test options.it("draws the bar at its width for the frame", () => {
const const html: stringhtml = function renderToStaticMarkup(element: React.ReactNode, options?: ServerOptions): stringSimilar to `renderToString`, except this doesn't create extra DOM attributes
such as `data-reactid`, that React uses internally. This is useful if you want
to use React as a simple static page generator, as stripping away the extra
attributes can save lots of bytes.renderToStaticMarkup(
<const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer
compiled: CompiledScene<void, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled={const compiled: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>compiled}
frame: numberframe={36}
width: numberwidth={1280}
height: numberheight={720}
runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime}
/>,
);
expect<string>(actual: string, message?: string): Assertion<string> (+1 overload)expect(const html: stringhtml).JestAssertion<string>.toContain: <string>(item: string) => voidUsed when you want to check that an item is in a list.
For testing the items in the list, this uses `===`, a strict equality check.toContain('<i class="toast-bar" style="width:180px">');
});
Keep these few. The values a renderer draws are better tested on the actor, as above, where a wrong number fails without parsing markup.
What to test
Test what can break without anyone choosing it:
- timing, at a frame inside the motion,
- seeking: resolve a late frame, then an early one, and get the same answer as before,
- a child staying the same actor across frames,
- the bug you just fixed.
A test that an actor has the fields you just declared, or that a renderer shows the prop you passed it, cannot fail for a reason you did not write.