// @vitest-environment jsdom // // Contract of a video's details panel (opened from the video's menu): the // header (full title + channel) renders IMMEDIATELY from the Video we already hold, while the // description (videoDetail) and the channel's tags (channelDetail.tag_ids resolved against the tag // list) fill in from their on-demand queries; an empty description falls back to the "no description" // line. i18n is key-passthrough and the AnchoredPopover platform is floating-ui, so we stub the observers autoUpdate wires and wrap in a FloatingTree + QueryClient. import { describe, it, expect, afterEach, beforeAll, vi } from "vitest"; import { render, screen, cleanup } from "@testing-library/react"; import { FloatingTree } from "@floating-ui/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }), }; }); const videoDetail = vi.fn(); const channelDetail = vi.fn(); const tags = vi.fn(); vi.mock("../../lib/api", () => ({ api: { videoDetail: (...a: unknown[]) => videoDetail(...a), channelDetail: (...a: unknown[]) => channelDetail(...a), tags: (...a: unknown[]) => tags(...a), }, })); import VideoDetailsAt from "./VideoInfoCard"; import type { Video } from "../../lib/api"; 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(); }); const video = (over: Partial