shoc-frontend-new/src/test/domain/work-orders/utils/slide-over-draft.test.ts
Arthur Bassi c71d9482ec feat(work-orders): align Schedule On clear with shared demotion path
Board Clear uses empty-week patch; slide-over Save derives Pending via mergeAutoSchedulePatch.
2026-09-09 15:44:49 -03:00

179 lines
5.4 KiB
TypeScript

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> = {}): 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);
});
});