import { describe, expect, it } from "vitest"; import type { ResolvedToolcraftAppSchema } from "../schema/resolved-app-schema"; import { getToolcraftCollectionItemControlAddress } from "./collection-control-address"; import { evaluateToolcraftTimelineValue, evaluateToolcraftTimelineValues, } from "./keyframe-evaluation"; import { createState } from "./reducer-test-utils"; function createCollectionState() { const base = createState({ values: { "storyboard.notes": [ { color: "#14B8A6", opacity: 60, position: { x: 2, y: 3 } }, ], }, }); const controls = { sections: [ { controls: { notes: { applicability: { mode: "always", origin: "explicit" }, defaultValue: [], itemControls: { color: { defaultValue: "#14B8A6", type: "color" }, opacity: { defaultValue: 60, keyframeable: true, max: 100, min: 0, type: "slider", }, }, target: "storyboard.notes", type: "collectionActions", }, }, id: "notes", }, ], title: "Controls", } satisfies NonNullable; return { ...base, schema: { ...base.schema, panels: { ...base.schema.panels, controls }, }, }; } describe("Toolcraft collection keyframe evaluation", () => { it("reconstructs parent collections from atomic nested tracks", () => { const address = getToolcraftCollectionItemControlAddress( "storyboard.notes", 0, "opacity", ); const base = createCollectionState(); const state = { ...base, timeline: { ...base.timeline, keyframeGroups: [ { controlId: address, keyframes: [ { controlId: address, controlLabel: "Opacity", id: "zero", timeSeconds: 0, value: 20, valueLabel: "20", }, { controlId: address, controlLabel: "Opacity", id: "ten", timeSeconds: 10, value: 80, valueLabel: "80", }, ], label: "Opacity", }, ], }, }; expect(evaluateToolcraftTimelineValue(state, address, 5)).toBeCloseTo(50); expect( (evaluateToolcraftTimelineValue(state, "storyboard.notes", 5) as Array< Record >)[0]?.opacity, ).toBeCloseTo(50); expect( (evaluateToolcraftTimelineValues(state, 5)["storyboard.notes"] as Array< Record >)[0]?.opacity, ).toBeCloseTo(50); }); it("fails closed for a nested track whose field did not opt into keyframes", () => { const address = getToolcraftCollectionItemControlAddress( "storyboard.notes", 0, "color", ); const base = createCollectionState(); const state = { ...base, timeline: { ...base.timeline, keyframeGroups: [ { controlId: address, keyframes: [ { controlId: address, controlLabel: "Color", id: "invalid-color", timeSeconds: 0, value: "#FF0000", valueLabel: "#FF0000", }, ], label: "Color", }, ], }, }; expect(evaluateToolcraftTimelineValue(state, address, 0)).toBeUndefined(); expect(evaluateToolcraftTimelineValue(state, "storyboard.notes", 0)).toEqual( base.values["storyboard.notes"], ); }); });