Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

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): void
Registers 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.
@paramfn - The callback function to be executed after all tests.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterAll to close a database connection afterAll(async () => { await database.disconnect(); }); ```
afterAll
, const expect: ExpectStaticexpect, const it: TestAPI
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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): void
Registers 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.
@paramfn - The callback function to be executed after all tests.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterAll to close a database connection afterAll(async () => { await database.disconnect(); }); ```
afterAll
(() =>
const compiled: CompiledScene<{
    toast: Toast;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled
[var Symbol: SymbolConstructorSymbol.SymbolConstructor.dispose: typeof Symbol.dispose
A 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.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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) => void
Checks that a value is what you expect. It calls `Object.is` to compare values. Don't use `toBe` with floating-point numbers.
@exampleexpect(result).toBe(42); expect(status).toBe(true);
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) => void
Checks that a value is what you expect. It calls `Object.is` to compare values. Don't use `toBe` with floating-point numbers.
@exampleexpect(result).toBe(42); expect(status).toBe(true);
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.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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) => void
Checks that a value is what you expect. It calls `Object.is` to compare values. Don't use `toBe` with floating-point numbers.
@exampleexpect(result).toBe(42); expect(status).toBe(true);
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) => void
Checks that a value is what you expect. It calls `Object.is` to compare values. Don't use `toBe` with floating-point numbers.
@exampleexpect(result).toBe(42); expect(status).toBe(true);
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.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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.
@paramcallback A function that accepts up to three arguments. The flatMap method calls the callback function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callback function. If thisArg is omitted, undefined is used as the this value.
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.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(({ eventName: stringeventName, frame: numberframe }) => [eventName: stringeventName, frame: numberframe]))
.JestAssertion<(string | number)[][]>.toEqual: <(string | number)[][]>(expected: (string | number)[][]) => void
Used 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.
@exampleexpect(user).toEqual({ name: 'Alice', age: 30 });
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: TestAPI
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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): void
Publish `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) => void
Checks that a value is what you expect. It calls `Object.is` to compare values. Don't use `toBe` with floating-point numbers.
@exampleexpect(result).toBe(42); expect(status).toBe(true);
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>) => ReactRenderRuntime
The 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): string
Similar 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): void
Registers 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.
@paramfn - The callback function to be executed after all tests.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterAll to close a database connection afterAll(async () => { await database.disconnect(); }); ```
afterAll
, const expect: ExpectStaticexpect, const it: TestAPI
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
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>): ReactRenderRuntime
The 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[] | undefined
Family 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): void
Registers 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.
@paramfn - The callback function to be executed after all tests.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterAll to close a database connection afterAll(async () => { await database.disconnect(); }); ```
afterAll
(() => const compiled: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>compiled[var Symbol: SymbolConstructorSymbol.SymbolConstructor.dispose: typeof Symbol.dispose
A 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.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
it
("draws the bar at its width for the frame", () => {
const const html: stringhtml = function renderToStaticMarkup(element: React.ReactNode, options?: ServerOptions): string
Similar 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) => void
Used 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.
@exampleexpect(items).toContain('apple'); expect(numbers).toContain(5);
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.

Was this page helpful?