This commit includes the temporary Next.js build cache files, public brand logos, and project configuration added in this change.
148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
|
|
import { CustomerForm } from "./customer-form";
|
|
|
|
const {
|
|
createMock,
|
|
findCustomerDuplicatesMock,
|
|
getAllCompaniesMock,
|
|
} = vi.hoisted(() => ({
|
|
createMock: vi.fn(),
|
|
findCustomerDuplicatesMock: vi.fn(),
|
|
getAllCompaniesMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/pocketbase", () => ({
|
|
__esModule: true,
|
|
default: () => ({
|
|
collection: () => ({
|
|
create: createMock,
|
|
update: vi.fn(),
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("sonner", () => ({
|
|
toast: { error: vi.fn(), success: vi.fn() },
|
|
}));
|
|
|
|
vi.mock("@/lib/pb-helpers", () => ({
|
|
getAllCompanies: getAllCompaniesMock,
|
|
findCustomerDuplicates: findCustomerDuplicatesMock,
|
|
}));
|
|
|
|
vi.mock("@/components/ui/company-search", () => ({
|
|
CompanySearch: () => <div>Company Search</div>,
|
|
}));
|
|
|
|
vi.mock("@/components/ui/user-search", () => ({
|
|
UserSearch: () => <div>User Search</div>,
|
|
}));
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
getAllCompaniesMock.mockResolvedValue([]);
|
|
findCustomerDuplicatesMock.mockResolvedValue([]);
|
|
});
|
|
|
|
// The form's useEffect on mount calls `getAllCompanies()` to populate
|
|
// the company combobox. The pre-existing test (delivery-coords
|
|
// position) never awaited that promise — it just used `render` — and
|
|
// the form reads `companies.length` in a separate effect. Provide a
|
|
// safe default before each test.
|
|
beforeAll(() => {
|
|
getAllCompaniesMock.mockResolvedValue([]);
|
|
});
|
|
|
|
describe("CustomerForm", () => {
|
|
it("shows delivery coordinates in the contact section before details", () => {
|
|
render(
|
|
<CustomerForm
|
|
open
|
|
onOpenChange={() => undefined}
|
|
onSaved={() => undefined}
|
|
/>
|
|
);
|
|
|
|
const content = document.body.textContent ?? "";
|
|
expect(content.indexOf("Delivery Coordinates")).toBeGreaterThan(content.indexOf("Contact"));
|
|
expect(content.indexOf("Delivery Coordinates")).toBeLessThan(content.indexOf("Details"));
|
|
});
|
|
|
|
it("blocks save when an existing customer has the same (name, company) pair and lets the operator override", async () => {
|
|
// Form is filling out (John Smith, company=acme-id, email=).
|
|
// PB returns an existing customer with the same name+company —
|
|
// a real collision. Submit should pop the duplicate dialog.
|
|
findCustomerDuplicatesMock.mockResolvedValueOnce([
|
|
{
|
|
id: "cu-existing",
|
|
customer: "John Smith",
|
|
company: "acme-id",
|
|
email: "",
|
|
coordinates: { lon: 0, lat: 0 },
|
|
} as never,
|
|
]);
|
|
createMock.mockResolvedValue({ id: "cu1" });
|
|
|
|
render(
|
|
<CustomerForm
|
|
open
|
|
onOpenChange={() => undefined}
|
|
onSaved={() => undefined}
|
|
/>
|
|
);
|
|
|
|
const form = screen.getByRole("dialog").querySelector("form")!;
|
|
fireEvent.change(within(form).getByPlaceholderText("Full name or business name"), {
|
|
target: { value: "john smith" },
|
|
});
|
|
fireEvent.click(within(form).getByRole("button", { name: "Create Customer" }));
|
|
|
|
// Wait for the dialog to show the existing match.
|
|
await waitFor(() => {
|
|
expect(screen.getByText("John Smith")).toBeInTheDocument();
|
|
});
|
|
// The write was NOT performed yet.
|
|
expect(createMock).not.toHaveBeenCalled();
|
|
|
|
// "Save anyway" should land the write.
|
|
fireEvent.click(screen.getByRole("button", { name: /save anyway/i }));
|
|
await waitFor(() => {
|
|
expect(createMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({ customer: "john smith" })
|
|
);
|
|
});
|
|
});
|
|
|
|
it("passes the current record's id as excludeId in edit mode", async () => {
|
|
// The form is bound to an existing customer. The duplicate check
|
|
// must exclude the record being edited so it doesn't flag itself.
|
|
const existing = { id: "cu-self", customer: "John Smith" };
|
|
findCustomerDuplicatesMock.mockResolvedValue([]);
|
|
render(
|
|
<CustomerForm
|
|
open
|
|
onOpenChange={() => undefined}
|
|
onSaved={() => undefined}
|
|
customer={existing as never}
|
|
/>
|
|
);
|
|
|
|
const form = screen.getByRole("dialog").querySelector("form")!;
|
|
fireEvent.change(within(form).getByPlaceholderText("Full name or business name"), {
|
|
target: { value: "John Smith" },
|
|
});
|
|
|
|
// The live check debounces by 300ms — wait for the helper to be
|
|
// called with the record's id as the excludeId.
|
|
await waitFor(() => {
|
|
expect(findCustomerDuplicatesMock).toHaveBeenCalledWith(
|
|
"John Smith",
|
|
"",
|
|
"",
|
|
"cu-self"
|
|
);
|
|
});
|
|
});
|
|
});
|