Files
initiative/apps/web/src/components/__tests__/source-manager.test.tsx
Lukas 86768842ff
All checks were successful
CI / check (push) Successful in 1m18s
CI / build-image (push) Has been skipped
Refactor App.tsx from god component to context-based architecture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:33:33 +01:00

151 lines
4.1 KiB
TypeScript

// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("../../adapters/bestiary-cache.js", () => ({
getCachedSources: vi.fn(),
clearSource: vi.fn(),
clearAll: vi.fn(),
}));
// Mock the context module
vi.mock("../../contexts/bestiary-context.js", () => ({
useBestiaryContext: vi.fn(),
}));
import * as bestiaryCache from "../../adapters/bestiary-cache.js";
import { useBestiaryContext } from "../../contexts/bestiary-context.js";
import { SourceManager } from "../source-manager.js";
const mockGetCachedSources = vi.mocked(bestiaryCache.getCachedSources);
const mockClearSource = vi.mocked(bestiaryCache.clearSource);
const mockClearAll = vi.mocked(bestiaryCache.clearAll);
const mockUseBestiaryContext = vi.mocked(useBestiaryContext);
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
function setupMockContext() {
const refreshCache = vi.fn().mockResolvedValue(undefined);
mockUseBestiaryContext.mockReturnValue({
refreshCache,
search: vi.fn().mockReturnValue([]),
getCreature: vi.fn(),
isLoaded: true,
isSourceCached: vi.fn().mockResolvedValue(false),
fetchAndCacheSource: vi.fn(),
uploadAndCacheSource: vi.fn(),
} as ReturnType<typeof useBestiaryContext>);
return { refreshCache };
}
describe("SourceManager", () => {
it("shows 'No cached sources' empty state when no sources", async () => {
setupMockContext();
mockGetCachedSources.mockResolvedValue([]);
render(<SourceManager />);
await waitFor(() => {
expect(screen.getByText("No cached sources")).toBeInTheDocument();
});
});
it("lists cached sources with display name and creature count", async () => {
setupMockContext();
mockGetCachedSources.mockResolvedValue([
{
sourceCode: "mm",
displayName: "Monster Manual",
creatureCount: 300,
cachedAt: Date.now(),
},
{
sourceCode: "vgm",
displayName: "Volo's Guide",
creatureCount: 100,
cachedAt: Date.now(),
},
]);
render(<SourceManager />);
await waitFor(() => {
expect(screen.getByText("Monster Manual")).toBeInTheDocument();
});
expect(screen.getByText("300 creatures")).toBeInTheDocument();
expect(screen.getByText("Volo's Guide")).toBeInTheDocument();
expect(screen.getByText("100 creatures")).toBeInTheDocument();
});
it("Clear All button calls cache clear and refreshCache", async () => {
const user = userEvent.setup();
const { refreshCache } = setupMockContext();
mockGetCachedSources
.mockResolvedValueOnce([
{
sourceCode: "mm",
displayName: "Monster Manual",
creatureCount: 300,
cachedAt: Date.now(),
},
])
.mockResolvedValue([]);
mockClearAll.mockResolvedValue(undefined);
render(<SourceManager />);
await waitFor(() => {
expect(screen.getByText("Monster Manual")).toBeInTheDocument();
});
await user.click(screen.getByRole("button", { name: "Clear All" }));
await waitFor(() => {
expect(mockClearAll).toHaveBeenCalled();
});
expect(refreshCache).toHaveBeenCalled();
});
it("individual source delete button calls clear for that source", async () => {
const user = userEvent.setup();
const { refreshCache } = setupMockContext();
mockGetCachedSources
.mockResolvedValueOnce([
{
sourceCode: "mm",
displayName: "Monster Manual",
creatureCount: 300,
cachedAt: Date.now(),
},
{
sourceCode: "vgm",
displayName: "Volo's Guide",
creatureCount: 100,
cachedAt: Date.now(),
},
])
.mockResolvedValue([
{
sourceCode: "vgm",
displayName: "Volo's Guide",
creatureCount: 100,
cachedAt: Date.now(),
},
]);
mockClearSource.mockResolvedValue(undefined);
render(<SourceManager />);
await waitFor(() => {
expect(screen.getByText("Monster Manual")).toBeInTheDocument();
});
await user.click(
screen.getByRole("button", { name: "Remove Monster Manual" }),
);
await waitFor(() => {
expect(mockClearSource).toHaveBeenCalledWith("mm");
});
expect(refreshCache).toHaveBeenCalled();
});
});