import { Toasty } from "@cloudflare/kumo"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import * as React from "react"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { MenuList } from "../../src/components/MenuList"; import { render } from "../utils/render.tsx"; vi.mock("@tanstack/react-router", async () => { const actual = await vi.importActual("@tanstack/react-router"); return { ...actual, Link: ({ children, to, ...props }: { children: React.ReactNode; to?: string; [key: string]: unknown; }) => ( {children} ), useNavigate: () => vi.fn(), }; }); vi.mock("../../src/lib/api", async () => { const actual = await vi.importActual("../../src/lib/api"); return { ...actual, fetchMenus: vi.fn(), createMenu: vi.fn().mockResolvedValue({ name: "new-menu", label: "New Menu" }), deleteMenu: vi.fn().mockResolvedValue(undefined), }; }); import * as api from "../../src/lib/api"; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const MAIN_MENU_ITEMS_REGEX = /main.*3 items/; const FOOTER_MENU_ITEMS_REGEX = /footer.*1 items/; const DELETE_MENU_CONFIRMATION_REGEX = /Are you sure you want to delete this menu/; const CREATE_MENU_REGEX = /Create Menu/; function mockMenus() { vi.mocked(api.fetchMenus).mockResolvedValue([ { id: "m1", name: "main", label: "Main Menu", itemCount: 3, created_at: "", updated_at: "", }, { id: "m2", name: "footer", label: "Footer Menu", itemCount: 1, created_at: "", updated_at: "", }, ]); } function Wrapper({ children }: { children: React.ReactNode }) { const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, }); return ( {children} ); } describe("MenuList", () => { beforeEach(() => { vi.clearAllMocks(); mockMenus(); }); it("displays list of menus with labels and item counts", async () => { const screen = await render(, { wrapper: Wrapper }); await expect.element(screen.getByRole("heading", { name: "Main Menu" })).toBeInTheDocument(); await expect.element(screen.getByText(MAIN_MENU_ITEMS_REGEX)).toBeInTheDocument(); await expect.element(screen.getByRole("heading", { name: "Footer Menu" })).toBeInTheDocument(); await expect.element(screen.getByText(FOOTER_MENU_ITEMS_REGEX)).toBeInTheDocument(); }); it("Create Menu button opens dialog", async () => { const screen = await render(, { wrapper: Wrapper }); await screen.getByRole("button", { name: CREATE_MENU_REGEX }).click(); await expect.element(screen.getByText("Create New Menu")).toBeInTheDocument(); }); it("create dialog has name and label inputs", async () => { const screen = await render(, { wrapper: Wrapper }); await screen.getByRole("button", { name: CREATE_MENU_REGEX }).click(); await expect.element(screen.getByLabelText("Name")).toBeInTheDocument(); await expect.element(screen.getByLabelText("Label")).toBeInTheDocument(); }); it("delete button opens confirmation dialog", async () => { const screen = await render(, { wrapper: Wrapper }); await expect.element(screen.getByRole("heading", { name: "Main Menu" })).toBeInTheDocument(); await screen.getByRole("button", { name: "Delete main menu" }).click(); await expect.element(screen.getByRole("heading", { name: "Delete Menu" })).toBeInTheDocument(); await expect.element(screen.getByText(DELETE_MENU_CONFIRMATION_REGEX)).toBeInTheDocument(); }); it("shows empty state when no menus", async () => { vi.mocked(api.fetchMenus).mockResolvedValue([]); const screen = await render(, { wrapper: Wrapper }); await expect.element(screen.getByText("No menus yet")).toBeInTheDocument(); await expect .element(screen.getByText("Create your first navigation menu to get started")) .toBeInTheDocument(); }); it("each menu has an Edit link", async () => { const screen = await render(, { wrapper: Wrapper }); await expect.element(screen.getByRole("heading", { name: "Main Menu" })).toBeInTheDocument(); const editLinks = screen.getByText("Edit"); await expect.element(editLinks.first()).toBeInTheDocument(); }); });