WFDASH-WEBAPP/src/components/sidebar.test.tsx
Dave 60ccfa8122 fix(sidebar): top-level nav rendered only dashboardItems[0]
The Search entry added in 1ee3555 was in the array, in the build, and
typed correctly — and never appeared. The top-level nav block rendered
`dashboardItems[0]` with a hardcoded index, so every entry after the
first was silently dropped. Caught when the operator asked where the
link was.

Rendered as a map so appending an entry just works.

Regression test asserts the whole top-level set (Dashboard + Search)
rather than "Search exists", so the next person to append an entry gets
a passing test instead of an invisible link. Verified it fails when the
map is limited back to a single entry while the three pre-existing
sidebar tests keep passing.
2026-10-14 11:03:04 +02:00

125 lines
4.4 KiB
TypeScript

import { fireEvent, render, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { Sidebar } from "./sidebar";
vi.mock("next/link", () => ({
default: ({ href, children, ...props }: { href: string; children: React.ReactNode }) => (
<a href={href} {...props}>
{children}
</a>
),
}));
vi.mock("next/navigation", () => ({
usePathname: () => "/suppliers",
}));
vi.mock("./auth-provider", () => ({
useAuth: () => ({
user: { name: "Test User", email: "test@example.com" },
logout: vi.fn(),
}),
}));
vi.mock("next-themes", () => ({
useTheme: () => ({
theme: "light",
setTheme: vi.fn(),
}),
}));
describe("Sidebar", () => {
it("does not show a separate supplier map navigation item", () => {
render(<Sidebar />);
// Desktop + mobile nav trees both render — assert at least one.
expect(screen.getAllByText("Suppliers").length).toBeGreaterThan(0);
expect(screen.queryByText("Supplier Map")).not.toBeInTheDocument();
});
it("shows pricelist under the Stock section", () => {
render(<Sidebar />);
// Scope to the desktop sidebar (the first <aside>). The mobile
// Sheet renders a duplicate nav tree that's hidden via Tailwind
// `lg:hidden` but still in the DOM, so unscoped role queries
// return two of every button.
const desktop = screen.getAllByRole("complementary")[0];
// The toggle is a `<button>` with text "Stock" (the nav link
// is an `<a>` so role=button scopes us to the toggle only,
// and exact match stops it from matching "Stock Orders" link).
const stockToggle = within(desktop).getByRole("button", {
name: "Stock",
});
fireEvent.click(stockToggle);
const stockSection = stockToggle.parentElement;
expect(stockSection).not.toBeNull();
// The Stock group should contain Stock, Stock Orders, and Pricelist.
// Use role=link to scope to the nav items (not the toggle
// button, which also says "Stock" and would create an
// ambiguous match).
const stock = within(stockSection as HTMLElement);
expect(stock.getByRole("link", { name: "Stock" })).toBeInTheDocument();
expect(stock.getByRole("link", { name: "Stock Orders" })).toBeInTheDocument();
expect(stock.getByRole("link", { name: "Pricelist" })).toBeInTheDocument();
});
it("shows Stock under the Stock section, not Operations", () => {
render(<Sidebar />);
const desktop = screen.getAllByRole("complementary")[0];
const stockToggle = within(desktop).getByRole("button", {
name: "Stock",
});
const operationsToggle = within(desktop).getByRole("button", {
name: "Operations",
});
fireEvent.click(stockToggle);
fireEvent.click(operationsToggle);
const stockSection = stockToggle.parentElement;
const operationsSection = operationsToggle.parentElement;
expect(stockSection).not.toBeNull();
expect(operationsSection).not.toBeNull();
const ops = within(operationsSection as HTMLElement);
// Operations should NOT contain Stock anymore — that's the whole
// point of the reorg.
expect(ops.queryByText("Stock", { exact: true })).not.toBeInTheDocument();
expect(ops.queryByText("Stock Orders")).not.toBeInTheDocument();
expect(ops.queryByText("Pricelist")).not.toBeInTheDocument();
});
it("renders every top-level nav entry, not just the first", () => {
// Regression guard. The top-level nav rendered `dashboardItems[0]`
// — a hardcoded index — so any entry added after Dashboard existed
// in the array and in the build but never appeared in the sidebar.
// That's how the universal Search link shipped invisible: the
// operator asked for it, the code was correct, the entry was simply
// never rendered.
//
// Asserts the full top-level set rather than "Search exists" so the
// next person to append an entry gets a passing test instead of a
// silently invisible link.
render(<Sidebar />);
const desktop = screen.getAllByRole("complementary")[0];
for (const label of ["Dashboard", "Search"]) {
expect(
within(desktop).queryByText(label, { exact: true }),
`top-level nav should render "${label}"`,
).toBeInTheDocument();
}
// And the Search entry must actually point at the route.
const link = within(desktop).getByText("Search", { exact: true }).closest("a");
expect(link).not.toBeNull();
expect(link).toHaveAttribute("href", "/search");
});
});