import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { ToolcraftRoot } from "../app-shell/toolcraft-root"; import { createTimelineSchema, installTimelinePanelTestEnvironment, renderTimelinePanel, } from "./timeline-panel-test-utils"; import { TimelinePanel } from "./timeline-panel"; installTimelinePanelTestEnvironment(); type RectFixture = { bottom: number; height: number; left: number; right: number; top: number; width: number; x?: number; y?: number; }; function makeRect({ bottom, height, left, right, top, width, x = left, y = top }: RectFixture) { return { bottom, height, left, right, toJSON: () => ({}), top, width, x, y, } as DOMRect; } const emptyRect = makeRect({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, }); describe("TimelinePanel floating layout", () => { it("delegates the whole timeline window size to motion during expand and collapse", () => { const { container } = renderTimelinePanel({ panelPlacement: "floating" }); const timelinePanel = container.querySelector('[data-slot="timeline-panel"]'); expect(timelinePanel?.style.width).toBe("256px"); expect(timelinePanel?.style.height).toBe("36px"); fireEvent.click(screen.getByRole("button", { name: "Expand timeline panel" })); expect(timelinePanel?.getAttribute("data-expanded-height")).toBe("110"); fireEvent.click(screen.getByRole("button", { name: "Collapse timeline panel" })); expect(timelinePanel?.getAttribute("data-expanded-height")).toBeNull(); }); it("uses the shared panel host when floating", () => { const { container } = renderTimelinePanel({ panelPlacement: "floating" }); expect(container.querySelector('[data-panel-type="timeline"]')).toBeTruthy(); expect(container.querySelector('[data-snap-edges="top bottom"]')).toBeTruthy(); }); it("moves the expanded floating timeline from the stable host position before narrowing", async () => { vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(function ( this: HTMLElement, ) { if (this.matches('[data-slot="toolcraft-runtime-app"]')) { return makeRect({ bottom: 720, height: 720, left: 0, right: 960, top: 0, width: 960, }); } if (this.matches('[data-panel-type="controls"]')) { return makeRect({ bottom: 560, height: 560, left: 10, right: 40, top: 10, width: 30, }); } if (this.matches('[data-panel-type="layers"]')) { return makeRect({ bottom: 560, height: 560, left: 760, right: 950, top: 10, width: 190, }); } if ( this.matches('[data-slot="toolcraft-runtime-panel-host"]') && this.matches('[data-panel-type="timeline"]') ) { return makeRect({ bottom: 128, height: 118, left: 136, right: 824, top: 10, width: 688, }); } if (this.matches('[data-slot="timeline-panel"]')) { return makeRect({ bottom: 128, height: 118, left: 99, right: 787, top: 10, width: 688, }); } return emptyRect; }); const { container } = render(
, ); const timelinePanel = container.querySelector('[data-slot="timeline-panel"]'); await waitFor(() => { expect(timelinePanel?.getAttribute("data-responsive-offset-x")).toBe("-74"); expect(timelinePanel?.getAttribute("data-responsive-width")).toBeNull(); }); }); it("narrows expanded floating timeline after it reaches both side panels", async () => { vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(function ( this: HTMLElement, ) { if (this.matches('[data-slot="toolcraft-runtime-app"]')) { return makeRect({ bottom: 720, height: 720, left: 0, right: 960, top: 0, width: 960, }); } if (this.matches('[data-panel-type="layers"]')) { return makeRect({ bottom: 560, height: 560, left: 10, right: 250, top: 10, width: 240, }); } if (this.matches('[data-panel-type="controls"]')) { return makeRect({ bottom: 560, height: 560, left: 650, right: 950, top: 10, width: 300, }); } if (this.matches('[data-slot="timeline-panel"]')) { return makeRect({ bottom: 128, height: 118, left: 136, right: 824, top: 10, width: 688, }); } return emptyRect; }); const { container } = render(
, ); const timelinePanel = container.querySelector('[data-slot="timeline-panel"]'); await waitFor(() => { expect(timelinePanel?.getAttribute("data-responsive-offset-x")).toBe("-30"); expect(timelinePanel?.getAttribute("data-responsive-width")).toBe("380"); }); }); it("keeps the expanded floating timeline inside the side-panel corridor immediately after resize", async () => { let isNarrow = false; vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(function ( this: HTMLElement, ) { if (this.matches('[data-slot="toolcraft-runtime-app"]')) { return makeRect({ bottom: 720, height: 720, left: 0, right: isNarrow ? 1024 : 1440, top: 0, width: isNarrow ? 1024 : 1440, }); } if (this.matches('[data-panel-type="layers"]')) { return makeRect({ bottom: 560, height: 560, left: 10, right: 250, top: 10, width: 240, }); } if (this.matches('[data-panel-type="controls"]')) { return makeRect({ bottom: 560, height: 560, left: isNarrow ? 714 : 1130, right: isNarrow ? 1014 : 1430, top: 10, width: 300, x: isNarrow ? 714 : 1130, }); } if ( this.matches('[data-slot="toolcraft-runtime-panel-host"]') && this.matches('[data-panel-type="timeline"]') ) { return makeRect({ bottom: 128, height: 118, left: isNarrow ? 384 : 592, right: isNarrow ? 640 : 848, top: 10, width: 256, x: isNarrow ? 384 : 592, }); } if (this.matches('[data-slot="timeline-panel"]')) { return makeRect({ bottom: 128, height: 118, left: isNarrow ? 384 : 592, right: isNarrow ? 640 : 848, top: 10, width: 256, x: isNarrow ? 384 : 592, }); } return emptyRect; }); const { container } = render(
, ); const timelinePanel = container.querySelector('[data-slot="timeline-panel"]'); await waitFor(() => { expect(timelinePanel?.getAttribute("data-responsive-width")).toBeNull(); expect(timelinePanel?.getAttribute("data-responsive-offset-x")).toBeNull(); }); isNarrow = true; fireEvent(window, new Event("resize")); await waitFor(() => { expect(timelinePanel?.getAttribute("data-responsive-width")).toBe("444"); expect(timelinePanel?.getAttribute("data-responsive-offset-x")).toBe("-30"); }); }); });