// @vitest-environment jsdom // // A reload reopens the item playing NOW (the owner, 2026-10-25): binge a series, press F5 mid-episode, // and the player restarted the episode the session started with — the player moved on in its own // state while the history entry still held the first item. The player says what it moved on to // (PlexPlayer's own tests); this is the page writing it into the entry and the address bar. 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, queue, onItemChange, }: { itemId: string; queue?: string[]; onItemChange: (id: string) => void; }) => (
player {itemId} queue {queue?.join(",") ?? "none"}
), })); vi.mock("./PlexPlaylistView", () => ({ default: ({ onPlay }: { onPlay: (rk: string, queue: string[]) => void }) => ( ), })); vi.mock("./plexSavedViews", async (importOriginal) => ({ ...(await importOriginal()), usePlexSavedViews: () => ({ views: [], displayView: null, isDirty: false, activeViewId: null }), })); import PlexBrowse from "./PlexBrowse"; import { api } 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(() => { vi.spyOn(api, "plexLibrary").mockResolvedValue({ items: [], total: 0, offset: 0, episodes: [], } as never); window.history.replaceState( { _sub: { kind: "player", id: "e1", queue: ["e1", "e2", "e3"] }, sfScroll: 4 }, "", "/?page=plex&v=player:e1", ); }); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); function mount() { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render( , ); } describe("the Plex player on a reload", () => { it("reopens the item it moved on to, with the list it plays from", async () => { mount(); expect(await screen.findByText("player e1 queue e1,e2,e3")).toBeTruthy(); const before = window.history.length; fireEvent.click(screen.getByText("next")); expect(screen.getByText("player e2 queue e1,e2,e3")).toBeTruthy(); expect(window.history.length).toBe(before); // moving on is no step for Back cleanup(); // F5: the page goes, the history entry stays mount(); expect(await screen.findByText("player e2 queue e1,e2,e3")).toBeTruthy(); }); it("says the item playing now in the address bar, so a bookmark opens it", async () => { mount(); fireEvent.click(await screen.findByText("next")); expect(new URLSearchParams(window.location.search).get("v")).toBe("player:e2"); cleanup(); window.history.replaceState(null, "", window.location.href); // a fresh tab: the URL alone mount(); expect(await screen.findByText("player e2 queue none")).toBeTruthy(); }); }); describe("the Plex player opened from a playlist", () => { const playlist = (ids: string[]) => vi.spyOn(api, "plexPlaylist").mockResolvedValue({ id: 7, title: "Road trip", items: ids.map((id) => ({ id, title: id })), } as never); it("names the playlist after the item, as it moves on too", async () => { window.history.replaceState(null, "", "/?page=plex&v=playlist:7"); mount(); fireEvent.click(await screen.findByText("play from the playlist")); expect(await screen.findByText("player e1 queue e1,e2")).toBeTruthy(); expect(new URLSearchParams(window.location.search).get("v")).toBe("player:e1:playlist:7"); fireEvent.click(screen.getByText("next")); expect(new URLSearchParams(window.location.search).get("v")).toBe("player:e2:playlist:7"); }); it("a bookmark plays on through the playlist, its order rebuilt from it", async () => { window.history.replaceState(null, "", "/?page=plex&v=player:e2:playlist:7"); const read = playlist(["e1", "e2", "e3"]); mount(); expect(await screen.findByText("player e2 queue e1,e2,e3")).toBeTruthy(); expect(read).toHaveBeenCalledWith(7, "en"); }); it("a bookmark whose item has left the playlist plays it on its own neighbours", async () => { window.history.replaceState(null, "", "/?page=plex&v=player:e9:playlist:7"); const read = playlist(["e1", "e2"]); mount(); await vi.waitFor(() => expect(read).toHaveBeenCalled()); await new Promise((r) => setTimeout(r, 20)); expect(screen.getByText("player e9 queue none")).toBeTruthy(); }); });