diff --git a/e2e/invoice-detail.spec.ts b/e2e/invoice-detail.spec.ts index afaa803..165b09e 100644 --- a/e2e/invoice-detail.spec.ts +++ b/e2e/invoice-detail.spec.ts @@ -43,4 +43,10 @@ test.describe("invoice detail 3-pane shell", () => { await expect(page).toHaveURL(/\/invoices\/inv-/); await expect(page.getByTestId("invoice-document-viewer")).toBeVisible(); }); + + test("unknown invoice id shows not-found warning", async ({ page }) => { + await page.goto("/invoices/missing-invoice"); + await expect(page.getByTestId("invoice-not-found")).toBeVisible(); + await expect(page.getByTestId("invoice-document-viewer")).toHaveCount(0); + }); }); diff --git a/src/domain/invoices/use-invoices.ts b/src/domain/invoices/use-invoices.ts index d888bec..e5ad475 100644 --- a/src/domain/invoices/use-invoices.ts +++ b/src/domain/invoices/use-invoices.ts @@ -13,6 +13,7 @@ import { type MockInvoiceLine, type MockInvoiceStatus, } from "@/mocks"; +import { approvalQueryKeys } from "@/domain/approvals/query-keys"; import { invoiceQueryKeys } from "@/domain/invoices/query-keys"; async function invalidateInvoiceQueries( @@ -37,9 +38,9 @@ export function useInvoice(id: string | undefined) { queryKey: invoiceQueryKeys.detail(id ?? ""), queryFn: () => { if (!id) { - return undefined; + return null; } - return getMockInvoice(id); + return getMockInvoice(id) ?? null; }, enabled: Boolean(id), }); @@ -117,6 +118,7 @@ export function useTransitionInvoiceStatus() { }) => Promise.resolve(transitionMockInvoiceStatus(id, status, activity)), onSuccess: async (_data, variables) => { await invalidateInvoiceQueries(queryClient, variables.id); + await queryClient.invalidateQueries({ queryKey: approvalQueryKeys.all }); }, }); } diff --git a/src/mocks/approvals.ts b/src/mocks/approvals.ts index 6a114b9..2040ca7 100644 --- a/src/mocks/approvals.ts +++ b/src/mocks/approvals.ts @@ -69,6 +69,23 @@ export function updateMockApprovalStatus( return { ...updated }; } +/** Keep inbox rows aligned when invoice detail stubs transition status. */ +export function syncMockApprovalsForInvoice( + invoiceId: string, + status: MockApprovalStatus, +): MockApprovalItem[] { + const updated: MockApprovalItem[] = []; + approvalStore = approvalStore.map((item) => { + if (item.invoiceId !== invoiceId) { + return item; + } + const next = { ...item, status }; + updated.push({ ...next }); + return next; + }); + return updated; +} + export function resetMockApprovals(): void { approvalStore = seedApprovals.map((item) => ({ ...item })); } diff --git a/src/mocks/index.test.ts b/src/mocks/index.test.ts index 946d8db..f3e8692 100644 --- a/src/mocks/index.test.ts +++ b/src/mocks/index.test.ts @@ -124,6 +124,12 @@ describe("mocks module", () => { }); expect(approved.status).toBe("approved"); expect(approved.activity.some((entry) => entry.message === "Approved")).toBe(true); + expect(getMockPendingApprovals().some((item) => item.invoiceId === "inv-2")).toBe(false); + expect(getMockApprovals().find((item) => item.invoiceId === "inv-2")?.status).toBe("approved"); + }); + + it("returns undefined for unknown invoice ids", () => { + expect(getMockInvoice("missing-invoice")).toBeUndefined(); }); it("switches mock session user", () => { diff --git a/src/mocks/index.ts b/src/mocks/index.ts index d38baea..fa79186 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -64,6 +64,7 @@ export { getMockApprovals, getMockPendingApprovals, resetMockApprovals, + syncMockApprovalsForInvoice, updateMockApprovalStatus, type MockApprovalItem, type MockApprovalStatus, diff --git a/src/mocks/invoices.ts b/src/mocks/invoices.ts index e3b33f9..c324ad7 100644 --- a/src/mocks/invoices.ts +++ b/src/mocks/invoices.ts @@ -1,3 +1,4 @@ +import { syncMockApprovalsForInvoice } from "@/mocks/approvals"; import { areMocksEnabled } from "@/mocks/env"; export type MockInvoiceStatus = "pending_approval" | "approved" | "scheduled" | "paid" | "rejected"; @@ -515,11 +516,19 @@ export function transitionMockInvoiceStatus( createdAt: activity.createdAt ?? new Date().toISOString(), }; - return replaceInvoice(index, { + const updated = replaceInvoice(index, { ...current, status, activity: [...current.activity, nextActivity], }); + + if (status === "approved" || status === "rejected") { + syncMockApprovalsForInvoice(id, status); + } else if (status === "pending_approval") { + syncMockApprovalsForInvoice(id, "pending"); + } + + return updated; } /** Test helper to restore seed data between unit tests. */