import { describe, expect, it } from "vitest"; import { issuesToMap, planVisitSchema, todayIsoDate, type PlanVisitInput } from "@/lib/schema"; import { formatNanpDisplay, normalizeNanpPhone, smsLink } from "@/lib/phone"; const base: PlanVisitInput = { submissionId: "6f1a2c3e-4b5d-4e6f-8a9b-0c1d2e3f4a5b", firstName: "Jordan", lastName: "", email: "jordan@example.com", phone: "", campusId: "norwood", visitDate: "", serviceTime: "", questions: "", emailConsent: false, smsConsent: false, consentVersion: "2026-09-22.v1", website: "", startedAt: Date.now() - 10_000, attribution: {}, }; describe("planVisitSchema", () => { it("accepts a minimal valid submission", () => { expect(planVisitSchema.safeParse(base).success).toBe(true); }); it("requires a first name and a real email", () => { const r = planVisitSchema.safeParse({ ...base, firstName: " ", email: "nope" }); expect(r.success).toBe(false); const errs = issuesToMap(r.success ? [] : r.error.issues); expect(errs.firstName).toBeTruthy(); expect(errs.email).toBeTruthy(); }); it("rejects unknown or launching campuses", () => { expect(planVisitSchema.safeParse({ ...base, campusId: "orlando" }).success).toBe(false); expect(planVisitSchema.safeParse({ ...base, campusId: "mars" }).success).toBe(false); }); it("enforces consent rules: SMS consent requires a valid mobile number", () => { const noPhone = planVisitSchema.safeParse({ ...base, smsConsent: true, phone: "" }); expect(noPhone.success).toBe(false); expect(issuesToMap(noPhone.success ? [] : noPhone.error.issues).phone).toMatch(/mobile number/i); const badPhone = planVisitSchema.safeParse({ ...base, smsConsent: true, phone: "123" }); expect(badPhone.success).toBe(false); const good = planVisitSchema.safeParse({ ...base, smsConsent: true, phone: "(508) 555-0142" }); expect(good.success).toBe(true); }); it("does not bundle consents: email consent alone never needs a phone", () => { expect(planVisitSchema.safeParse({ ...base, emailConsent: true }).success).toBe(true); }); it("consent boxes default to false in the initial state", () => { expect(base.emailConsent).toBe(false); expect(base.smsConsent).toBe(false); }); it("rejects past visit dates and accepts today", () => { expect(planVisitSchema.safeParse({ ...base, visitDate: "2020-01-01" }).success).toBe(false); expect(planVisitSchema.safeParse({ ...base, visitDate: todayIsoDate() }).success).toBe(true); }); it("rejects a filled honeypot", () => { expect(planVisitSchema.safeParse({ ...base, website: "http://spam.example" }).success).toBe(false); }); it("caps children and validates age ranges", () => { expect(planVisitSchema.safeParse({ ...base, children: 3, childAgeRanges: ["0-2", "6-10"] }).success).toBe(true); expect(planVisitSchema.safeParse({ ...base, children: 99 }).success).toBe(false); expect(planVisitSchema.safeParse({ ...base, childAgeRanges: ["30-40"] }).success).toBe(false); }); it("trims free text", () => { const r = planVisitSchema.safeParse({ ...base, firstName: " Sam ", questions: " Is parking free? " }); expect(r.success && r.data.firstName).toBe("Sam"); expect(r.success && r.data.questions).toBe("Is parking free?"); }); }); describe("phone helpers", () => { it("normalizes NANP numbers to E.164", () => { expect(normalizeNanpPhone("(508) 695-1300")).toBe("+15086951300"); expect(normalizeNanpPhone("1-508-695-1300")).toBe("+15086951300"); expect(normalizeNanpPhone("+1 508 695 1300")).toBe("+15086951300"); expect(normalizeNanpPhone("508-695-130")).toBeNull(); expect(normalizeNanpPhone("011-555-1234")).toBeNull(); // area code starting with 0 expect(normalizeNanpPhone("911-555-1234")).toBeNull(); // N11 }); it("formats for display", () => { expect(formatNanpDisplay("+15086951300")).toBe("(508) 695-1300"); }); it("builds an sms: link with an editable, encoded body", () => { const link = smsLink("+15086951300", "Hi! I'm thinking about visiting."); expect(link.startsWith("sms:+15086951300?&body=")).toBe(true); expect(decodeURIComponent(link.split("body=")[1])).toBe("Hi! I'm thinking about visiting."); }); });