WFDASH-WEBAPP/src/components/company-form.test.tsx
SJCoetz 7a68cafe35 chore: add logos, opencode config and build assets
This commit includes the temporary Next.js build cache files, public brand
logos, and project configuration added in this change.
2026-07-27 08:21:20 +02:00

155 lines
5 KiB
TypeScript

import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { CompanyForm } from "./company-form";
const { createMock, updateMock, toastErrorMock, toastSuccessMock, findCompanyDuplicatesMock } = vi.hoisted(() => ({
createMock: vi.fn(),
updateMock: vi.fn(),
toastErrorMock: vi.fn(),
toastSuccessMock: vi.fn(),
findCompanyDuplicatesMock: vi.fn(),
}));
vi.mock("@/lib/pocketbase", () => ({
__esModule: true,
default: () => ({
collection: () => ({
create: createMock,
update: updateMock,
}),
}),
}));
vi.mock("sonner", () => ({
toast: {
error: toastErrorMock,
success: toastSuccessMock,
},
}));
vi.mock("@/lib/pb-helpers", () => ({
findCompanyDuplicates: findCompanyDuplicatesMock,
}));
describe("CompanyForm", () => {
beforeEach(() => {
createMock.mockReset();
updateMock.mockReset();
toastErrorMock.mockReset();
toastSuccessMock.mockReset();
findCompanyDuplicatesMock.mockReset();
// Default: no duplicates found. Tests that need a duplicate
// override this with `mockResolvedValueOnce` / `mockResolvedValue`.
findCompanyDuplicatesMock.mockResolvedValue([]);
});
it("saves company email and telephone fields", async () => {
createMock.mockResolvedValue({ id: "co1" });
const onSaved = vi.fn();
const onOpenChange = vi.fn();
render(
<CompanyForm
open
onOpenChange={onOpenChange}
onSaved={onSaved}
/>
);
const form = screen.getByRole("dialog").querySelector("form")!;
fireEvent.change(within(form).getByPlaceholderText("e.g. Acme Corp"), {
target: { value: "Acme" },
});
fireEvent.change(within(form).getByPlaceholderText("accounts@acme.co.za"), {
target: { value: "accounts@acme.co.za" },
});
fireEvent.change(within(form).getByPlaceholderText("e.g. 011 555 0100"), {
target: { value: "011 555 0100" },
});
fireEvent.click(within(form).getByRole("button", { name: "Create Company" }));
await waitFor(() => {
expect(createMock).toHaveBeenCalledWith({
company: "Acme",
address: "",
vendor: "",
company_email: "accounts@acme.co.za",
company_tel: "011 555 0100",
coordinates: { lon: 0, lat: 0 },
});
});
});
it("blocks save when an exact-case-insensitive duplicate exists and lets the operator override", async () => {
// Simulate PB returning an existing record whose name matches the
// form value when both are lower-cased. The submit handler should
// pop the duplicate dialog instead of writing the record.
findCompanyDuplicatesMock.mockResolvedValue([
{ id: "co-existing", company: "ACME" } as never,
]);
const onSaved = vi.fn();
const onOpenChange = vi.fn();
render(
<CompanyForm open onOpenChange={onOpenChange} onSaved={onSaved} />
);
// Scope to the visible form — the base-ui Sheet/Dialog infrastructure
// leaves inert focus-guard nodes in the DOM that match a global
// `getByPlaceholderText` query, so we narrow the lookup to the
// <form> element the form renders.
const form = screen.getByRole("dialog").querySelector("form")!;
fireEvent.change(within(form).getByPlaceholderText("e.g. Acme Corp"), {
target: { value: "acme" },
});
fireEvent.click(within(form).getByRole("button", { name: "Create Company" }));
// Wait for the dialog to appear — that's the proof the block fired.
// The dialog renders the existing match.
await waitFor(() => {
expect(screen.getByText("ACME")).toBeInTheDocument();
});
// PB write was NOT called.
expect(createMock).not.toHaveBeenCalled();
// Click "Save anyway" — the override should land the write.
fireEvent.click(screen.getByRole("button", { name: /save anyway/i }));
await waitFor(() => {
expect(createMock).toHaveBeenCalledWith(
expect.objectContaining({ company: "acme" })
);
});
});
it("excludes the current record from the duplicate check in edit mode", async () => {
// The form is bound to an existing record. Typing the same name
// shouldn't flag itself. We assert that the helper was called with
// the record's id as `excludeId`.
const existing = { id: "co-self", company: "Acme" };
findCompanyDuplicatesMock.mockResolvedValue([]);
render(
<CompanyForm
open
onOpenChange={() => undefined}
onSaved={() => undefined}
company={existing as never}
/>
);
const form = screen.getByRole("dialog").querySelector("form")!;
fireEvent.change(within(form).getByPlaceholderText("e.g. Acme Corp"), {
target: { value: "Acme" },
});
// The effect debounces by 300ms, so we wait for the helper call
// to land with the record's id as the excludeId argument.
await waitFor(() => {
expect(findCompanyDuplicatesMock).toHaveBeenCalledWith(
"Acme",
"co-self"
);
});
});
});