shoc-frontend-new/src/test/app/(protected)/workorders/vendor-dialog-stable-identity.test.tsx

230 lines
6.4 KiB
TypeScript

import { fireEvent, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { VendorDialog } from "@/app/(protected)/workorders/_components/list/table/cells/vendor-dialog";
import type { Vendor } from "@/domain/vendors/types/vendor";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import { renderWithProviders } from "@/test/test-utils";
const mutateAsync = vi.fn();
vi.mock("@/domain/vendors/use-cases/use-create-vendor", () => ({
useCreateVendor: () => ({
mutateAsync,
isPending: false,
}),
}));
const existingVendor: VendorDropdownItem = {
id: 10,
companyName: "Gateway Plumbing",
contactName: "Jordan Lee",
tradeSpecialties: "Plumbing",
address: "1 Main St",
distanceMiles: 4.2,
email: "dispatch@gateway.test",
};
const createdVendor: Vendor = {
id: 99,
companyId: 10,
companyName: "Gateway Plumbing",
contactName: "Jordan Lee",
email: "dispatch@gateway.test",
phone: "(314) 555-9999",
companyPhone: "",
googleMapsUrl: "",
notes: "",
totalJobs: 0,
address: "1 Main St",
city: "",
state: "",
zip: "",
tradeSpecialties: "Plumbing",
isActive: true,
preferredContact: "Phone",
};
describe("VendorDialog stable technician identity", () => {
beforeEach(() => {
mutateAsync.mockReset();
mutateAsync.mockResolvedValue(createdVendor);
});
it("keeps draft identity on same-name collision and posts live phone on save", async () => {
const onSave = vi.fn();
const onOpenChange = vi.fn();
renderWithProviders(
<VendorDialog
open={true}
onOpenChange={onOpenChange}
vendorId={10}
company="Gateway Plumbing"
tech="Jordan Lee"
techPhone="(314) 555-0100"
vendors={[existingVendor]}
onSave={onSave}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /\+ add technician/i }));
fireEvent.change(screen.getByPlaceholderText("Full name"), {
target: { value: "Jordan Lee" },
});
fireEvent.change(screen.getByPlaceholderText("(000) 000-0000"), {
target: { value: "3145550199" },
});
fireEvent.click(screen.getByRole("button", { name: /^add$/i }));
// Draft stays staged: add form collapses and select still shows the colliding name.
expect(screen.getByRole("button", { name: /\+ add technician/i })).toBeTruthy();
expect(screen.getByRole("button", { name: "Jordan Lee" })).toBeTruthy();
fireEvent.change(screen.getByDisplayValue("3145550199"), {
target: { value: "(314) 555-9999" },
});
fireEvent.click(screen.getByRole("button", { name: /^save$/i }));
await waitFor(() => {
expect(mutateAsync).toHaveBeenCalledTimes(1);
});
expect(mutateAsync).toHaveBeenCalledWith(
expect.objectContaining({
contactName: "Jordan Lee",
companyName: "Gateway Plumbing",
phone: "(314) 555-9999",
}),
);
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
vendorId: "99",
tech: "Jordan Lee",
company: "Gateway Plumbing",
techPhone: "(314) 555-9999",
}),
);
});
// Must not persist the pre-existing colliding vendor id.
expect(onSave.mock.calls[0]?.[0].vendorId).not.toBe("10");
});
it("does not clear staged draft when technicianOptions rebuild after add", async () => {
const onSave = vi.fn();
mutateAsync.mockResolvedValue({
...createdVendor,
id: 88,
contactName: "Casey New",
phone: "3145550188",
});
renderWithProviders(
<VendorDialog
open={true}
onOpenChange={vi.fn()}
vendorId={11}
company="Gateway Plumbing"
tech="Adam Whyte"
techPhone=""
vendors={[
existingVendor,
{
...existingVendor,
id: 11,
contactName: "Adam Whyte",
},
]}
onSave={onSave}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /\+ add technician/i }));
fireEvent.change(screen.getByPlaceholderText("Full name"), {
target: { value: "Casey New" },
});
fireEvent.change(screen.getByPlaceholderText("(000) 000-0000"), {
target: { value: "3145550188" },
});
fireEvent.click(screen.getByRole("button", { name: /^add$/i }));
expect(screen.getByRole("button", { name: "Casey New" })).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: /^save$/i }));
await waitFor(() => {
expect(mutateAsync).toHaveBeenCalledTimes(1);
});
expect(mutateAsync).toHaveBeenCalledWith(
expect.objectContaining({
contactName: "Casey New",
phone: "3145550188",
}),
);
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
vendorId: "88",
tech: "Casey New",
}),
);
});
});
it("keeps a staged draft when the same company is picked again", async () => {
const onSave = vi.fn();
mutateAsync.mockResolvedValue({
...createdVendor,
id: 88,
contactName: "Casey New",
phone: "3145550188",
});
renderWithProviders(
<VendorDialog
open={true}
onOpenChange={vi.fn()}
vendorId={10}
company="Gateway Plumbing"
tech="Jordan Lee"
techPhone="(314) 555-0100"
vendors={[existingVendor]}
onSave={onSave}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /\+ add technician/i }));
fireEvent.change(screen.getByPlaceholderText("Full name"), {
target: { value: "Casey New" },
});
fireEvent.change(screen.getByPlaceholderText("(000) 000-0000"), {
target: { value: "3145550188" },
});
fireEvent.click(screen.getByRole("button", { name: /^add$/i }));
fireEvent.click(screen.getByRole("button", { name: "Gateway Plumbing" }));
const companyButtons = await screen.findAllByRole("button", { name: "Gateway Plumbing" });
fireEvent.click(companyButtons[companyButtons.length - 1]!);
fireEvent.click(screen.getByRole("button", { name: /^save$/i }));
await waitFor(() => {
expect(mutateAsync).toHaveBeenCalledWith(
expect.objectContaining({
contactName: "Casey New",
phone: "3145550188",
}),
);
});
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
vendorId: "88",
tech: "Casey New",
}),
);
});
});