shoc-frontend-new/src/test/domain/vendors/use-cases/use-save-vendor-company-roster.test.ts
arthur.bassi a05e768115 fix(work-orders): address Bugbot findings on slide-over and vendor roster
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 18:35:28 -03:00

142 lines
4.2 KiB
TypeScript

import { createElement, type ReactNode } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
getSingleStatusOnlyChange,
useSaveVendorCompanyRoster,
} from "@/domain/vendors/use-cases/use-save-vendor-company-roster";
import type { VendorCompanyRoster } from "@/domain/vendors/types/vendor";
const updateVendor = vi.fn();
vi.mock("@/domain/vendors/api/vendors-api", () => ({
vendorsApi: {
update: (...args: unknown[]) => updateVendor(...args),
},
}));
vi.mock("@/domain/vendors/api/vendor-company-roster-api", () => ({
vendorCompanyRosterApi: {
update: vi.fn(),
create: vi.fn(),
},
}));
vi.mock("react-toastify", () => ({
toast: { success: vi.fn(), error: vi.fn() },
}));
const roster: VendorCompanyRoster = {
companyId: 10,
rowVersion: "rv-1",
name: "Legacy Plumbing",
companyPhone: "555-1234 ext 9",
email: "dispatch@example.test",
address: "1 Main St",
city: "St. Louis",
state: "MO",
zip: "63101",
googleMapsUrl: "http://maps.example.test/legacy",
notes: "Legacy values remain untouched",
technicians: [
{
id: 7,
contactName: "Taylor",
phone: "555-0199 ext 2",
email: "taylor@example.test",
preferredContact: "Phone",
tradeSpecialties: "Plumbing",
isActive: false,
totalJobs: 12,
},
],
};
const values = {
name: roster.name,
companyPhone: roster.companyPhone,
email: roster.email,
address: roster.address,
city: roster.city,
state: roster.state,
zip: roster.zip,
googleMapsUrl: roster.googleMapsUrl,
notes: roster.notes,
technicians: roster.technicians.map((technician) => ({
id: technician.id,
contactName: technician.contactName,
phone: technician.phone,
email: technician.email,
preferredContact: technician.preferredContact,
tradeSpecialties: technician.tradeSpecialties,
isActive: technician.isActive,
})),
};
describe("getSingleStatusOnlyChange", () => {
it("preserves the sparse legacy status path when exactly one technician status changes", () => {
expect(
getSingleStatusOnlyChange(roster, {
...values,
technicians: [{ ...values.technicians[0], isActive: true }],
}),
).toEqual({ id: 7, isActive: true });
});
it("requires aggregate reconciliation when company or technician data changes", () => {
expect(getSingleStatusOnlyChange(roster, { ...values, notes: "changed" })).toBeNull();
expect(
getSingleStatusOnlyChange(roster, {
...values,
technicians: [{ ...values.technicians[0], contactName: "Renamed", isActive: true }],
}),
).toBeNull();
});
});
describe("useSaveVendorCompanyRoster status-only deactivation gate", () => {
beforeEach(() => {
updateVendor.mockReset();
updateVendor.mockResolvedValue(undefined);
});
it("rejects sparse Active→Inactive updates and still allows reactivation", async () => {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const wrapper = ({ children }: { children: ReactNode }) =>
createElement(QueryClientProvider, { client }, children);
const { result } = renderHook(() => useSaveVendorCompanyRoster(), { wrapper });
const activeRoster: VendorCompanyRoster = {
...roster,
technicians: [{ ...roster.technicians[0], isActive: true }],
};
await expect(
result.current.mutateAsync({
mode: "update",
values: {
...values,
technicians: [{ ...values.technicians[0], isActive: false }],
},
companyId: 10,
rowVersion: "rv-1",
originalRoster: activeRoster,
}),
).rejects.toThrow("Use Deactivate to check open work orders before inactivating a technician.");
expect(updateVendor).not.toHaveBeenCalled();
await result.current.mutateAsync({
mode: "update",
values: {
...values,
technicians: [{ ...values.technicians[0], isActive: true }],
},
companyId: 10,
rowVersion: "rv-1",
originalRoster: roster,
});
expect(updateVendor).toHaveBeenCalledWith(7, { isActive: true });
});
});