// @vitest-environment jsdom // // A show's card in the Browse grid plays (the owner, 2026-10-09): its picture asks the server which // episode and opens the player on it; a started show's Restart opens it from the start; its title // opens the show's page. The hook is tested on its own (usePlayShow); this is the grid wiring it. import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; vi.mock("react-i18next", async (importOriginal) => ({ ...(await importOriginal()), useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }), })); vi.mock("./PlexPlayer", () => ({ default: ({ itemId, fromStart }: { itemId: string; fromStart?: boolean }) => (
player {itemId} {fromStart ? "from the start" : "resumed"}
), })); vi.mock("./plexViews", async (importOriginal) => ({ ...(await importOriginal()), PlexShowView: ({ showId }: { showId: string }) =>
show page {showId}
, PlexInfoView: ({ id }: { id: string }) =>
info page {id}
, })); import PlexBrowse from "./PlexBrowse"; import { api, type PlexCard } 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; }); beforeEach(() => window.history.replaceState({ _sub: { kind: "grid" } }, "")); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); const show: PlexCard = { id: "sh", type: "show", title: "Grimm", thumb: "", status: "in_progress", resumable: true, }; function mount(episodes: PlexCard[] = []) { vi.spyOn(api, "plexLibrary").mockResolvedValue({ items: [show], total: 1, offset: 0, episodes, } as never); const next = vi.spyOn(api, "plexShowNext").mockResolvedValue({ next: { id: "e7", type: "episode", title: "Ep 7", thumb: "" }, }); const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render( , ); return { next }; } describe("PlexBrowse — a show's card in the grid", () => { it("plays the episode the server names", async () => { const { next } = mount(); fireEvent.click(await screen.findByRole("button", { name: "card.continue" })); expect(await screen.findByText("player e7 resumed")).toBeTruthy(); expect(next).toHaveBeenCalledWith("sh", "en"); }); it("starts that episode over from its Restart", async () => { mount(); fireEvent.click(await screen.findByRole("button", { name: "card.restart" })); expect(await screen.findByText("player e7 from the start")).toBeTruthy(); }); it("opens the show's page from its title, asking nothing", async () => { const { next } = mount(); fireEvent.click(await screen.findByText("Grimm"), { detail: 1 }); expect(await screen.findByText("show page sh")).toBeTruthy(); expect(next).not.toHaveBeenCalled(); }); // A matched episode's title opens its page, a pointer's door: its menu offers the same to the keyboard. it("opens a matched episode's page from its menu, as its title does", async () => { mount([{ id: "ep1", type: "episode", title: "Pilot", thumb: "", show_title: "Grimm" }]); const section = (await screen.findByText("plex.unified.episodes")).parentElement!; fireEvent.click(section.querySelector("button[aria-haspopup]")!); const rows = await screen.findAllByRole("menuitem", { hidden: true }); fireEvent.click(rows.find((r) => r.textContent === "plex.info.title")!); expect(await screen.findByText("info page ep1")).toBeTruthy(); }); it("opens the show's page from its ⓘ, asking nothing", async () => { const { next } = mount(); fireEvent.click(await screen.findByRole("button", { name: "plex.openShow" })); expect(await screen.findByText("show page sh")).toBeTruthy(); expect(next).not.toHaveBeenCalled(); }); });