import { describe, expect, it } from "vitest"; import { buildSlideOverPatch, isSlideOverDraftDirty, SLIDE_OVER_EDIT_KEYS, } from "@/domain/work-orders/utils/slide-over-draft"; import { EMPTY_UPLIFT_SUMMARY } from "@/domain/work-orders/types/work-order-uplift"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import { mergeAutoSchedulePatch } from "@/domain/work-orders/utils/wo-lifecycle-transitions"; function baseRow(overrides: Partial = {}): WorkOrderTableRow { return { id: 1, woNumber: "1001", site: "SITE-A", type: "PM", severity: null, dispatcherId: "10", dispatcherName: "Alex", scheduledOn: "2026-07-16", completedDate: "", targetWeek: "", dayGroup: "2026-07-16", dayLabel: "Thu", location: "Main", locationId: 5, pm: "Plumbing", serviceNotes: "", extraServices: [], vendorId: "v1", company: "Acme", tech: "Sam", techPhone: "", apptStart: "", apptEnd: "", apptTime: "09:00", status: "Incomplete", docStatus: "", pocName: "", pocPhone: "", pocNotes: "", additionalContacts: [], dueDate: "2026-07-20", rescheduleCount: 0, carriedOver: 0, isAddOn: false, originalWeek: "", originalDate: "", isPastDue: false, scheduleWeekOnly: false, mediaCount: 0, title: "", priority: "", primaryDispatchId: null, rowVersion: "1", dispatchRowVersion: "", flagColor: null, upliftSummary: EMPTY_UPLIFT_SUMMARY, ...overrides, }; } describe("slide-over-draft", () => { it("exposes only board-persistable edit keys", () => { expect(SLIDE_OVER_EDIT_KEYS).toContain("woNumber"); expect(SLIDE_OVER_EDIT_KEYS).toContain("status"); expect(SLIDE_OVER_EDIT_KEYS).toContain("vendorId"); expect(SLIDE_OVER_EDIT_KEYS).toContain("additionalContacts"); expect(SLIDE_OVER_EDIT_KEYS).toContain("avetaRequired"); expect(SLIDE_OVER_EDIT_KEYS).not.toContain("pocName"); expect(SLIDE_OVER_EDIT_KEYS).not.toContain("locationId"); expect(SLIDE_OVER_EDIT_KEYS).not.toContain("techPhone"); expect(SLIDE_OVER_EDIT_KEYS).not.toContain("company"); expect(SLIDE_OVER_EDIT_KEYS).not.toContain("tech"); }); it("tracks vendor via vendorId only (company/tech are display companions)", () => { const row = baseRow(); const draft = baseRow({ company: "Other Co", tech: "Pat" }); expect(isSlideOverDraftDirty(draft, row)).toBe(false); expect(buildSlideOverPatch(draft, row)).toEqual({}); const vendorDraft = baseRow({ vendorId: "v2", company: "Other Co", tech: "Pat" }); expect(isSlideOverDraftDirty(vendorDraft, row)).toBe(true); expect(buildSlideOverPatch(vendorDraft, row)).toEqual({ vendorId: "v2" }); }); it("ignores POC-only edits for dirty state", () => { const row = baseRow(); const draft = baseRow({ pocName: "Jordan", pocPhone: "555-0100" }); expect(isSlideOverDraftDirty(draft, row)).toBe(false); expect(buildSlideOverPatch(draft, row)).toEqual({}); }); it("detects dirty draft when a tracked field changes", () => { const row = baseRow(); const draft = baseRow({ scheduledOn: "2026-07-17" }); expect(isSlideOverDraftDirty(draft, row)).toBe(true); }); it("clears client schedule range overlays in the save patch", () => { const row = baseRow({ scheduleEnd: "2026-07-18", schedMode: "range", }); const draft = baseRow({ scheduledOn: "", scheduleEnd: "", schedMode: undefined, }); expect(buildSlideOverPatch(draft, row)).toEqual({ scheduledOn: "", scheduleEnd: "", schedMode: undefined, }); }); it("is not dirty when values match", () => { const row = baseRow(); expect(isSlideOverDraftDirty(row, row)).toBe(false); }); it("builds a patch with only changed fields", () => { const row = baseRow(); const draft = baseRow({ woNumber: "1002", status: "Completed", extraServices: ["HVAC"], }); expect(buildSlideOverPatch(draft, row)).toEqual({ woNumber: "1002", status: "Completed", extraServices: ["HVAC"], }); }); it("derives Pending on persist when a Scheduled draft clears Schedule On", () => { const baseline = baseRow({ status: "Scheduled", scheduledOn: "2026-07-16", scheduleWeekOnly: false, targetWeek: "", scheduleEnd: "", schedMode: "date", }); const draft = baseRow({ status: "Scheduled", scheduledOn: "", scheduleWeekOnly: false, targetWeek: "", scheduleEnd: "", schedMode: undefined, }); const patch = buildSlideOverPatch(draft, baseline); expect(patch).toEqual({ scheduledOn: "", schedMode: undefined, }); expect(patch.status).toBeUndefined(); expect(mergeAutoSchedulePatch(baseline, patch)).toEqual( expect.objectContaining({ scheduledOn: "", status: "Pending", }), ); }); it("tracks ordered additionalContacts for dirty state and patch (SH-218)", () => { const row = baseRow(); const contacts = [{ name: "Backup", phone: "555-0101", notes: "Gate" }]; const draft = baseRow({ additionalContacts: contacts }); expect(isSlideOverDraftDirty(draft, row)).toBe(true); expect(buildSlideOverPatch(draft, row)).toEqual({ additionalContacts: contacts }); expect(isSlideOverDraftDirty(baseRow({ additionalContacts: [] }), row)).toBe(false); }); });