This commit includes the temporary Next.js build cache files, public brand logos, and project configuration added in this change.
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { SupplierForm } from "./supplier-form";
|
|
|
|
/**
|
|
* Tests for the SupplierForm's duplicate-detection flow. There was no
|
|
* prior supplier-form test file, so this also covers the basic save
|
|
* path that the company/customer tests already exercise — we want
|
|
* the same shape of safety net for every entity that grew the dup
|
|
* check.
|
|
*/
|
|
|
|
const { createMock, findSupplierDuplicatesMock } = vi.hoisted(() => ({
|
|
createMock: vi.fn(),
|
|
findSupplierDuplicatesMock: 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", () => ({
|
|
findSupplierDuplicates: findSupplierDuplicatesMock,
|
|
}));
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
findSupplierDuplicatesMock.mockResolvedValue([]);
|
|
});
|
|
|
|
describe("SupplierForm", () => {
|
|
it("blocks save when an existing supplier has the same name and lets the operator override", async () => {
|
|
// Form is filling out "West Rand". PB returns an existing row
|
|
// with the same name (case-insensitive). Submit should pop the
|
|
// duplicate dialog instead of writing.
|
|
findSupplierDuplicatesMock.mockResolvedValueOnce([
|
|
{ id: "sup-existing", suppliername: "West Rand" } as never,
|
|
]);
|
|
createMock.mockResolvedValue({ id: "sup1" });
|
|
|
|
render(
|
|
<SupplierForm
|
|
open
|
|
onOpenChange={() => undefined}
|
|
onSaved={() => undefined}
|
|
/>
|
|
);
|
|
|
|
const form = screen.getByRole("dialog").querySelector("form")!;
|
|
fireEvent.change(within(form).getByPlaceholderText("e.g. West Rand Industrial"), {
|
|
target: { value: "west rand" },
|
|
});
|
|
fireEvent.click(within(form).getByRole("button", { name: "Create Supplier" }));
|
|
|
|
// Wait for the dialog to show the existing match.
|
|
await waitFor(() => {
|
|
expect(screen.getByText("West Rand")).toBeInTheDocument();
|
|
});
|
|
// The write was NOT performed yet.
|
|
expect(createMock).not.toHaveBeenCalled();
|
|
|
|
// "Save anyway" should land the write with the typed value.
|
|
fireEvent.click(screen.getByRole("button", { name: /save anyway/i }));
|
|
await waitFor(() => {
|
|
expect(createMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({ suppliername: "west rand" })
|
|
);
|
|
});
|
|
});
|
|
|
|
it("excludes the current record from the duplicate check in edit mode", async () => {
|
|
// Bound to an existing supplier; typing the same name shouldn't
|
|
// flag itself. We assert the helper was called with the record's
|
|
// id as the excludeId.
|
|
const existing = { id: "sup-self", suppliername: "West Rand" };
|
|
findSupplierDuplicatesMock.mockResolvedValue([]);
|
|
render(
|
|
<SupplierForm
|
|
open
|
|
onOpenChange={() => undefined}
|
|
onSaved={() => undefined}
|
|
supplier={existing as never}
|
|
/>
|
|
);
|
|
|
|
const form = screen.getByRole("dialog").querySelector("form")!;
|
|
fireEvent.change(within(form).getByPlaceholderText("e.g. West Rand Industrial"), {
|
|
target: { value: "West Rand" },
|
|
});
|
|
|
|
// Live check debounces by 300ms — wait for the helper call.
|
|
await waitFor(() => {
|
|
expect(findSupplierDuplicatesMock).toHaveBeenCalledWith(
|
|
"West Rand",
|
|
"sup-self"
|
|
);
|
|
});
|
|
});
|
|
});
|