diff --git a/src/components/stock-orders/quick-draft-modal.test.tsx b/src/components/stock-orders/quick-draft-modal.test.tsx index 904a08f..9c372f0 100644 --- a/src/components/stock-orders/quick-draft-modal.test.tsx +++ b/src/components/stock-orders/quick-draft-modal.test.tsx @@ -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( + {}} + 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( + {}} + 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( + {}} + 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 + ).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( + {}} + 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 + ).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"); + }); +});