// @vitest-environment jsdom // // Contract of the header library-scope pill (S3 relocation + Piece 2 browse modes): it renders the // three library scopes PLUS the Playlist/Collection browse-mode entries, marks the active one, writes a // picked scope to the provider, and drives the playlists/collections views through the PlexViewBridge. // i18n is key-passthrough and both the provider and the bridge are mocked; SegmentedToggle is a plain // button group (no floating-ui), so no observer stubs are needed. import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key }) }; }); const plexState = { scope: "both" } as { scope: string }; const setScope = vi.fn(); vi.mock("./PlexProvider", () => ({ usePlex: () => plexState, usePlexActions: () => ({ setScope }), })); // The bridge: a controllable current view kind + an open spy. const controls = { kind: "discover" as string, open: vi.fn() }; vi.mock("./PlexViewBridge", () => ({ usePlexViewControls: () => controls, })); import PlexScopeToggle from "./PlexScopeToggle"; beforeEach(() => { plexState.scope = "both"; controls.kind = "discover"; setScope.mockReset(); controls.open.mockReset(); }); afterEach(cleanup); const buttons = () => [ ...screen.getByRole("group", { name: "plex.filter.scope" }).querySelectorAll("button"), ]; const labels = () => buttons().map((b) => b.textContent); const pressed = () => buttons() .filter((b) => b.getAttribute("aria-pressed") === "true") .map((b) => b.textContent); describe("PlexScopeToggle", () => { it("renders the three scopes plus the two browse modes, the current scope pressed", () => { plexState.scope = "movie"; render(); expect(labels()).toEqual([ "plex.filter.scopeOpt.both", "plex.filter.scopeOpt.movie", "plex.filter.scopeOpt.show", "plex.filter.scopeOpt.playlists", "plex.filter.scopeOpt.collections", ]); expect(pressed()).toEqual(["plex.filter.scopeOpt.movie"]); }); it("writes a picked scope back to the provider (no view switch when already on a title view)", () => { render(); fireEvent.click(screen.getByRole("button", { name: "plex.filter.scopeOpt.show" })); expect(setScope).toHaveBeenCalledWith("show"); expect(controls.open).not.toHaveBeenCalled(); }); it("opens the playlists view (not a scope change) for the Playlist mode", () => { render(); fireEvent.click(screen.getByRole("button", { name: "plex.filter.scopeOpt.playlists" })); expect(controls.open).toHaveBeenCalledWith({ kind: "playlists" }); expect(setScope).not.toHaveBeenCalled(); }); it("marks the Collection mode active when the current view is collections", () => { controls.kind = "collections"; render(); expect(pressed()).toEqual(["plex.filter.scopeOpt.collections"]); }); it("returns to the title grid when a scope is picked from a browse mode", () => { controls.kind = "playlists"; render(); fireEvent.click(screen.getByRole("button", { name: "plex.filter.scopeOpt.movie" })); expect(setScope).toHaveBeenCalledWith("movie"); expect(controls.open).toHaveBeenCalledWith({ kind: "grid" }); }); });