This commit adds the complete v2/v3 stock management functionality: - add new stock-specific item statuses and update the status visual map - implement full stock purchase API suite (create, list, retrieve, send, receive) - reorganize sidebar navigation to group stock-related pages together - add stock item CRUD APIs, status utilities, low stock filtering, and surplus booking logic - add database migration for the `stock_items.target_qty` field - update type definitions and add comprehensive tests for all new stock components
97 lines
3.2 KiB
TypeScript
97 lines
3.2 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 />);
|
|
|
|
expect(screen.getByText("Suppliers")).toBeInTheDocument();
|
|
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();
|
|
});
|
|
});
|