// @vitest-environment jsdom import "@testing-library/jest-dom/vitest"; import { cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createTestAdapters } from "../../__tests__/adapters/in-memory-adapters.js"; import { AdapterProvider } from "../../contexts/adapter-context.js"; import { BulkImportPrompt } from "../bulk-import-prompt.js"; const THREE_SOURCES_REGEX = /3 sources/; const GITHUB_URL_REGEX = /raw\.githubusercontent/; const LOADING_PROGRESS_REGEX = /Loading sources\.\.\. 4\/10/; const SEVEN_OF_TEN_REGEX = /7\/10 sources/; const THREE_FAILED_REGEX = /3 failed/; afterEach(cleanup); const mockFetchAndCacheSource = vi.fn(); const mockIsSourceCached = vi.fn().mockResolvedValue(false); const mockRefreshCache = vi.fn(); const mockStartImport = vi.fn(); const mockReset = vi.fn(); const mockDismissPanel = vi.fn(); let mockImportState = { status: "idle" as string, total: 0, completed: 0, failed: 0, }; // Uses context mocks because the bulk import state machine (idle → loading → // complete → partial-failure) is impractical to drive through user interactions // without real network calls. Consider migrating if adapter injection expands // to cover these state transitions. vi.mock("../../contexts/bestiary-context.js", () => ({ useBestiaryContext: () => ({ fetchAndCacheSource: mockFetchAndCacheSource, isSourceCached: mockIsSourceCached, refreshCache: mockRefreshCache, }), })); vi.mock("../../contexts/bulk-import-context.js", () => ({ useBulkImportContext: () => ({ state: mockImportState, startImport: mockStartImport, reset: mockReset, }), })); vi.mock("../../contexts/side-panel-context.js", () => ({ useSidePanelContext: () => ({ dismissPanel: mockDismissPanel, }), })); function createAdaptersWithSources() { const adapters = createTestAdapters(); adapters.bestiaryIndex = { ...adapters.bestiaryIndex, getAllSourceCodes: () => ["MM", "VGM", "XGE"], }; return adapters; } function renderWithAdapters() { const adapters = createAdaptersWithSources(); return render( , ); } describe("BulkImportPrompt", () => { afterEach(() => { vi.clearAllMocks(); mockImportState = { status: "idle", total: 0, completed: 0, failed: 0 }; }); it("idle: shows base URL input, source count, Load All button", () => { renderWithAdapters(); expect(screen.getByText(THREE_SOURCES_REGEX)).toBeInTheDocument(); expect(screen.getByDisplayValue(GITHUB_URL_REGEX)).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Load All" }), ).toBeInTheDocument(); }); it("idle: clearing URL disables the button", async () => { const user = userEvent.setup(); renderWithAdapters(); const input = screen.getByDisplayValue(GITHUB_URL_REGEX); await user.clear(input); expect(screen.getByRole("button", { name: "Load All" })).toBeDisabled(); }); it("idle: clicking Load All calls startImport with URL", async () => { const user = userEvent.setup(); renderWithAdapters(); await user.click(screen.getByRole("button", { name: "Load All" })); expect(mockStartImport).toHaveBeenCalledWith( expect.stringContaining("raw.githubusercontent"), mockFetchAndCacheSource, mockIsSourceCached, mockRefreshCache, ); }); it("loading: shows progress text and progress bar", () => { mockImportState = { status: "loading", total: 10, completed: 3, failed: 1, }; renderWithAdapters(); expect(screen.getByText(LOADING_PROGRESS_REGEX)).toBeInTheDocument(); }); it("complete: shows success message and Done button", () => { mockImportState = { status: "complete", total: 10, completed: 10, failed: 0, }; renderWithAdapters(); expect(screen.getByText("All sources loaded")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument(); }); it("complete: Done calls dismissPanel and reset", async () => { mockImportState = { status: "complete", total: 10, completed: 10, failed: 0, }; const user = userEvent.setup(); renderWithAdapters(); await user.click(screen.getByRole("button", { name: "Done" })); expect(mockDismissPanel).toHaveBeenCalled(); expect(mockReset).toHaveBeenCalled(); }); it("partial-failure: shows loaded/failed counts", () => { mockImportState = { status: "partial-failure", total: 10, completed: 7, failed: 3, }; renderWithAdapters(); expect(screen.getByText(SEVEN_OF_TEN_REGEX)).toBeInTheDocument(); expect(screen.getByText(THREE_FAILED_REGEX)).toBeInTheDocument(); }); });