// @vitest-environment jsdom // // E5 S5: the enriched Plex detail page. Covers the TMDB-fed pieces added on the page variant — the // multi-source ratings row (IMDb / TMDb / audience), the facts panel (budget / revenue / studios / // countries), the keyword chips (→ onFilter), and the full crew (→ onOpenPerson). i18n is // key-passthrough; PlexInfo is prop-driven (no queries) so it just needs a QueryClient for its cache // reads. The overlay variant is asserted to stay lean (no facts/keywords/crew). import { describe, it, expect, afterEach, vi } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string, opts?: Record) => opts?.name ? `${key}:${opts.name}` : key, }), }; }); import PlexInfo from "./PlexInfo"; import type { PlexItemDetail } from "../lib/api"; afterEach(cleanup); const detail: PlexItemDetail = { id: "m1", kind: "movie", title: "Test Movie", summary: "A summary.", year: 2010, duration_seconds: 7200, playable: "direct", thumb: "/api/plex/image/m1", art: "/api/plex/image/m1?variant=art", cast: [], imdb_rating: 7.8, imdb_id: "tt1", imdb_url: "https://www.imdb.com/title/tt1", rating: 8.4, // Plex audience content_rating: "R", genres: ["Crime"], directors: ["Plex Director"], studio: "Fallback Studio", tagline: "A tagline.", markers: [], audio_streams: [], subtitle_streams: [], status: "new", position_seconds: 0, tmdb: { tmdb_id: 42, rating: 7.2, vote_count: 1234, budget: 160000000, revenue: 830000000, keywords: ["heist", "bank robbery"], crew: [ { name: "Jane Director", job: "Director" }, { name: "Sam Writer", job: "Writer" }, ], studios: ["Studio A", "Studio B"], production_countries: ["United States"], recommendations: [], backdrops: [], }, }; function wrap(ui: React.ReactElement) { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render({ui}); } describe("PlexInfo enriched detail (page)", () => { it("renders the multi-source ratings row (IMDb / TMDb / audience)", () => { wrap(); expect(screen.getByText("IMDb")).toBeTruthy(); expect(screen.getByText("7.8")).toBeTruthy(); expect(screen.getByText(/TMDb/)).toBeTruthy(); // "TMDb · 1.2K" (vote count compacted) expect(screen.getByText("7.2")).toBeTruthy(); expect(screen.getByText("plex.info.audience")).toBeTruthy(); expect(screen.getByText("8.4")).toBeTruthy(); }); it("renders the facts panel with budget / revenue / studios / countries", () => { wrap(); expect(screen.getByText("plex.info.facts.title")).toBeTruthy(); expect(screen.getByText("plex.info.facts.budget")).toBeTruthy(); expect(screen.getByText("$160M")).toBeTruthy(); expect(screen.getByText("$830M")).toBeTruthy(); expect(screen.getByText("Studio A")).toBeTruthy(); // tmdb.studios preferred over the fallback expect(screen.getByText("United States")).toBeTruthy(); }); it("keyword chips filter the library on click", () => { const onFilter = vi.fn(); wrap(); fireEvent.click(screen.getByText("heist")); expect(onFilter).toHaveBeenCalledWith({ keywords: ["heist"] }); }); it("crew members open the person page on click", () => { const onOpenPerson = vi.fn(); wrap(); expect(screen.getByText("plex.info.crew")).toBeTruthy(); fireEvent.click(screen.getByText("Jane Director")); expect(onOpenPerson).toHaveBeenCalledWith("Jane Director"); }); it("RatingsRow: with no IMDb score, the audience score becomes the filterable one", () => { const onFilter = vi.fn(); const noImdb = { ...detail, imdb_rating: null, imdb_id: null, imdb_url: null }; wrap(); expect(screen.queryByText("IMDb")).toBeNull(); // audience (8.4) is now the primary/filterable score → clicking it filters ratingMin. fireEvent.click(screen.getByText("8.4")); expect(onFilter).toHaveBeenCalledWith({ ratingMin: 8 }); }); it("RatingsRow: suppresses the duplicate audience chip when it equals the IMDb score", () => { const dupe = { ...detail, rating: 7.8 }; // audience == imdb_rating (7.8) wrap(); expect(screen.getByText("IMDb")).toBeTruthy(); expect(screen.queryByText("plex.info.audience")).toBeNull(); // duplicate audience chip suppressed expect(screen.getAllByText("7.8")).toHaveLength(1); // the score shows once (IMDb only) }); it("the overlay variant stays lean (no facts / keywords / crew panels)", () => { wrap(); expect(screen.queryByText("plex.info.facts.title")).toBeNull(); expect(screen.queryByText("plex.info.keywords")).toBeNull(); expect(screen.queryByText("plex.info.crew")).toBeNull(); // but the ratings row still renders (it's compact enough for the overlay) expect(screen.getByText("IMDb")).toBeTruthy(); }); });