mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 13:32:06 +00:00
161 lines
6.8 KiB
TypeScript
161 lines
6.8 KiB
TypeScript
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { VendorCreateModal } from "@/app/(protected)/vendors/_components/vendor-create-modal";
|
|
import { VendorRosterFormFields } from "@/app/(protected)/vendors/_components/vendor-roster-form-fields";
|
|
import {
|
|
VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE,
|
|
emptyVendorCompanyRosterForm,
|
|
vendorCompanyRosterSchema,
|
|
type VendorCompanyRosterFormValues,
|
|
} from "@/domain/vendors/schemas/vendor-roster-schema";
|
|
import { renderWithProviders } from "@/test/test-utils";
|
|
|
|
vi.mock("@/domain/vendors/use-cases/use-vendor-facets", () => ({
|
|
useVendorFacets: () => ({ data: { companies: [], trades: [] }, isLoading: false }),
|
|
}));
|
|
|
|
vi.mock("@/domain/vendors/use-cases/use-save-vendor-company-roster", async () => {
|
|
const actual = await vi.importActual<
|
|
typeof import("@/domain/vendors/use-cases/use-save-vendor-company-roster")
|
|
>("@/domain/vendors/use-cases/use-save-vendor-company-roster");
|
|
return {
|
|
...actual,
|
|
useSaveVendorCompanyRoster: () => ({ mutate: vi.fn(), isPending: false }),
|
|
};
|
|
});
|
|
|
|
function PlainCompanyFieldsHarness() {
|
|
const { control, handleSubmit, formState } = useForm<VendorCompanyRosterFormValues>({
|
|
defaultValues: emptyVendorCompanyRosterForm,
|
|
resolver: zodResolver(vendorCompanyRosterSchema),
|
|
mode: "onChange",
|
|
});
|
|
return (
|
|
<form onSubmit={handleSubmit(() => {})}>
|
|
<VendorRosterFormFields control={control} errors={formState.errors} />
|
|
</form>
|
|
);
|
|
}
|
|
|
|
function renderCreateModal() {
|
|
renderWithProviders(<VendorCreateModal open={true} onClose={vi.fn()} />, {
|
|
route: "/vendors",
|
|
withAuth: false,
|
|
});
|
|
}
|
|
|
|
async function fillCompanyNameAndSubmit(name = "Gateway Plumbing") {
|
|
fireEvent.change(screen.getByRole("combobox", { name: "Company (required)" }), {
|
|
target: { value: name },
|
|
});
|
|
await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
|
|
}
|
|
|
|
describe("VendorCreateModal validation", () => {
|
|
it("explains the company phone-or-email requirement after submission", async () => {
|
|
renderCreateModal();
|
|
await fillCompanyNameAndSubmit();
|
|
|
|
expect(await screen.findByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).toBeInTheDocument();
|
|
});
|
|
|
|
it("labels the autocomplete company field exactly, required, with no generated asterisk", () => {
|
|
renderCreateModal();
|
|
|
|
const company = screen.getByRole("combobox", { name: "Company (required)" });
|
|
expect(company).toBeRequired();
|
|
expect(screen.getAllByText("Company (required)")).toHaveLength(2);
|
|
expect(screen.queryByText("*")).not.toBeInTheDocument();
|
|
expect(document.querySelector(".MuiFormLabel-asterisk")).toBeNull();
|
|
expect(company.closest(".MuiFormControl-root")?.querySelector("legend")).toHaveTextContent(
|
|
/^Company \(required\)$/,
|
|
);
|
|
});
|
|
|
|
it("labels the plain company field exactly, required, with no generated asterisk", () => {
|
|
renderWithProviders(<PlainCompanyFieldsHarness />, { route: "/vendors", withAuth: false });
|
|
|
|
const company = screen.getByRole("textbox", { name: "Company (required)" });
|
|
expect(company).toBeRequired();
|
|
expect(screen.getAllByText("Company (required)")).toHaveLength(2);
|
|
expect(screen.queryByText("*")).not.toBeInTheDocument();
|
|
expect(document.querySelector(".MuiFormLabel-asterisk")).toBeNull();
|
|
expect(company.closest(".MuiFormControl-root")?.querySelector("legend")).toHaveTextContent(
|
|
/^Company \(required\)$/,
|
|
);
|
|
});
|
|
|
|
it("shows the phone-or-email error once without marking either input individually invalid", async () => {
|
|
renderCreateModal();
|
|
await fillCompanyNameAndSubmit();
|
|
|
|
const message = await screen.findByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE);
|
|
expect(message).toHaveAttribute("id");
|
|
expect(screen.getAllByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).toHaveLength(1);
|
|
|
|
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
|
|
const email = screen.getByRole("textbox", { name: "Email (optional)" });
|
|
expect(phone).not.toHaveAttribute("aria-invalid", "true");
|
|
expect(email).not.toHaveAttribute("aria-invalid", "true");
|
|
expect(phone.getAttribute("aria-describedby")).toContain(message.getAttribute("id"));
|
|
expect(email.getAttribute("aria-describedby")).toContain(message.getAttribute("id"));
|
|
});
|
|
|
|
it("marks only the phone input invalid when the phone is malformed", async () => {
|
|
renderCreateModal();
|
|
await fillCompanyNameAndSubmit();
|
|
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
|
|
const email = screen.getByRole("textbox", { name: "Email (optional)" });
|
|
|
|
await userEvent.type(phone, "314");
|
|
await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
|
|
|
|
expect(await screen.findByText("Enter a 10-digit phone number")).toBeInTheDocument();
|
|
expect(phone).toHaveAttribute("aria-invalid", "true");
|
|
expect(email).not.toHaveAttribute("aria-invalid", "true");
|
|
expect(screen.queryByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("marks only the email input invalid when the email is malformed", async () => {
|
|
renderCreateModal();
|
|
await fillCompanyNameAndSubmit();
|
|
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
|
|
const email = screen.getByRole("textbox", { name: "Email (optional)" });
|
|
|
|
await userEvent.type(email, "not-an-email");
|
|
await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
|
|
|
|
expect(await screen.findByText("Invalid email")).toBeInTheDocument();
|
|
expect(email).toHaveAttribute("aria-invalid", "true");
|
|
expect(phone).not.toHaveAttribute("aria-invalid", "true");
|
|
expect(screen.queryByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("clears the phone-or-email error once a valid phone is entered", async () => {
|
|
renderCreateModal();
|
|
await fillCompanyNameAndSubmit();
|
|
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
|
|
|
|
expect(await screen.findByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).toBeInTheDocument();
|
|
|
|
await userEvent.type(phone, "3145550100");
|
|
await waitFor(() =>
|
|
expect(screen.queryByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).not.toBeInTheDocument(),
|
|
);
|
|
});
|
|
|
|
it("shows the notes limit and live character counter", async () => {
|
|
renderCreateModal();
|
|
|
|
const notes = screen.getByRole("textbox", { name: "Notes (optional)" });
|
|
fireEvent.change(notes, { target: { value: "abc" } });
|
|
|
|
expect(notes).toHaveAttribute("maxlength", "500");
|
|
await waitFor(() =>
|
|
expect(notes).toHaveAccessibleDescription(/3\/500 characters · Maximum 500 characters/),
|
|
);
|
|
});
|
|
});
|