shoc-frontend-new/src/test/app/(protected)/locations/location-contacts-field-array.test.tsx
Alexandre Brandizzi 07734d8361
Some checks are pending
Frontend checks / Build and test (push) Waiting to run
Frontend checks / governance (push) Waiting to run
Frontend checks / Visual regression (push) Waiting to run
Deploy dev content / Deploy shoc-frontend-new-dev through Terraform (push) Waiting to run
SH-138: manage ordered site contacts (#191)
* feat(locations): derive site areas from state

* chore(locations): keep ticket keys out of source comments

* feat(locations): manage ordered site contacts

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-16 14:53:58 -03:00

178 lines
5.4 KiB
TypeScript

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { LocationContactsFieldArray } from "@/app/(protected)/locations/_components/location-contacts-field-array";
import {
locationDefaultValues,
locationSchema,
type LocationFormValues,
} from "@/domain/locations/schemas/location-schema";
import { renderWithProviders } from "@/test/test-utils";
const dataTransferStub = {
setData: vi.fn(),
effectAllowed: "none" as DataTransfer["effectAllowed"],
};
function ContactsHarness({
initialContacts,
onSubmit = vi.fn(),
}: {
initialContacts: LocationFormValues["contacts"];
onSubmit?: (values: LocationFormValues) => void;
}) {
const {
control,
handleSubmit,
register,
formState: { errors },
} = useForm<LocationFormValues>({
resolver: zodResolver(locationSchema),
defaultValues: {
...locationDefaultValues,
name: "Dallas DC",
address: "3811 Distribution Dr",
city: "Dallas",
state: "TX",
zipCode: "70068",
contactEmail: "jane@example.com",
contacts: initialContacts,
},
});
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<LocationContactsFieldArray control={control} errors={errors} register={register} />
<button type="submit">Save</button>
</form>
);
}
function nameInputs(): HTMLInputElement[] {
return screen.getAllByLabelText(/^Contact Name/) as HTMLInputElement[];
}
/** 1-based handle position: `handleFor(2)` is the second row's drag handle. */
function handleFor(position: number): HTMLElement {
return screen.getByRole("button", {
name: `Reorder contact ${position}. Use arrow up or down to move.`,
});
}
describe("LocationContactsFieldArray", () => {
it("marks the first row as Main and disables removal when only one remains", () => {
renderWithProviders(
<ContactsHarness initialContacts={[{ name: "Jane", phone: "(421) 433-0032" }]} />,
{ withAuth: false },
);
expect(screen.getByText("Main")).toBeInTheDocument();
expect(screen.getByLabelText(/^Contact Name \(Main\)/)).toHaveValue("Jane");
expect(screen.getByLabelText("Remove contact 1")).toBeDisabled();
});
it("adds and removes contact rows", () => {
renderWithProviders(
<ContactsHarness initialContacts={[{ name: "Jane", phone: "(421) 433-0032" }]} />,
{ withAuth: false },
);
fireEvent.click(screen.getByRole("button", { name: /add contact/i }));
expect(nameInputs()).toHaveLength(2);
expect(screen.getByLabelText("Remove contact 1")).toBeEnabled();
expect(screen.getByText("Main")).toBeInTheDocument();
fireEvent.click(screen.getByLabelText("Remove contact 2"));
expect(nameInputs()).toHaveLength(1);
expect(screen.getByLabelText("Remove contact 1")).toBeDisabled();
});
it("reorders with keyboard ArrowUp and keeps focus on the moved handle", async () => {
renderWithProviders(
<ContactsHarness
initialContacts={[
{ id: 11, name: "Jane", phone: "(421) 433-0032" },
{ id: 12, name: "Bob", phone: "(421) 433-0033" },
]}
/>,
{ withAuth: false },
);
expect(nameInputs().map((input) => input.value)).toEqual(["Jane", "Bob"]);
const bobHandle = handleFor(2);
bobHandle.focus();
fireEvent.keyDown(bobHandle, { key: "ArrowUp" });
expect(nameInputs().map((input) => input.value)).toEqual(["Bob", "Jane"]);
await waitFor(() => {
expect(document.activeElement).toBe(handleFor(1));
});
});
it("ignores ArrowDown on the last row", () => {
renderWithProviders(
<ContactsHarness
initialContacts={[
{ name: "Jane", phone: "(421) 433-0032" },
{ name: "Bob", phone: "(421) 433-0033" },
]}
/>,
{ withAuth: false },
);
fireEvent.keyDown(handleFor(2), { key: "ArrowDown" });
expect(nameInputs().map((input) => input.value)).toEqual(["Jane", "Bob"]);
});
it("reorders with native drag events from the handle", () => {
renderWithProviders(
<ContactsHarness
initialContacts={[
{ name: "Jane", phone: "(421) 433-0032" },
{ name: "Bob", phone: "(421) 433-0033" },
]}
/>,
{ withAuth: false },
);
fireEvent.dragStart(handleFor(1), { dataTransfer: dataTransferStub });
fireEvent.drop(nameInputs()[1].closest("div") as HTMLElement, {
dataTransfer: dataTransferStub,
});
expect(nameInputs().map((input) => input.value)).toEqual(["Bob", "Jane"]);
});
it("submits contacts in displayed order", async () => {
const onSubmit = vi.fn();
renderWithProviders(
<ContactsHarness
initialContacts={[
{ id: 11, name: "Jane", phone: "(421) 433-0032" },
{ id: 12, name: "Bob", phone: "(421) 433-0033" },
]}
onSubmit={onSubmit}
/>,
{ withAuth: false },
);
fireEvent.keyDown(handleFor(2), { key: "ArrowUp" });
fireEvent.click(screen.getByRole("button", { name: "Save" }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledTimes(1);
});
expect(onSubmit.mock.calls[0][0]).toEqual(
expect.objectContaining({
contacts: [
{ id: 12, name: "Bob", phone: "(421) 433-0033" },
{ id: 11, name: "Jane", phone: "(421) 433-0032" },
],
}),
);
});
});