2026-06-11 18:30:02 +00:00
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
|
import { quoteStatusClassName, formatQuoteStatus } from "./quote-status";
|
|
|
|
|
|
|
|
|
|
describe("quoteStatusClassName", () => {
|
|
|
|
|
it("returns a styled class string for known statuses", () => {
|
|
|
|
|
const cls = quoteStatusClassName("new");
|
2026-07-07 15:22:03 +00:00
|
|
|
expect(cls).toContain("border-info");
|
|
|
|
|
expect(cls).toContain("bg-info");
|
2026-06-11 18:30:02 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("returns a fallback class for unknown statuses", () => {
|
|
|
|
|
const cls = quoteStatusClassName("not_a_real_status");
|
|
|
|
|
expect(cls).toContain("border-muted");
|
|
|
|
|
expect(cls).toContain("bg-muted");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("supports every documented quote status", () => {
|
|
|
|
|
const statuses = [
|
|
|
|
|
"new",
|
|
|
|
|
"claimed",
|
|
|
|
|
"processed",
|
|
|
|
|
"emailed",
|
|
|
|
|
"approval_pending",
|
|
|
|
|
"approved",
|
|
|
|
|
"rejected",
|
|
|
|
|
"problem",
|
|
|
|
|
"complete",
|
|
|
|
|
"cancelled",
|
|
|
|
|
"follow_up",
|
|
|
|
|
"email_failed",
|
|
|
|
|
"unmatched_bounce",
|
feat: add quote templates, improved email, new statuses
Add complete quote template functionality, enhanced outbound email sending, new quote statuses, and supporting infrastructure:
- Add new quote statuses (template, sending, mail_failed) with tailored UI styling for status pills
- Implement full template workflows: save real quotes as templates, create new quotes from existing templates
- Add support for custom quote dates and extended validity periods on generated PDF documents
- Build out improved email pipeline with persisted compose fields and retry functionality for failed sends
- Add attachment direction tracking to categorize inbound, outbound, and generated quote attachments
- Fix tooltip component to properly handle Radix `asChild` prop to avoid React DOM attribute errors
- Add comprehensive test coverage, schema migrations, and update sample test and production data
2026-06-24 20:57:01 +00:00
|
|
|
"template",
|
|
|
|
|
"sending",
|
|
|
|
|
"mail_failed",
|
2026-07-27 06:21:20 +00:00
|
|
|
// Added 2026-07-24 alongside the partial-approval flow.
|
|
|
|
|
"partial_approval_pending",
|
|
|
|
|
"partial_approved",
|
2026-06-11 18:30:02 +00:00
|
|
|
];
|
|
|
|
|
for (const s of statuses) {
|
|
|
|
|
const cls = quoteStatusClassName(s);
|
2026-07-07 15:22:03 +00:00
|
|
|
// Every known status must produce a border-using class so the
|
|
|
|
|
// pill has visual weight. The neutral `cancelled` shares the
|
|
|
|
|
// muted fallback on purpose (it's terminal — no semantic
|
|
|
|
|
// colour needed); everything else must produce its own map
|
|
|
|
|
// entry.
|
2026-06-11 18:30:02 +00:00
|
|
|
expect(cls, `class for ${s}`).toMatch(/border-/);
|
2026-07-07 15:22:03 +00:00
|
|
|
if (s !== "cancelled") {
|
|
|
|
|
expect(
|
|
|
|
|
cls,
|
|
|
|
|
`class for ${s}`,
|
|
|
|
|
).not.toContain("border-muted bg-muted text-muted-foreground");
|
|
|
|
|
}
|
2026-06-11 18:30:02 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("formatQuoteStatus", () => {
|
2026-06-29 20:15:32 +00:00
|
|
|
it("uses workflow-friendly labels for email-status enums", () => {
|
|
|
|
|
expect(formatQuoteStatus("email_failed")).toBe("Email failed");
|
|
|
|
|
expect(formatQuoteStatus("mail_failed")).toBe("Mail failed");
|
|
|
|
|
expect(formatQuoteStatus("unmatched_bounce")).toBe("Unmatched bounce");
|
2026-06-11 18:30:02 +00:00
|
|
|
});
|
|
|
|
|
|
2026-06-29 20:15:32 +00:00
|
|
|
it("renders workflow-friendly labels for the merged tabs", () => {
|
|
|
|
|
expect(formatQuoteStatus("approval_pending")).toBe("Pending approval");
|
|
|
|
|
expect(formatQuoteStatus("emailed")).toBe("Awaiting reply");
|
|
|
|
|
expect(formatQuoteStatus("sending")).toBe("Sending…");
|
|
|
|
|
expect(formatQuoteStatus("follow_up")).toBe("Follow up");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("leaves single-word and terminal statuses unchanged", () => {
|
2026-06-11 18:30:02 +00:00
|
|
|
expect(formatQuoteStatus("new")).toBe("new");
|
|
|
|
|
expect(formatQuoteStatus("complete")).toBe("complete");
|
2026-06-29 20:15:32 +00:00
|
|
|
expect(formatQuoteStatus("cancelled")).toBe("cancelled");
|
|
|
|
|
expect(formatQuoteStatus("rejected")).toBe("rejected");
|
|
|
|
|
expect(formatQuoteStatus("claimed")).toBe("claimed");
|
2026-06-11 18:30:02 +00:00
|
|
|
});
|
|
|
|
|
});
|