Adds aria-label attributes to HP placeholder and source delete buttons for both accessibility and testability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
3.5 KiB
TypeScript
128 lines
3.5 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(),
|
|
}));
|
|
|
|
import * as bestiaryCache from "../../adapters/bestiary-cache.js";
|
|
import { SourceManager } from "../source-manager";
|
|
|
|
const mockGetCachedSources = vi.mocked(bestiaryCache.getCachedSources);
|
|
const mockClearSource = vi.mocked(bestiaryCache.clearSource);
|
|
const mockClearAll = vi.mocked(bestiaryCache.clearAll);
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("SourceManager", () => {
|
|
it("shows 'No cached sources' empty state when no sources", async () => {
|
|
mockGetCachedSources.mockResolvedValue([]);
|
|
render(<SourceManager onCacheCleared={vi.fn()} />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText("No cached sources")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("lists cached sources with display name and creature count", async () => {
|
|
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 onCacheCleared={vi.fn()} />);
|
|
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 onCacheCleared", async () => {
|
|
const user = userEvent.setup();
|
|
const onCacheCleared = vi.fn();
|
|
mockGetCachedSources
|
|
.mockResolvedValueOnce([
|
|
{
|
|
sourceCode: "mm",
|
|
displayName: "Monster Manual",
|
|
creatureCount: 300,
|
|
cachedAt: Date.now(),
|
|
},
|
|
])
|
|
.mockResolvedValue([]);
|
|
mockClearAll.mockResolvedValue(undefined);
|
|
render(<SourceManager onCacheCleared={onCacheCleared} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Monster Manual")).toBeInTheDocument();
|
|
});
|
|
|
|
await user.click(screen.getByRole("button", { name: "Clear All" }));
|
|
await waitFor(() => {
|
|
expect(mockClearAll).toHaveBeenCalled();
|
|
});
|
|
expect(onCacheCleared).toHaveBeenCalled();
|
|
});
|
|
|
|
it("individual source delete button calls clear for that source", async () => {
|
|
const user = userEvent.setup();
|
|
const onCacheCleared = vi.fn();
|
|
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 onCacheCleared={onCacheCleared} />);
|
|
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(onCacheCleared).toHaveBeenCalled();
|
|
});
|
|
});
|