mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 10:42:04 +00:00
Some checks are pending
* 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>
178 lines
5.4 KiB
TypeScript
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" },
|
|
],
|
|
}),
|
|
);
|
|
});
|
|
});
|