shoc-frontend-new/src/test/app/(protected)/workorders/wizard-vendor-inline-technician.test.tsx
Codex Review Integration abe28a3a7f Merge remote-tracking branch 'origin/dev' into fix/ab/sh-364-inline-technician-registration
# Conflicts:
#	src/app/(protected)/workorders/_components/wizard/use-new-wo-wizard-controller.ts
#	src/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select.tsx
#	src/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time.tsx
#	src/test/app/(protected)/workorders/wizard-service-clears-vendor.test.tsx
2026-09-17 15:27:39 -03:00

260 lines
8.5 KiB
TypeScript

import { act, fireEvent, render, renderHook, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { WizardStepVendorTime } from "@/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time";
import { useWizardCreateWithTechnicians } from "@/app/(protected)/workorders/_components/wizard/use-wizard-create-with-technicians";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import {
EMPTY_WIZARD_DRAFT,
type WorkOrderWizardDraft,
} from "@/domain/work-orders/types/work-order-wizard";
const mutateAsync = vi.fn();
vi.mock("@/domain/vendors/use-cases/use-add-technicians-to-company", () => ({
useAddTechniciansToCompany: () => ({ mutateAsync, isPending: false }),
}));
vi.mock("react-toastify", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
const vendors: VendorDropdownItem[] = [
{
id: 41,
companyName: "Vinewood LLC",
contactName: "Adam Whyte",
tradeSpecialties: "HVAC",
address: "1 Market St",
distanceMiles: null,
},
{
id: 52,
companyName: "Gateway Plumbing",
contactName: "Casey Pipe",
tradeSpecialties: "Plumbing",
address: "",
distanceMiles: null,
},
];
function draftWith(patch: Partial<WorkOrderWizardDraft>): WorkOrderWizardDraft {
return { ...EMPTY_WIZARD_DRAFT, type: "PM", status: "Incomplete", ...patch };
}
describe("WizardStepVendorTime inline technician (SH-364)", () => {
it("hides the add action until a company is picked", () => {
render(
<WizardStepVendorTime
draft={draftWith({})}
vendors={vendors}
onUpdate={vi.fn()}
onPatch={vi.fn()}
/>,
);
expect(screen.queryByRole("button", { name: "+ Add technician" })).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /pick a company first/i })).toBeDisabled();
});
it("stages a new technician row for the picked company", () => {
const onPatch = vi.fn();
render(
<WizardStepVendorTime
draft={draftWith({ vendorCompany: "Vinewood LLC" })}
vendors={vendors}
onUpdate={vi.fn()}
onPatch={onPatch}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "+ Add technician" }));
expect(onPatch).toHaveBeenCalledWith({ newTechnicians: [{ name: "", phone: "" }] });
});
it("scopes technicians to the picked company", () => {
render(
<WizardStepVendorTime
draft={draftWith({
vendorCompany: "Vinewood LLC",
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
})}
vendors={vendors}
onUpdate={vi.fn()}
onPatch={vi.fn()}
/>,
);
expect(screen.getByRole("group", { name: "New Technician 1" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /select technician/i }));
expect(screen.getByRole("button", { name: "Adam Whyte" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Casey Pipe" })).not.toBeInTheDocument();
});
it("clears the picked vendor and staged technicians when the company changes", () => {
const onPatch = vi.fn();
render(
<WizardStepVendorTime
draft={draftWith({
vendorCompany: "Vinewood LLC",
vendorId: "41",
vendorName: "Vinewood LLC",
techPhone: "(314) 555-0100",
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }],
})}
vendors={vendors}
onUpdate={vi.fn()}
onPatch={onPatch}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Vinewood LLC" }));
fireEvent.click(screen.getByRole("button", { name: "Gateway Plumbing" }));
expect(onPatch).toHaveBeenCalledWith({
vendorCompany: "Gateway Plumbing",
vendorId: "",
vendorName: "",
techPhone: "",
vendorNotes: "",
newTechnicians: [],
});
});
it("preserves the picked vendor and staged technicians when the same company is re-picked", () => {
const onPatch = vi.fn();
render(
<WizardStepVendorTime
draft={draftWith({
vendorCompany: "Vinewood LLC",
vendorId: "41",
vendorName: "Vinewood LLC",
techPhone: "(314) 555-0100",
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }],
})}
vendors={vendors}
onUpdate={vi.fn()}
onPatch={onPatch}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Vinewood LLC" }));
const companyOptions = screen.getAllByRole("button", { name: "Vinewood LLC" });
fireEvent.click(companyOptions[companyOptions.length - 1]);
expect(onPatch).toHaveBeenCalledWith({ vendorCompany: "Vinewood LLC" });
});
});
describe("useWizardCreateWithTechnicians (SH-364)", () => {
beforeEach(() => {
mutateAsync.mockReset();
});
it("creates the work order directly when no technician was staged", () => {
const createMutation = { mutate: vi.fn(), isPending: false };
const { result } = renderHook(() =>
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
);
const draft = draftWith({ vendorCompany: "Vinewood LLC", newTechnicians: [] });
result.current.mutate(draft);
expect(mutateAsync).not.toHaveBeenCalled();
expect(createMutation.mutate).toHaveBeenCalledWith(draft);
});
it("persists staged technicians first and assigns a single new technician", async () => {
mutateAsync.mockResolvedValue([{ ...vendors[0], id: 99, contactName: "Jordan Lee" }]);
const createMutation = { mutate: vi.fn(), isPending: false };
const patchDraft = vi.fn();
const { result } = renderHook(() =>
useWizardCreateWithTechnicians({ vendors, patchDraft, createMutation }),
);
await act(async () => {
result.current.mutate(
draftWith({
vendorCompany: "Vinewood LLC",
newTechnicians: [
{ name: "Jordan Lee", phone: "(314) 555-0199" },
{ name: " ", phone: "" },
],
}),
);
});
expect(mutateAsync).toHaveBeenCalledWith({
anchorVendorId: 41,
technicians: [
{ contactName: "Jordan Lee", phone: "(314) 555-0199", tradeSpecialties: "HVAC" },
],
});
const expectedPatch = {
newTechnicians: [],
vendorId: "99",
vendorName: "Vinewood LLC",
techPhone: "(314) 555-0199",
};
expect(patchDraft).toHaveBeenCalledWith(expectedPatch);
expect(createMutation.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedPatch));
});
it("blocks Create and skips the roster save when a staged phone is not a valid number", async () => {
const createMutation = { mutate: vi.fn(), isPending: false };
const { result } = renderHook(() =>
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
);
await act(async () => {
result.current.mutate(
draftWith({
vendorCompany: "Vinewood LLC",
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-01" }],
}),
);
});
expect(mutateAsync).not.toHaveBeenCalled();
expect(createMutation.mutate).not.toHaveBeenCalled();
});
it("allows a staged technician with a blank phone, matching the modal path", async () => {
mutateAsync.mockResolvedValue([{ ...vendors[0], id: 99, contactName: "Jordan Lee" }]);
const createMutation = { mutate: vi.fn(), isPending: false };
const { result } = renderHook(() =>
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
);
await act(async () => {
result.current.mutate(
draftWith({
vendorCompany: "Vinewood LLC",
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
}),
);
});
expect(mutateAsync).toHaveBeenCalledWith({
anchorVendorId: 41,
technicians: [{ contactName: "Jordan Lee", phone: "", tradeSpecialties: "HVAC" }],
});
expect(createMutation.mutate).toHaveBeenCalled();
});
it("does not create the work order when the technician save fails", async () => {
mutateAsync.mockRejectedValue(new Error("boom"));
const createMutation = { mutate: vi.fn(), isPending: false };
const { result } = renderHook(() =>
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
);
await act(async () => {
result.current.mutate(
draftWith({
vendorCompany: "Vinewood LLC",
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
}),
);
});
expect(createMutation.mutate).not.toHaveBeenCalled();
});
});