import { describe, expect, it } from "vitest"; import { createSchema, defineToolcraft, fireEvent, renderControlsPanel, renderControlsPanelWithSchema, screen, waitFor, } from "../testing/controls-panel-test-utils"; describe("ControlsPanel keyframe controls", () => { it("renders keyframe actions for timeline-expanded keyframe-capable controls", () => { renderControlsPanel(undefined, { timeline: { expanded: true, keyframeGroups: [] } }); expect(screen.getByRole("button", { name: "Add Anchor keyframe" })).toBeTruthy(); expect(screen.getByRole("button", { name: "Add Opacity keyframe" })).toBeTruthy(); expect(screen.getByRole("button", { name: "Add Static opacity keyframe" })).toBeTruthy(); expect(screen.getByRole("button", { name: "Add Output Mix keyframe" })).toBeTruthy(); expect(screen.getByRole("button", { name: "Add Curves keyframe" })).toBeTruthy(); expect(screen.queryByRole("button", { name: "Add Prompt keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Blend keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Channels keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Enabled keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Canvas width keyframe" })).toBeNull(); expect( screen .getByRole("button", { name: "Add Opacity keyframe" }) .closest("[data-control-field-label]"), ).toBeTruthy(); expect( screen .getByRole("button", { name: "Add Output Mix keyframe" }) .closest("[data-control-field-label]"), ).toBeTruthy(); expect( screen .getByRole("button", { name: "Add Curves keyframe" }) .closest("[data-control-field-label]"), ).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Add Opacity keyframe" })); expect(screen.getByTestId("timeline-expanded").textContent).toBe("true"); const opacityKeyframeButton = screen.getByRole("button", { name: "Disable Opacity keyframes", }); expect(opacityKeyframeButton.className).toContain( "!text-[color:var(--link)]", ); expect(opacityKeyframeButton.className).toContain( "data-popup-open:!text-[color:var(--link)]", ); expect(opacityKeyframeButton.className).toContain( "[&_svg]:!fill-[color:var(--link)]", ); expect(opacityKeyframeButton.style.color).toBe("var(--link)"); expect(screen.getByTestId("timeline-keyframes").textContent).toContain( '"controlId":"selectedLayer.opacity"', ); expect(screen.getByTestId("timeline-keyframes").textContent).toContain( '"valueLabel":"75%"', ); expect(screen.getByTestId("timeline-keyframes").textContent).toContain('"value":75'); expect(screen.queryByRole("button", { name: "Add Prompt keyframe" })).toBeNull(); }); it("updates the selected control keyframe instead of the current playhead time", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { range: { defaultValue: { end: "100%", start: "0%" }, label: "Range", target: "shape.range", type: "rangeInput", }, }, }, ], title: "Controls", }, timeline: true, }, }); renderControlsPanelWithSchema(schema, {}, { timeline: { currentTimeSeconds: 4, expanded: true, keyframeGroups: [ { controlId: "shape.range", keyframes: [ { controlId: "shape.range", controlLabel: "Range", id: "shape.range::2", timeSeconds: 2, value: { end: "100%", start: "0%" }, valueLabel: "0% - 100%", }, ], label: "Range", }, ], selectedKeyframeId: "shape.range::2", }, }); fireEvent.change(screen.getByLabelText("Range start"), { target: { value: "25%" }, }); fireEvent.blur(screen.getByLabelText("Range start")); const keyframeGroups = JSON.parse( screen.getByTestId("timeline-keyframes").textContent ?? "[]", ) as Array<{ keyframes: Array<{ id: string; timeSeconds: number; value: { end: string; start: string } }>; }>; expect(keyframeGroups[0]?.keyframes).toHaveLength(1); expect(keyframeGroups[0]?.keyframes[0]).toMatchObject({ id: "shape.range::2", timeSeconds: 2, value: { end: "100%", start: "25%" }, }); }); it("keeps keyframe actions hidden while the timeline is collapsed", () => { renderControlsPanel(); expect(screen.getByTestId("timeline-expanded").textContent).toBe("false"); expect(screen.queryByRole("button", { name: "Add Prompt keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Opacity keyframe" })).toBeNull(); }); it("keeps keyframe actions hidden for playback-only timeline apps", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: createSchema().panels.controls, timeline: { mode: "playback" }, }, }); renderControlsPanelWithSchema(schema, undefined, { timeline: { expanded: true, keyframeGroups: [] }, }); expect(screen.getByTestId("timeline-expanded").textContent).toBe("true"); expect(screen.queryByRole("button", { name: "Add Prompt keyframe" })).toBeNull(); expect(screen.queryByRole("button", { name: "Add Opacity keyframe" })).toBeNull(); }); it("suppresses control transitions when keyframe controls appear", async () => { const { container } = renderControlsPanel(); const content = () => container.querySelector('[data-slot="toolcraft-panel-content"]'); await waitFor(() => { expect(content()?.getAttribute("data-toolcraft-controls-mounting")).toBeNull(); }); fireEvent.click(screen.getByRole("button", { name: "Expand timeline" })); expect(content()?.getAttribute("data-toolcraft-controls-mounting")).toBe("true"); }); });