import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/svelte"; // Controllable stores for the offline "server only" branch. Declared via // vi.hoisted so they exist when the hoisted vi.mock factories run. A tiny // writable shim avoids importing svelte inside the hoisted block. 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 }), downloadItem: vi.fn(async () => 1), getUserId: vi.fn(() => "user-1"), }; }); const { isConnectedStore, showServerCatalogStore, downloadsStore, downloadItem, getUserId } = h; vi.mock("$lib/stores/connectivity", () => ({ isConnected: { subscribe: h.isConnectedStore.subscribe }, })); vi.mock("$lib/services/offlineCatalog", () => ({ showServerCatalog: { subscribe: h.showServerCatalogStore.subscribe }, })); vi.mock("$lib/stores/downloads", () => ({ downloads: { subscribe: h.downloadsStore.subscribe, downloadItem: h.downloadItem }, })); vi.mock("$lib/stores/auth", () => ({ auth: { getUserId: h.getUserId }, })); // CachedImage does async repo/image work irrelevant to these tests. vi.mock("$lib/components/common/CachedImage.svelte", async () => ({ default: (await import("./__mocks__/StubImage.svelte")).default, })); import MediaCard from "./MediaCard.svelte"; const track = { id: "track-1", name: "Some Song", type: "Audio" as const, serverId: "server-1", artists: ["Artist A"], albumName: "Album X", }; describe("MediaCard server-only (offline browse & queue)", () => { beforeEach(() => { vi.clearAllMocks(); isConnectedStore.set(true); showServerCatalogStore.set(false); downloadsStore.set({ downloads: {} }); }); it("shows no queue button while online", () => { render(MediaCard, { props: { item: track } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("shows no queue button when offline but the reveal toggle is off", () => { isConnectedStore.set(false); render(MediaCard, { props: { item: track } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("reveals a queue button when offline and the reveal toggle is on", () => { isConnectedStore.set(false); showServerCatalogStore.set(true); render(MediaCard, { props: { item: track } }); expect(screen.getByLabelText(/Queue download for Some Song/i)).toBeTruthy(); }); it("queues the item for download with its metadata on click", async () => { isConnectedStore.set(false); showServerCatalogStore.set(true); render(MediaCard, { props: { item: track } }); await fireEvent.click(screen.getByLabelText(/Queue download for Some Song/i)); expect(downloadItem).toHaveBeenCalledTimes(1); const args = downloadItem.mock.calls[0] as unknown as any[]; expect(args[0]).toBe("track-1"); // itemId expect(args[1]).toBe("user-1"); // userId expect(args[5]).toBe("Some Song"); // itemName expect(args[6]).toBe("Artist A"); // artistName expect(args[7]).toBe("Album X"); // albumName }); it("shows a Queued badge (not the button) for a pending download", () => { isConnectedStore.set(false); showServerCatalogStore.set(true); downloadsStore.set({ downloads: { "track-1": { itemId: "track-1", status: "pending", progress: 0 } }, }); render(MediaCard, { props: { item: track } }); expect(screen.getByText(/Queued/i)).toBeTruthy(); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); it("does not grey out a completed download", () => { isConnectedStore.set(false); showServerCatalogStore.set(true); downloadsStore.set({ downloads: { "track-1": { itemId: "track-1", status: "completed", progress: 1 } }, }); render(MediaCard, { props: { item: track } }); expect(screen.queryByLabelText(/Queue download/i)).toBeNull(); }); });