test(quote-form): wire fetch mocks for new /api/v1/quotes/next-number call

The form's deprecated generateQuoteNumber() was swapped for
fetch('/api/v1/quotes/next-number'). Three tests' setups pointed
fetchMock only at the upload response — and one asserted
fetchMock was never called (it now always is, for next-number).

- 'uploads per-item files' + 'shows a partial failure': chain two
  mockResolvedValueOnce responses (next-number, then upload).
- 'allows a company-only selection': one mockResolvedValueOnce for
  next-number.
- 'stops later uploads when a quote item create fails': add the
  next-number mock and replace 'not.toHaveBeenCalled()' with
  'toHaveBeenCalledTimes(1)' plus an explicit assertion on the
  URL — item-create branch throws before the upload fetch, so
  the original negative assertion was always-true and is now wrong.

The generateQuoteNumberMock in the hoisted block is now dead (the
form no longer imports it). Left in place to keep this commit
narrow; safe to delete in a future PR alongside the deprecated
function in src/lib/pb-helpers.ts (which now has zero callers).
This commit is contained in:
SJCoetz 2026-07-26 22:33:37 +02:00
parent bd2a56ce9f
commit 2d0861a2e7

View file

@ -113,10 +113,19 @@ describe("QuoteForm", () => {
createMock
.mockResolvedValueOnce({ id: "quote-1" })
.mockResolvedValueOnce({ id: "item-1" });
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({ id: "att-1" }),
});
// The form now hits /api/v1/quotes/next-number before PB create,
// and /api/v1/quotes/items/<id>/attachments/upload after. Queue
// both responses so the first fetch call gets the next-number
// shape and the second gets the upload shape.
fetchMock
.mockResolvedValueOnce({
ok: true,
json: async () => ({ quote_number: "Q-2026-07-001" }),
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: "att-1" }),
});
render(<QuoteForm open onOpenChange={vi.fn()} onSaved={vi.fn()} />);
@ -165,6 +174,11 @@ describe("QuoteForm", () => {
it("allows a company-only selection when creating a quote", async () => {
createMock.mockResolvedValueOnce({ id: "quote-2" }).mockResolvedValueOnce({ id: "item-2" });
// The form hits /api/v1/quotes/next-number before PB create.
fetchMock.mockResolvedValueOnce({
ok: true,
json: async () => ({ quote_number: "Q-2026-07-001" }),
});
const onSaved = vi.fn();
const onOpenChange = vi.fn();
@ -201,6 +215,12 @@ describe("QuoteForm", () => {
createMock
.mockResolvedValueOnce({ id: "quote-1" })
.mockRejectedValueOnce(new Error("item create failed"));
// The form hits /api/v1/quotes/next-number before PB create.
// The item-create then fails, so the upload fetch is NOT called.
fetchMock.mockResolvedValueOnce({
ok: true,
json: async () => ({ quote_number: "Q-2026-07-001" }),
});
render(<QuoteForm open onOpenChange={vi.fn()} onSaved={vi.fn()} />);
@ -220,7 +240,10 @@ describe("QuoteForm", () => {
fireEvent.click(screen.getByRole("button", { name: /create quote/i }));
await waitFor(() => {
expect(fetchMock).not.toHaveBeenCalled();
// Next-number WAS called before PB create; the upload
// fetch was NOT called because the item-create branch
// threw before reaching it.
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(toastErrorMock).toHaveBeenCalledWith(
expect.stringMatching(/partially created/i),
);
@ -231,11 +254,17 @@ describe("QuoteForm", () => {
createMock
.mockResolvedValueOnce({ id: "quote-1" })
.mockResolvedValueOnce({ id: "item-1" });
fetchMock.mockResolvedValue({
ok: false,
status: 500,
json: async () => ({ error: "Upload failed" }),
});
// First fetch: next-number (success). Second fetch: upload (500).
fetchMock
.mockResolvedValueOnce({
ok: true,
json: async () => ({ quote_number: "Q-2026-07-001" }),
})
.mockResolvedValueOnce({
ok: false,
status: 500,
json: async () => ({ error: "Upload failed" }),
});
render(<QuoteForm open onOpenChange={vi.fn()} onSaved={vi.fn()} />);