From 4c0256baf4738ef9568182e320baa80255a7b15c Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 6 Nov 2026 05:57:17 +0200 Subject: [PATCH] test(repairs): pin the status labels, variants and next steps --- src/lib/repair-status.test.ts | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/lib/repair-status.test.ts b/src/lib/repair-status.test.ts index 018b656..2dec5cb 100644 --- a/src/lib/repair-status.test.ts +++ b/src/lib/repair-status.test.ts @@ -28,6 +28,37 @@ describe("repair status vocabulary", () => { } }); + // The label is the operator-facing wording on EVERY repairs surface + // (office list, office detail, the phone app, the agent's tools). + // Truthiness above only proves a label EXISTS - a typo or a swapped + // word would still pass. Pin the exact wording. + it("pins the operator-facing label of every status", () => { + expect(REPAIR_STATUS_LABEL).toEqual({ + new: "Booked in", + assessing: "With supplier", + quoted: "Quote in", + approved: "Approved", + in_progress: "On the bench", + completed: "Done", + cancelled: "Cancelled", + }); + }); + + // Same for the badge variant. "secondary" is a legal variant on the + // WRONG status, and no type check can catch that - only the exact + // assignment can. + it("pins the badge variant of every status", () => { + expect(REPAIR_STATUS_VARIANT).toEqual({ + new: "outline", + assessing: "secondary", + quoted: "secondary", + approved: "default", + in_progress: "secondary", + completed: "default", + cancelled: "destructive", + }); + }); + it("allows exactly the transitions the service allowed before", () => { expect(REPAIR_TRANSITIONS.new).toEqual(["assessing", "quoted", "cancelled"]); expect(REPAIR_TRANSITIONS.assessing).toEqual(["quoted", "cancelled"]); @@ -59,6 +90,40 @@ describe("repair status vocabulary", () => { expect(nextStepFor("cancelled")).toBeNull(); }); + // The whole next step, pinned - label AND action AND target. The + // target matters most: "quoted" must point at "approved", never at + // "in_progress". Both are legal transitions, but in_progress would + // let a job skip the customer's approval. + it("pins the exact next step of every status", () => { + expect(nextStepFor("new")).toEqual({ + label: "Send to supplier", + action: "generate_repair_request", + to: "assessing", + }); + expect(nextStepFor("assessing")).toEqual({ + label: "Upload supplier quote", + action: "upload_supplier_quote", + to: "quoted", + }); + expect(nextStepFor("quoted")).toEqual({ + label: "Make customer quote", + action: "create_customer_quote", + to: "approved", + }); + expect(nextStepFor("approved")).toEqual({ + label: "Start work", + action: "start_work", + to: "in_progress", + }); + expect(nextStepFor("in_progress")).toEqual({ + label: "Mark completed", + action: "mark_completed", + to: "completed", + }); + expect(nextStepFor("completed")).toBeNull(); + expect(nextStepFor("cancelled")).toBeNull(); + }); + it("never offers a next step that is not a legal transition", () => { for (const status of REPAIR_STATUS) { const step = nextStepFor(status); @@ -74,6 +139,11 @@ describe("repair status vocabulary", () => { expect(() => assertRepairTransition("new", "completed")).toThrow( /allowed: assessing, quoted, cancelled/, ); + // A status with NO legal targets names "none" - the terminal + // wording the caller shows the operator. + expect(() => assertRepairTransition("completed", "completed")).toThrow( + /Cannot transition repair from "completed" to "completed" \(allowed: none\)\./, + ); expect(() => assertRepairTransition("new", "assessing")).not.toThrow(); }); });