import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import * as React from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; vi.mock("motion/react", async (importOriginal) => { const actual = await importOriginal(); type TestDragInfo = { velocity: { x: number; y: number } }; type TestMotionDivProps = Omit< React.HTMLAttributes, "onDragEnd" | "style" > & { animate?: unknown; drag?: unknown; dragControls?: unknown; dragElastic?: unknown; dragListener?: unknown; dragMomentum?: unknown; dragTransition?: unknown; initial?: unknown; onDragEnd?: (event: Event, info: TestDragInfo) => void; onDragStart?: () => void; style?: React.CSSProperties & { x?: { set: (value: number) => void }; y?: { set: (value: number) => void }; }; transition?: unknown; }; const MotionDiv = React.forwardRef( function MotionDiv( { animate: _animate, children, drag: _drag, dragControls: _dragControls, dragElastic: _dragElastic, dragListener: _dragListener, dragMomentum: _dragMomentum, dragTransition: _dragTransition, initial: _initial, onDragEnd, onDragStart, style, transition: _transition, ...props }, ref, ) { return (
{ const detail: unknown = Reflect.get(event.nativeEvent, "detail"); if ( typeof detail !== "object" || detail === null || !("x" in detail) || typeof detail.x !== "number" || !("y" in detail) || typeof detail.y !== "number" ) { throw new Error("Expected a numeric test drag position."); } style?.x?.set(detail.x); style?.y?.set(detail.y); onDragEnd?.(event.nativeEvent, { velocity: { x: 0, y: 0 }, }); }} onDragStart={onDragStart} ref={ref} > {children}
); }, ); return { ...actual, motion: { ...actual.motion, div: MotionDiv }, }; }); import { defineToolcraft } from "../../schema/define-toolcraft"; import { ToolcraftRoot } from "../app-shell/toolcraft-root"; import { useToolcraft } from "../app-shell/use-toolcraft"; import { ToolcraftPanelHost } from "./panel-host"; const schema = defineToolcraft({ canvas: { enabled: true }, panels: {} }); function ToolbarPlacementProbe(): React.JSX.Element { const { state } = useToolcraft(); const { offset, snapEdge } = state.panels.toolbar; return ( {offset.x},{offset.y},{snapEdge ?? "free"} ); } function renderToolbar(offset = { x: 0, y: 0 }) { return render(
Toolbar
, ); } afterEach(() => { cleanup(); window.localStorage.clear(); vi.restoreAllMocks(); }); describe("ToolcraftPanelHost runtime placement", () => { it("commits the final free drag position through runtime panel state", async () => { const { container } = renderToolbar(); const host = container.querySelector( "[data-slot='toolcraft-runtime-panel-host']", ); if (!host) { throw new Error("Expected the toolbar panel host to render."); } vi.spyOn(host, "getBoundingClientRect").mockReturnValue({ bottom: 300, height: 100, left: 200, right: 300, top: 200, width: 100, x: 200, y: 200, toJSON: () => ({}), }); fireEvent( host, new CustomEvent("dragend", { bubbles: true, detail: { x: 40, y: 80 }, }), ); await waitFor(() => { expect(screen.getByTestId("toolbar-placement").textContent).toBe( "40,80,free", ); }); }); it("clamps restored runtime placement after a viewport resize", async () => { Object.defineProperty(window, "innerWidth", { configurable: true, value: 800, }); Object.defineProperty(window, "innerHeight", { configurable: true, value: 500, }); const { container } = renderToolbar({ x: 900, y: -200 }); const host = container.querySelector( "[data-slot='toolcraft-runtime-panel-host']", ); if (!host) { throw new Error("Expected the toolbar panel host to render."); } vi.spyOn(host, "getBoundingClientRect").mockReturnValue({ bottom: -50, height: 100, left: 1000, right: 1200, top: -150, width: 200, x: 1000, y: -150, toJSON: () => ({}), }); fireEvent(window, new Event("resize")); await waitFor(() => { expect(screen.getByTestId("toolbar-placement").textContent).toBe( "490,-40,free", ); }); }); it("keeps the CSS-anchored default placement during viewport resize", async () => { const { container } = renderToolbar(); const host = container.querySelector( "[data-slot='toolcraft-runtime-panel-host']", ); if (!host) { throw new Error("Expected the toolbar panel host to render."); } vi.spyOn(host, "getBoundingClientRect").mockReturnValue({ bottom: 900, height: 100, left: 1_500, right: 1_700, top: 800, width: 200, x: 1_500, y: 800, toJSON: () => ({}), }); fireEvent(window, new Event("resize")); await waitFor(() => { expect(screen.getByTestId("toolbar-placement").textContent).toBe( "0,0,free", ); }); }); });