Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions tests/clip-utils.test.ts
Original file line number Diff line number Diff line change
@@ -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> = {}): 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<Clip>))).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<Clip>))).toBe(
false
);
});
});
8 changes: 6 additions & 2 deletions tests/svg-toolbar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,10 @@ describe("SvgToolbar - Data Flow Integrity", () => {
it("keeps document timing intent in slider undo history", () => {
const mockEdit = createMockEditSession();
const svgClip = createSvgClip('<svg viewBox="0 0 100 100"><rect width="50" height="50"/></svg>');
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
Expand All @@ -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", () => {
Expand Down
Loading