// @vitest-environment happy-dom import React, { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, describe, expect, it, vi } from "vitest"; import { usePlayerStore, type TimelineElement } from "../../player/store/playerStore"; import { useAssetPreviewStore } from "../../utils/assetPreviewStore"; import { AssetCard } from "./AssetCard"; import { AudioRow } from "./AudioRow"; (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; let root: Root | null = null; afterEach(() => { if (root) { act(() => root?.unmount()); root = null; } document.body.innerHTML = ""; usePlayerStore.getState().reset(); useAssetPreviewStore.getState().clearPreviewAsset(); vi.restoreAllMocks(); }); function mount(node: React.ReactElement): HTMLElement { const host = document.createElement("div"); document.body.append(host); root = createRoot(host); act(() => { root?.render(node); }); return host; } function clip(input: Partial & { id: string; src: string }): TimelineElement { return { tag: "div", start: 0, duration: 5, track: 0, ...input }; } /** Simulate a drag-free click: pointerdown + pointerup at the same point. */ function clickCard(host: HTMLElement): void { const card = host.querySelector('[draggable="true"]'); if (!card) throw new Error("Expected a draggable card root"); const PointerCtor = (window as { PointerEvent?: typeof MouseEvent }).PointerEvent ?? MouseEvent; act(() => { card.dispatchEvent(new PointerCtor("pointerdown", { bubbles: true, clientX: 5, clientY: 5 })); card.dispatchEvent(new PointerCtor("pointerup", { bubbles: true, clientX: 5, clientY: 5 })); }); } describe("AssetCard click behavior", () => { const cardProps = { projectId: "p1", onCopy: vi.fn(), isCopied: false, }; it("clears an open preview overlay when clicking an already-added asset (reveal branch)", () => { usePlayerStore.getState().setElements([clip({ id: "img1", src: "assets/logo.png" })]); // Preview overlay is open on ANOTHER asset — the reveal must dismiss it, // or it stays stuck over the canvas while the timeline reveals the clip. useAssetPreviewStore.getState().setPreviewAsset("assets/other.png", "p1"); const host = mount(); clickCard(host); expect(useAssetPreviewStore.getState().previewAsset).toBeNull(); expect(usePlayerStore.getState().selectedElementId).toBe("img1"); }); it("opens the preview overlay for a not-yet-added asset", () => { const host = mount(); clickCard(host); expect(useAssetPreviewStore.getState().previewAsset).toBe("assets/logo.png"); expect(useAssetPreviewStore.getState().previewProjectId).toBe("p1"); }); }); describe("AudioRow click behavior", () => { const rowProps = { projectId: "p1", onCopy: vi.fn(), isCopied: false, }; it("clears an open preview overlay when clicking an already-added audio asset (reveal branch)", () => { usePlayerStore .getState() .setElements([clip({ id: "bgm1", tag: "audio", src: "assets/bgm.mp3" })]); useAssetPreviewStore.getState().setPreviewAsset("assets/other.mp3", "p1"); const host = mount(); clickCard(host); expect(useAssetPreviewStore.getState().previewAsset).toBeNull(); expect(usePlayerStore.getState().selectedElementId).toBe("bgm1"); }); it("opens the preview overlay for a not-yet-added audio asset", () => { const host = mount(); clickCard(host); expect(useAssetPreviewStore.getState().previewAsset).toBe("assets/bgm.mp3"); }); });