test(work-orders): cover board filters and wizard mappers

This commit is contained in:
Arthur Bassi 2026-07-17 12:02:05 -03:00
parent df8f3553e8
commit e446bbae0e
4 changed files with 383 additions and 0 deletions

View file

@ -0,0 +1,92 @@
import { describe, expect, it } from "vitest";
import { mapWizardDraftToFormValues } from "@/domain/work-orders/mappers/wizard-draft-mapper";
import {
EMPTY_WIZARD_DRAFT,
canContinueWizardStep1,
canCreateWorkOrderFromWizard,
isWizardStepValid,
} from "@/domain/work-orders/types/work-order-wizard";
describe("mapWizardDraftToFormValues", () => {
it("maps minimal draft with type and site to valid form values", () => {
const draft = {
...EMPTY_WIZARD_DRAFT,
type: "PM" as const,
locationId: "42",
locationName: "DFW8 · Dallas, TX",
woNumber: "24818",
assigneeName: "Sarah Chen",
status: "Incomplete" as const,
};
const values = mapWizardDraftToFormValues(draft);
expect(values.title).toBe("PM — #24818");
expect(values.workOrderNumber).toBe("24818");
expect(values.locationId).toBe("42");
expect(values.assignedTo).toBe("Sarah Chen");
expect(values.status).toBe("Open");
expect(values.priority).toBe("Sev 3");
expect(values.source).toBe("PM");
expect(values.createdDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
it("maps emergency type to Sev 1 priority", () => {
const values = mapWizardDraftToFormValues({
...EMPTY_WIZARD_DRAFT,
type: "Emergency",
locationId: "1",
locationName: "Site A",
});
expect(values.priority).toBe("Sev 1");
expect(values.title).toBe("Emergency — Site A");
});
it("includes appointment and notes in description", () => {
const values = mapWizardDraftToFormValues({
...EMPTY_WIZARD_DRAFT,
type: "Reactive",
locationId: "1",
locationName: "Site A",
apptStart: "09:00",
apptEnd: "11:00",
pocNotes: "Call ahead",
vendorName: "Roto-Rooter",
vendorNotes: "Prefers morning",
});
expect(values.description).toContain("Appt:");
expect(values.description).toContain("POC notes: Call ahead");
expect(values.description).toContain("Vendor: Roto-Rooter");
});
});
describe("wizard step validation", () => {
it("blocks step 1 continue without type", () => {
expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(false);
expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(false);
});
it("allows step 1 continue when type is selected", () => {
const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const };
expect(canContinueWizardStep1(draft)).toBe(true);
expect(isWizardStepValid(1, draft)).toBe(true);
});
it("blocks create without location", () => {
const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const };
expect(canCreateWorkOrderFromWizard(draft)).toBe(false);
expect(isWizardStepValid(2, draft)).toBe(false);
});
it("allows create when location is selected", () => {
const draft = {
...EMPTY_WIZARD_DRAFT,
type: "PM" as const,
locationId: "99",
};
expect(canCreateWorkOrderFromWizard(draft)).toBe(true);
expect(isWizardStepValid(2, draft)).toBe(true);
});
});

View file

@ -0,0 +1,103 @@
import { describe, expect, it } from "vitest";
import {
ASSIGNEE_FILTER_UNASSIGNED,
EMPTY_ADVANCED_FILTERS,
type WorkOrderAdvancedFilters,
} from "@/domain/work-orders/types/work-order-advanced-filters";
import {
buildAdvancedFilterChips,
clearAdvancedFilterGroup,
cloneAdvancedFilters,
countAdvancedFilters,
} from "@/domain/work-orders/utils/advanced-filters-utils";
function withSites(...sites: string[]): WorkOrderAdvancedFilters {
return {
...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS),
sites: new Set(sites),
};
}
describe("countAdvancedFilters", () => {
it("returns 0 for default empty filters", () => {
expect(countAdvancedFilters(EMPTY_ADVANCED_FILTERS)).toBe(0);
});
it("counts non-default date range as one active group", () => {
const filters = {
...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS),
dateRange: "last-week" as const,
};
expect(countAdvancedFilters(filters)).toBe(1);
});
it("counts each active filter group once", () => {
const filters = {
...withSites("1", "2"),
types: new Set(["PM" as const]),
statuses: new Set(["Pending" as const]),
};
expect(countAdvancedFilters(filters)).toBe(3);
});
});
describe("buildAdvancedFilterChips", () => {
it("shows site labels when two or fewer sites are selected", () => {
const chips = buildAdvancedFilterChips(withSites("1"), {
siteNameOf: (id) => (id === "1" ? "DFW8" : id),
});
expect(chips).toHaveLength(1);
expect(chips[0]).toMatchObject({
key: "sites",
group: "Site",
value: "DFW8",
});
});
it("shows a count badge when more than two sites are selected", () => {
const chips = buildAdvancedFilterChips(withSites("1", "2", "3"));
expect(chips).toHaveLength(1);
expect(chips[0]).toMatchObject({
key: "sites",
group: "Site",
count: 3,
});
});
it("creates a separate unassigned chip for dispatchers", () => {
const filters = {
...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS),
dispatchers: new Set([ASSIGNEE_FILTER_UNASSIGNED, "42"]),
};
const chips = buildAdvancedFilterChips(filters, {
dispatcherNameOf: (id) => (id === "42" ? "Sarah Chen" : id),
});
expect(
chips.some((chip) => chip.key === "disp-unassigned" && chip.value === "Unassigned"),
).toBe(true);
expect(chips.some((chip) => chip.key === "disp" && chip.value === "Sarah Chen")).toBe(true);
});
});
describe("clearAdvancedFilterGroup", () => {
it("removes only the targeted filter group", () => {
const filters = {
...withSites("1"),
types: new Set(["PM" as const]),
};
const next = clearAdvancedFilterGroup(filters, "sites");
expect(next?.sites.size).toBe(0);
expect(next?.types.has("PM")).toBe(true);
});
it("returns null when clearing the last active group", () => {
const next = clearAdvancedFilterGroup(withSites("1"), "sites");
expect(next).toBeNull();
});
});

View file

@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import {
buildWeekWindow,
compactWeekLabel,
currentWeekMonday,
isFutureWeek,
nextWeekMonday,
weekRange,
} from "@/domain/work-orders/utils/list-week-utils";
import { addDaysIso, mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
describe("list-week-utils", () => {
it("currentWeekMonday returns a Monday ISO date", () => {
const monday = currentWeekMonday();
expect(monday).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(mondayOf(monday)).toBe(monday);
});
it("compactWeekLabel formats Mon–Fri range", () => {
expect(compactWeekLabel("2026-06-01")).toBe("6/1–6/5");
expect(compactWeekLabel("2026-05-25")).toBe("5/25–5/29");
});
it("weekRange returns Monday through Friday", () => {
expect(weekRange("2026-06-01")).toEqual(["2026-06-01", "2026-06-05"]);
});
it("buildWeekWindow generates ordered weeks around anchor", () => {
const weeks = buildWeekWindow("2026-06-01", 2, 2);
expect(weeks.length).toBeGreaterThanOrEqual(3);
expect(weeks[0].mondayIso).toBe("2026-05-18");
expect(weeks.some((week) => week.mondayIso === "2026-06-01")).toBe(true);
expect(weeks.every((week) => week.label.length > 0)).toBe(true);
});
it("buildWeekWindow always includes the next week even when it is in the future", () => {
const upcoming = nextWeekMonday();
const weeks = buildWeekWindow(currentWeekMonday(), 2, 0);
expect(weeks.some((week) => week.mondayIso === upcoming)).toBe(true);
if (isFutureWeek(upcoming)) {
expect(isFutureWeek(upcoming)).toBe(true);
}
});
it("buildWeekWindow always includes the current live week", () => {
const liveMonday = currentWeekMonday();
const weeks = buildWeekWindow("2000-01-03", 2, 0);
expect(weeks.some((week) => week.mondayIso === liveMonday && week.live)).toBe(true);
});
it("isFutureWeek identifies weeks after the current week", () => {
const live = currentWeekMonday();
expect(isFutureWeek(live)).toBe(false);
expect(isFutureWeek("2099-01-05")).toBe(true);
});
});

View file

@ -0,0 +1,132 @@
import { describe, expect, it } from "vitest";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
import {
buildDayGroups,
matchesAdvancedFilters,
splitScheduledUnscheduled,
} from "@/domain/work-orders/utils/table-filter-utils";
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
import { mapWorkOrderTableRow } from "@/domain/work-orders/mappers/work-order-mapper";
function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
return {
id: "1",
woNumber: "12345678901",
site: "SITE01",
type: "PM",
dispatcherId: "u1",
dispatcherName: "Alice",
scheduledOn: "2026-06-23",
targetWeek: "",
dayGroup: "06/23",
dayLabel: "TUE · 06/23",
location: "Main St",
locationId: "10",
pm: "HVAC",
company: "Acme",
tech: "Bob",
techPhone: "",
apptStart: "",
apptEnd: "",
apptTime: "",
status: "Scheduled",
docStatus: "No",
pocName: "",
pocPhone: "",
pocNotes: "",
dueDate: "2026-06-30",
rescheduleCount: 0,
carriedOver: 0,
originalWeek: "",
originalDate: "",
isPastDue: false,
scheduleWeekOnly: false,
mediaCount: 0,
title: "Test WO",
priority: "Sev 3",
...overrides,
};
}
describe("deriveDayFields", () => {
it("derives MM/DD group and weekday label", () => {
expect(deriveDayFields("2026-06-23")).toEqual({
dayGroup: "06/23",
dayLabel: "TUE · 06/23",
});
});
});
describe("splitScheduledUnscheduled", () => {
it("splits rows without scheduledOn into unscheduled", () => {
const scheduled = makeRow({ id: "a", scheduledOn: "2026-06-23" });
const unsched = makeRow({ id: "b", scheduledOn: "" });
const result = splitScheduledUnscheduled([scheduled, unsched]);
expect(result.scheduled).toHaveLength(1);
expect(result.unscheduled).toHaveLength(1);
expect(result.unscheduled[0].id).toBe("b");
});
});
describe("buildDayGroups", () => {
it("groups by dayGroup and sorts chronologically", () => {
const later = makeRow({
id: "2",
scheduledOn: "2026-06-24",
dayGroup: "06/24",
dayLabel: "WED · 06/24",
});
const earlier = makeRow({
id: "1",
scheduledOn: "2026-06-23",
dayGroup: "06/23",
dayLabel: "TUE · 06/23",
});
const groups = buildDayGroups([later, earlier]);
expect(groups).toHaveLength(2);
expect(groups[0].key).toBe("06/23");
expect(groups[1].key).toBe("06/24");
});
});
describe("matchesAdvancedFilters", () => {
it("filters by site and type", () => {
const row = makeRow({ site: "SITE01", type: "PM" });
const filters = {
...EMPTY_ADVANCED_FILTERS,
sites: new Set(["SITE01"]),
types: new Set(["PM" as const]),
};
expect(matchesAdvancedFilters(row, filters)).toBe(true);
const noMatch = {
...filters,
types: new Set(["Emergency" as const]),
};
expect(matchesAdvancedFilters(row, noMatch)).toBe(false);
});
});
describe("mapWorkOrderTableRow", () => {
it("maps API list fields to table row", () => {
const row = mapWorkOrderTableRow({
id: 42,
internalWONumber: "12345678901",
title: "Leak repair",
location: "Building A",
status: "Open",
scheduledDate: "2026-06-23",
dueDate: "2026-06-30",
assignedTo: "Alice",
problem: "Plumbing",
source: "Reactive",
});
expect(row.id).toBe(42);
expect(row.woNumber).toBe("12345678901");
expect(row.scheduledOn).toBe("2026-06-23");
expect(row.dayGroup).toBe("06/23");
expect(row.type).toBe("Reactive");
expect(row.status).toBe("Incomplete");
});
});