feat(stock-quick-draft): mount modal on /stock-orders via New Stock Item button

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-07-30 23:33:12 +02:00
parent 6329a100e7
commit 4cf9c782e6
2 changed files with 153 additions and 0 deletions

View file

@ -0,0 +1,138 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { cleanup, render, screen, fireEvent } from "@testing-library/react";
// ── Mocks ────────────────────────────────────────────────────────────────
//
// The page is a client component that reads PocketBase via
// `@/lib/pocketbase`. We mock both the `pocketbase` npm package
// (to keep EventSource out of jsdom) and the local helper module.
// Shared hoisted state so both mocks agree on what getList returns.
const { _state, _collectionMock, _resetState } = vi.hoisted(() => {
const state = {
items: [] as unknown[],
error: undefined as string | undefined,
};
const collectionMock = {
getList: vi.fn().mockImplementation(async () => ({
items: state.items,
totalItems: state.items.length,
page: 1,
perPage: 50,
totalPages: 1,
})),
getFullList: vi.fn().mockResolvedValue([]),
};
return {
_state: state,
_collectionMock: collectionMock,
_resetState: () => {
state.items.length = 0;
state.error = undefined;
collectionMock.getList.mockClear();
collectionMock.getList.mockImplementation(async () => ({
items: state.items,
totalItems: state.items.length,
page: 1,
perPage: 50,
totalPages: 1,
}));
},
};
});
vi.mock("pocketbase", () => ({
default: class MockPocketBase {
autoCancellation() {}
collection(_name: string) {
return _collectionMock;
}
},
}));
vi.mock("@/lib/pocketbase", () => ({
__esModule: true,
default: () => ({
collection: () => _collectionMock,
}),
getPocketBase: () => ({
collection: () => _collectionMock,
}),
}));
vi.mock("next/navigation", () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
back: vi.fn(),
}),
}));
// Stub the QuickDraftModal — it's already tested in
// `src/components/stock-orders/quick-draft-modal.test.tsx`. This test
// only cares that the page mounts the modal and toggles its `open`
// prop when the button is clicked.
vi.mock("@/components/stock-orders/quick-draft-modal", () => ({
QuickDraftModal: ({
open,
onOpenChange,
}: {
open: boolean;
onOpenChange: (b: boolean) => void;
}) => (
<div data-testid="quick-draft-modal" data-open={open}>
<button
type="button"
data-testid="quick-draft-modal-close"
onClick={() => onOpenChange(false)}
>
Close
</button>
</div>
),
}));
import StockOrdersPage from "./page";
// ── Tests ────────────────────────────────────────────────────────────────
describe("StockOrdersPage — quick draft button", () => {
beforeEach(() => {
_resetState();
});
afterEach(() => {
cleanup();
});
it("renders a New Stock Item button in the page header", async () => {
render(<StockOrdersPage />);
const button = await screen.findByTestId("new-stock-item-button");
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent(/new stock item/i);
});
it("mounts the QuickDraftModal (closed by default)", async () => {
render(<StockOrdersPage />);
const modal = await screen.findByTestId("quick-draft-modal");
expect(modal).toBeInTheDocument();
expect(modal.dataset.open).toBe("false");
});
it("opens the QuickDraftModal when the button is clicked", async () => {
render(<StockOrdersPage />);
const modal = await screen.findByTestId("quick-draft-modal");
expect(modal.dataset.open).toBe("false");
fireEvent.click(screen.getByTestId("new-stock-item-button"));
expect(modal.dataset.open).toBe("true");
});
it("closes the QuickDraftModal when the modal calls onOpenChange(false)", async () => {
render(<StockOrdersPage />);
const modal = await screen.findByTestId("quick-draft-modal");
fireEvent.click(screen.getByTestId("new-stock-item-button"));
expect(modal.dataset.open).toBe("true");
fireEvent.click(screen.getByTestId("quick-draft-modal-close"));
expect(modal.dataset.open).toBe("false");
});
});

View file

@ -16,6 +16,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { Loader2, Plus, AlertCircle, Package, ChevronRight, Boxes } from "lucide-react";
import { toast } from "sonner";
import getPocketBase from "@/lib/pocketbase";
import { QuickDraftModal } from "@/components/stock-orders/quick-draft-modal";
import {
STOCK_PURCHASE_STATUS_VALUES,
type StockPurchase,
@ -93,6 +94,7 @@ export default function StockOrdersPage() {
const [status, setStatus] = useState<StockPurchaseStatus | "">("");
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [quickDraftOpen, setQuickDraftOpen] = useState(false);
const fetchRows = useCallback(async () => {
setLoading(true);
@ -160,6 +162,14 @@ export default function StockOrdersPage() {
<Boxes className="mr-2 h-4 w-4" />
Reorder from low stock
</Button>
<Button
type="button"
variant="outline"
data-testid="new-stock-item-button"
onClick={() => setQuickDraftOpen(true)}
>
New Stock Item
</Button>
<Button onClick={() => router.push("/stock-orders/new")}>
<Plus className="mr-2 h-4 w-4" />
New stock order
@ -298,6 +308,11 @@ export default function StockOrdersPage() {
</Button>
</div>
)}
<QuickDraftModal
open={quickDraftOpen}
onOpenChange={setQuickDraftOpen}
/>
</div>
);
}