// @vitest-environment jsdom // // The show's and the season's restart button. It said only "Play from start" — of what? (the owner, // 2026-09-27) — and it resumed the first episode at its saved point instead of at 0. Now it names its // object and the episode ("Season from the start · S6 · E1"), and it asks the player for 0. import { describe, it, expect, afterEach, beforeAll, vi } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; beforeAll(() => { class Stub { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver; }); vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), }; }); const ep = (id: string, season: number, episode: number, position = 0) => ({ id, type: "episode", title: `Ep ${id}`, season_number: season, episode_number: episode, duration_seconds: 1500, thumb: `/api/plex/image/${id}`, playable: "direct", status: position > 0 ? "in_progress" : "new", position_seconds: position, }); // The first episode is half-watched: a restart that resumed it would start at 600, not 0. const s1 = [ep("e11", 1, 1, 600), ep("e12", 1, 2)]; const s2 = [ep("e21", 2, 1, 300), ep("e22", 2, 2)]; const detail = { show: { id: "show", title: "Show", thumb: "", art: "", genres: [], cast: [], status: "in_progress", resume: s2[1], first: s1[0], episode_count: 4, collection_keys: [], }, seasons: [ { id: "s1", season_number: 1, title: "Season 1", thumb: "", episode_count: 2, status: "in_progress", resume: s1[1], first: s1[0], episodes: s1, }, { id: "s2", season_number: 2, title: "Season 2", thumb: "", episode_count: 2, status: "in_progress", resume: s2[1], first: s2[0], episodes: s2, }, ], related: [], }; vi.mock("../lib/api", () => ({ api: { plexShow: () => Promise.resolve(detail), plexSetState: () => Promise.resolve(), plexSeasonState: () => Promise.resolve(), }, EMPTY_PLEX_FILTERS: {}, plexFilterCount: () => 0, })); import { PlexSeasonView, PlexShowView } from "./plexViews"; afterEach(cleanup); function wrap(ui: React.ReactElement) { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render({ui}); } const ALL = ["e11", "e12", "e21", "e22"]; describe("the restart button on a show and a season", () => { it("a season's names the season and its first episode, and starts that episode at 0", async () => { const onPlay = vi.fn(); wrap( {}} onPlay={onPlay} onInfoCard={() => {}} />, ); const btn = await screen.findByRole("button", { name: /plex\.series\.seasonFromStart/ }); expect(btn.textContent).toContain("S2 · E1"); fireEvent.click(btn); expect(onPlay).toHaveBeenCalledWith("e21", ALL, true); }); it("a show's names the show and its first episode, and starts that episode at 0", async () => { const onPlay = vi.fn(); wrap( {}} onPlay={onPlay} onOpenSeason={() => {}} onOpenShow={() => {}} onPlayShow={() => {}} />, ); const btn = await screen.findByRole("button", { name: /plex\.series\.showFromStart/ }); expect(btn.textContent).toContain("S1 · E1"); fireEvent.click(btn); expect(onPlay).toHaveBeenCalledWith("e11", ALL, true); }); }); describe("the show page's summary", () => { it("is clamped with a More, so a long one can still be read in full", async () => { const tall = vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockReturnValue(400); const box = vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(60); Object.assign(detail.show, { summary: "A long summary.\n\nThat goes on." }); try { wrap( {}} onPlay={() => {}} onOpenSeason={() => {}} onOpenShow={() => {}} onPlayShow={() => {}} />, ); fireEvent.click(await screen.findByText("plex.textMore")); expect(screen.getByText("plex.textLess").getAttribute("aria-expanded")).toBe("true"); } finally { tall.mockRestore(); box.mockRestore(); Object.assign(detail.show, { summary: undefined }); } }); });