// @vitest-environment jsdom // // A shared Plex link carries the collection filter as bare rating_keys — `?pf=collections%3D17045` // says nothing about what 17045 is called. Opened cold, the sidebar token rendered that raw id and // the applied-filter chip could only say "Collection". This hook resolves the missing ones. // // Two things it must NOT do: ask the server for a title the filter state already carries (the common // case — the user picked the collection in the sidebar), and pull the whole collection list to find // one name (431 collections / ~200 KB on the owner's library). import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderHook, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createElement, type ReactNode } from "react"; const plexCollections = vi.fn(); vi.mock("./api", () => ({ api: { plexCollections: (...a: unknown[]) => plexCollections(...a) } })); import { usePlexCollectionTitles } from "./usePlexCollectionTitles"; const wrap = () => { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return ({ children }: { children: ReactNode }) => createElement(QueryClientProvider, { client: qc }, children); }; const CATALOG: Record = { "17045": "Pirates of the Caribbean Collection", "68231": "IMDb Top 250", }; beforeEach(() => { plexCollections.mockReset(); // Answer like the endpoint does: only the requested keys come back. plexCollections.mockImplementation((_lib: unknown, _q: unknown, keys: string[]) => Promise.resolve({ collections: (keys ?? []).map((id) => ({ id, title: CATALOG[id] })), }), ); }); describe("usePlexCollectionTitles", () => { it("resolves the ids the filter state has no title for", async () => { const { result } = renderHook(() => usePlexCollectionTitles(["17045"], undefined), { wrapper: wrap(), }); await waitFor(() => expect(result.current.titles["17045"]).toBe("Pirates of the Caribbean Collection"), ); // Only the missing ids are asked for — nothing else. expect(plexCollections).toHaveBeenCalledWith(undefined, undefined, ["17045"]); }); it("asks for nothing when every id already has a title", async () => { const known = { "17045": "Pirates of the Caribbean Collection" }; const { result } = renderHook(() => usePlexCollectionTitles(["17045"], known), { wrapper: wrap(), }); expect(result.current.titles["17045"]).toBe("Pirates of the Caribbean Collection"); // Give a would-be fetch a chance to happen before asserting it did not. await new Promise((r) => setTimeout(r, 20)); expect(plexCollections).not.toHaveBeenCalled(); }); it("asks only for the missing half of a mixed selection", async () => { const { result } = renderHook( () => usePlexCollectionTitles(["17045", "68231"], { "17045": "Already Known" }), { wrapper: wrap() }, ); await waitFor(() => expect(result.current.titles["68231"]).toBe("IMDb Top 250")); // The known id is not in the request — and its stored title survives the merge. expect(plexCollections).toHaveBeenCalledWith(undefined, undefined, ["68231"]); expect(result.current.titles["17045"]).toBe("Already Known"); }); it("treats the same ids in a different order as one query", async () => { const w = wrap(); const a = renderHook(() => usePlexCollectionTitles(["68231", "17045"], undefined), { wrapper: w, }); await waitFor(() => expect(a.result.current.titles["17045"]).toBeDefined()); const calls = plexCollections.mock.calls.length; const b = renderHook(() => usePlexCollectionTitles(["17045", "68231"], undefined), { wrapper: w, }); await waitFor(() => expect(b.result.current.titles["17045"]).toBeDefined()); expect(plexCollections.mock.calls.length).toBe(calls); }); it("returns nothing extra while the lookup is still in flight", () => { plexCollections.mockReturnValue(new Promise(() => {})); const { result } = renderHook(() => usePlexCollectionTitles(["17045"], undefined), { wrapper: wrap(), }); // The caller falls back to its own label; it must not be handed a half-built map — and it is // told the names are on their way, so it draws a placeholder rather than the raw id. expect(result.current.titles["17045"]).toBeUndefined(); expect(result.current.pending).toBe(true); }); it("is not pending when nothing is missing, nor once the lookup answered", async () => { const known = renderHook(() => usePlexCollectionTitles(["17045"], { "17045": "Known" }), { wrapper: wrap(), }); expect(known.result.current.pending).toBe(false); const cold = renderHook(() => usePlexCollectionTitles(["17045"], undefined), { wrapper: wrap(), }); await waitFor(() => expect(cold.result.current.pending).toBe(false)); expect(cold.result.current.titles["17045"]).toBe("Pirates of the Caribbean Collection"); }); });