/** * The list view must grey and offer to queue server-only media exactly as the * grid does. It did neither: switching a library to list view offline turned * every non-downloaded item back into a normal, tappable row that plays * nothing. * * TRACES: UR-052 | DR-292 | UT-258 */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/svelte"; const h = vi.hoisted(() => { function shim(initial: T) { let value = initial; const subs = new Set<(v: T) => void>(); return { set(v: T) { value = v; subs.forEach((fn) => fn(value)); }, subscribe(fn: (v: T) => void) { subs.add(fn); fn(value); return () => subs.delete(fn); }, }; } return { isConnectedStore: shim(true), showServerCatalogStore: shim(false), downloadsStore: shim({ downloads: {} as Record }), deviceContentIdsStore: shim(new Set()), downloadItem: vi.fn(async () => 1), getUserId: vi.fn(() => "user-1"), }; }); vi.mock("$lib/stores/connectivity", () => ({ isConnected: { subscribe: h.isConnectedStore.subscribe }, })); vi.mock("$lib/services/offlineCatalog", () => ({ showServerCatalog: { subscribe: h.showServerCatalogStore.subscribe }, })); vi.mock("$lib/services/downloadedCatalog", () => ({ deviceContentIds: { subscribe: h.deviceContentIdsStore.subscribe }, })); vi.mock("$lib/stores/downloads", () => ({ downloads: { subscribe: h.downloadsStore.subscribe, downloadItem: h.downloadItem }, })); vi.mock("$lib/stores/auth", () => ({ auth: { getUserId: h.getUserId }, })); vi.mock("$lib/components/common/CachedImage.svelte", async () => ({ default: (await import("./__mocks__/StubImage.svelte")).default, })); import LibraryListView from "./LibraryListView.svelte"; const movie = { id: "movie-1", name: "Some Film", type: "Movie" as const, serverId: "server-1", productionYear: 1999, }; const album = { id: "album-1", name: "Some Album", type: "MusicAlbum" as const, serverId: "server-1", }; describe("LibraryListView server-only rows", () => { beforeEach(() => { vi.clearAllMocks(); h.isConnectedStore.set(true); h.showServerCatalogStore.set(false); h.downloadsStore.set({ downloads: {} }); h.deviceContentIdsStore.set(new Set()); }); it("shows no queue control while online", () => { render(LibraryListView, { props: { items: [movie] } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("offers to queue a server-only row when offline with the reveal on", () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); render(LibraryListView, { props: { items: [movie] } }); expect(screen.getByLabelText(/Queue download for Some Film/i)).toBeTruthy(); }); it("queues the row's item on click", async () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); render(LibraryListView, { props: { items: [movie] } }); await fireEvent.click(screen.getByLabelText(/Queue download for Some Film/i)); expect(h.downloadItem).toHaveBeenCalledTimes(1); const args = h.downloadItem.mock.calls[0] as unknown as unknown[]; expect(args[0]).toBe("movie-1"); expect(args[1]).toBe("user-1"); }); it("makes a server-only row inert: tapping it cannot start playback", async () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); const onItemClick = vi.fn(); render(LibraryListView, { props: { items: [movie], onItemClick } }); await fireEvent.click(screen.getByText("Some Film")); expect(onItemClick).not.toHaveBeenCalled(); }); it("leaves a downloaded row alone", () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); h.downloadsStore.set({ downloads: { "movie-1": { itemId: "movie-1", status: "completed", progress: 1 } }, }); render(LibraryListView, { props: { items: [movie] } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("leaves a container alone when the device holds its children", () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); h.deviceContentIdsStore.set(new Set(["album-1"])); render(LibraryListView, { props: { items: [album] } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("shows a Queued badge instead of the button for a pending row", () => { h.isConnectedStore.set(false); h.showServerCatalogStore.set(true); h.downloadsStore.set({ downloads: { "movie-1": { itemId: "movie-1", status: "pending", progress: 0 } }, }); render(LibraryListView, { props: { items: [movie] } }); expect(screen.getByText(/Queued/i)).toBeTruthy(); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); });