import { describe, expect, it } from "vitest"; import { getActiveMotionClipAtTime, resolveMotionClipEnd } from "./motion"; import type { MotionClip } from "../../schema/directorProject"; function clip(partial: Partial & Pick): MotionClip { return { kind: "walk", speed: 1, amplitude: 1, enabled: true, ...partial, }; } describe("getActiveMotionClipAtTime", () => { it("returns null when there are no clips", () => { expect(getActiveMotionClipAtTime(undefined, 1, 10)).toBeNull(); expect(getActiveMotionClipAtTime([], 1, 10)).toBeNull(); }); it("returns the clip covering the current time", () => { const a = clip({ id: "a", start: 0, end: 2 }); const b = clip({ id: "b", kind: "run", start: 2, end: 5 }); expect(getActiveMotionClipAtTime([a, b], 1, 5)?.id).toBe("a"); expect(getActiveMotionClipAtTime([a, b], 3, 5)?.id).toBe("b"); }); it("later clips win on overlap (last covering segment wins)", () => { const a = clip({ id: "a", start: 0, end: 5 }); const b = clip({ id: "b", start: 2, end: 6 }); expect(getActiveMotionClipAtTime([a, b], 3, 6)?.id).toBe("b"); }); it("treats an open-ended clip (Infinity) as lasting to the timeline end", () => { const a = clip({ id: "a", start: 0, end: Number.POSITIVE_INFINITY }); expect(getActiveMotionClipAtTime([a], 999, 10)?.id).toBe("a"); }); it("returns null for disabled / none clips (character stays static)", () => { const disabled = clip({ id: "a", start: 0, end: 5, enabled: false }); const none = clip({ id: "b", start: 0, end: 5, kind: "none" }); expect(getActiveMotionClipAtTime([disabled], 1, 5)).toBeNull(); expect(getActiveMotionClipAtTime([none], 1, 5)).toBeNull(); }); it("returns null outside any clip", () => { const a = clip({ id: "a", start: 1, end: 4 }); expect(getActiveMotionClipAtTime([a], 0, 5)).toBeNull(); expect(getActiveMotionClipAtTime([a], 5, 5)).toBeNull(); }); }); describe("resolveMotionClipEnd", () => { it("keeps a finite end within bounds", () => { const a = clip({ id: "a", start: 1, end: 4 }); expect(resolveMotionClipEnd(a, 10)).toBe(4); }); it("resolves an open-ended end to the timeline duration", () => { const a = clip({ id: "a", start: 1, end: Number.POSITIVE_INFINITY }); expect(resolveMotionClipEnd(a, 10)).toBe(10); }); });