From 009ba138ed737285219e1b59d60aec89f1c09de1 Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Tue, 11 Aug 2026 22:11:11 +1000 Subject: [PATCH] test: cover keyframe value predicates and assert the committed slider state --- tests/clip-utils.test.ts | 46 +++++++++++++++++++++++++++++++++++++++ tests/svg-toolbar.test.ts | 8 +++++-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tests/clip-utils.test.ts diff --git a/tests/clip-utils.test.ts b/tests/clip-utils.test.ts new file mode 100644 index 00000000..15de182f --- /dev/null +++ b/tests/clip-utils.test.ts @@ -0,0 +1,46 @@ +import { hasKeyframedVisualProperty, isKeyframedValue } from "@core/shared/clip-utils"; +import type { Clip } from "@schemas"; + +const tween = [{ from: 0, to: 1, start: 0, length: 1, interpolation: "linear" as const }]; + +function clip(overrides: Partial = {}): Clip { + return { asset: { type: "image", src: "https://example.com/a.jpg" }, start: 0, length: 5, ...overrides } as Clip; +} + +describe("isKeyframedValue", () => { + it("accepts plain numbers as editable, including zero", () => { + expect(isKeyframedValue(0)).toBe(false); + expect(isKeyframedValue(0.5)).toBe(false); + expect(isKeyframedValue(undefined)).toBe(false); + }); + + it("rejects keyframes and unresolved merge placeholders", () => { + expect(isKeyframedValue(tween)).toBe(true); + expect(isKeyframedValue("{{ MEDIA_OPACITY }}")).toBe(true); + }); +}); + +describe("hasKeyframedVisualProperty", () => { + it("is false for a clip whose visual properties are all fixed or absent", () => { + expect(hasKeyframedVisualProperty(clip())).toBe(false); + expect(hasKeyframedVisualProperty(clip({ opacity: 0, scale: 1 }))).toBe(false); + }); + + it.each([ + ["opacity", { opacity: tween }], + ["scale", { scale: tween }], + ["offset.x", { offset: { x: tween } }], + ["offset.y", { offset: { y: tween } }], + ["rotation", { transform: { rotate: { angle: tween } } }], + ["skew.x", { transform: { skew: { x: tween } } }], + ["skew.y", { transform: { skew: { y: tween } } }] + ])("is true when %s is keyframed", (_name, overrides) => { + expect(hasKeyframedVisualProperty(clip(overrides as Partial))).toBe(true); + }); + + it("ignores volume, which is asset-level and takes no part in preset composition", () => { + expect(hasKeyframedVisualProperty(clip({ asset: { type: "video", src: "https://example.com/a.mp4", volume: tween } } as Partial))).toBe( + false + ); + }); +}); diff --git a/tests/svg-toolbar.test.ts b/tests/svg-toolbar.test.ts index 64a1d06b..7f03da5b 100644 --- a/tests/svg-toolbar.test.ts +++ b/tests/svg-toolbar.test.ts @@ -529,8 +529,10 @@ describe("SvgToolbar - Data Flow Integrity", () => { it("keeps document timing intent in slider undo history", () => { const mockEdit = createMockEditSession(); const svgClip = createSvgClip(''); + const documentClip = { ...svgClip, start: "auto", length: "end" }; mockEdit.getResolvedClip.mockReturnValue(svgClip); - mockEdit.getDocumentClip.mockReturnValue({ ...svgClip, start: "auto", length: "end" }); + mockEdit.getDocumentClip.mockReturnValue(documentClip); + mockEdit.updateClipInDocument.mockImplementation((_clipId: string, updates: object) => Object.assign(documentClip, updates)); const { toolbar, parent } = createToolbar(mockEdit); // @ts-expect-error - accessing protected method for testing @@ -544,7 +546,9 @@ describe("SvgToolbar - Data Flow Integrity", () => { const [, initialState, finalState] = mockEdit.commitClipUpdate.mock.calls[0]; expect(initialState).toEqual(expect.objectContaining({ id: "clip-123", start: "auto", length: "end" })); - expect(finalState).toEqual(expect.objectContaining({ id: "clip-123", start: "auto", length: "end" })); + expect(initialState).not.toHaveProperty("opacity"); + // Final state must carry the edit AND still be document form, not resolved. + expect(finalState).toEqual(expect.objectContaining({ id: "clip-123", start: "auto", length: "end", opacity: 0.5 })); }); it("reads from edit session as single source of truth", () => {