test(stock-orders): QuickDraftModal edit-mode coverage

Asserts the modal hides non-targeted drafts, renders qty/cost inputs
per line, PATCHes only changed fields on Save, and that remove still
works in edit mode.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-07-31 13:58:05 +02:00
parent 2303d8a29d
commit 5e91a5d7c0

View file

@ -616,3 +616,295 @@ describe("QuickDraftModal — no-history fallback", () => {
).toBeInTheDocument();
});
});
describe("QuickDraftModal — edit mode", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("renders only the targeted draft and hides the others", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 50,
cost_price: 20,
},
],
},
{
id: "po-2",
purchase_number: "PO26073002STK",
supplier: "sup-2",
supplier_name: "Hardware Hub",
lines: [
{
id: "line-3",
description: "Bolt pack",
quantity: 5,
cost_price: 100,
},
],
},
],
}),
});
render(
<QuickDraftModal
open
onOpenChange={() => {}}
editPurchaseId="po-1"
/>,
);
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
expect(screen.getByText(/Steel & Pipe Co/)).toBeInTheDocument();
expect(screen.queryByText(/Hardware Hub/)).not.toBeInTheDocument();
});
it("renders edit-mode qty + cost inputs for each line of the targeted draft", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 50,
cost_price: 20,
},
],
},
],
}),
});
render(
<QuickDraftModal
open
onOpenChange={() => {}}
editPurchaseId="po-1"
/>,
);
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
const qtyInput = screen.getByTestId(
"quick-draft-edit-qty-line-1",
) as HTMLInputElement;
expect(qtyInput).toBeInTheDocument();
expect(qtyInput.value).toBe("50");
const costInput = screen.getByTestId(
"quick-draft-edit-cost-line-1",
) as HTMLInputElement;
expect(costInput).toBeInTheDocument();
expect(costInput.value).toBe("20");
});
it("Save PATCHes only the changed fields and refetches cards", async () => {
const { toast } = await import("sonner");
const successSpy = vi
.spyOn(toast, "success")
.mockImplementation(() => "");
global.fetch = vi
.fn()
// initial cards fetch
.mockResolvedValueOnce({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 50,
cost_price: 20,
},
],
},
],
}),
})
// PATCH response
.mockResolvedValueOnce({
ok: true,
json: async () => ({ ok: true }),
})
// refetch cards after save
.mockResolvedValueOnce({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 75,
cost_price: 20,
},
],
},
],
}),
});
render(
<QuickDraftModal
open
onOpenChange={() => {}}
editPurchaseId="po-1"
/>,
);
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
const qtyInput = screen.getByTestId(
"quick-draft-edit-qty-line-1",
) as HTMLInputElement;
fireEvent.change(qtyInput, { target: { value: "75" } });
fireEvent.click(screen.getByTestId("quick-draft-save-line-line-1"));
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
const patchCalls = (
global.fetch as ReturnType<typeof vi.fn>
).mock.calls.filter(
(c) => typeof c[0] === "string" && c[0].includes("/line/line-1"),
);
expect(patchCalls.length).toBeGreaterThan(0);
expect(patchCalls[0][1].method).toBe("PATCH");
const body = JSON.parse(patchCalls[0][1].body);
expect(body).toEqual({ quantity: 75 });
expect(successSpy).toHaveBeenCalledWith("Line updated");
successSpy.mockRestore();
});
it("remove still works in edit mode", async () => {
global.fetch = vi
.fn()
// initial cards fetch
.mockResolvedValueOnce({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 50,
cost_price: 20,
},
{
id: "line-2",
description: "Angle grinder",
quantity: 1,
cost_price: 1500,
},
],
},
],
}),
})
// DELETE response
.mockResolvedValueOnce({
ok: true,
json: async () => ({ ok: true }),
})
// refetch cards after delete
.mockResolvedValueOnce({
ok: true,
json: async () => ({
ok: true,
items: [
{
id: "po-1",
purchase_number: "PO26073001STK",
supplier: "sup-1",
supplier_name: "Steel & Pipe Co",
lines: [
{
id: "line-1",
description: "115mm Cutting Disc",
quantity: 50,
cost_price: 20,
},
],
},
],
}),
});
render(
<QuickDraftModal
open
onOpenChange={() => {}}
editPurchaseId="po-1"
/>,
);
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
fireEvent.click(screen.getByTestId("quick-draft-remove-line-line-2"));
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
const deleteCalls = (
global.fetch as ReturnType<typeof vi.fn>
).mock.calls.filter(
(c) => typeof c[0] === "string" && c[0].includes("/line/line-2"),
);
expect(deleteCalls.length).toBe(1);
expect(deleteCalls[0][1].method).toBe("DELETE");
});
});