// @vitest-environment jsdom // // A video's ONE action list. The right-click and the card's "…" open the same menu, which // holds everything the card no longer shows (download, details, copy, hide, reset, pin) beside the // everyday toggles, and each row fires the same handler the card's own buttons do. import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { FloatingTree } from "@floating-ui/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { Video } from "../../lib/api"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }), }; }); vi.mock("../../lib/api", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, api: { ...actual.api, comfortPins: () => Promise.resolve({ items: [] }), downloadIndex: () => Promise.resolve({}), playlists: () => Promise.resolve([]), }, }; }); const copyText = vi.fn((_: string) => Promise.resolve(true)); vi.mock("../../lib/clipboard", () => ({ copyText: (s: string) => copyText(s) })); import { VideoMenu } from "./VideoMenu"; import { MoreButton } from "../../components/ui/MoreButton"; import { useContextMenu } from "../../components/ui/useContextMenu"; beforeAll(() => { class Stub { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver; globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver; }); afterEach(() => { cleanup(); vi.clearAllMocks(); }); // The surface is visibility:hidden in jsdom (floating-ui never positions), so an item has no // accessible name there: matched on its text, as ContextMenu.dom.test.tsx does. const labels = () => screen.queryAllByRole("menuitem", { hidden: true }).map((b) => b.textContent); const item = (label: string) => { const found = screen .queryAllByRole("menuitem", { hidden: true }) .find((b) => b.textContent === label); if (!found) throw new Error(`no menuitem "${label}" in [${labels().join(", ")}]`); return found; }; const video = (extra: Partial