Ctrl K
ComunidadGuías…

src/components/checkin/checkin-details-form.render.test.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { User } from "@/lib/api/auth";
import type {
  CheckinDetails,
  CheckinDetailsUpdate,
} from "@/lib/api/checkin-details";
import type { PictureEdit } from "@/lib/picture-edits";
import { CheckinDetailsForm } from "./checkin-details-form";
import { UserFieldsSubmitButton } from "@/components/user/user-fields-form";

// The portrait rides on About You's Save as the avatar does on Profile Information's
// (see `user-fields-form.render.test.tsx`); what is particular here is the dialog: a
// picture that failed must not close it, or the pick it keeps goes with it.

const auth = vi.hoisted(() => ({
  user: { uuid: "user-1", name: "Jane Doe", email: "jane@example.com" } as User,
  refreshUser: vi.fn(),
}));
vi.mock("@/contexts/AuthContext", () => ({ useAuth: () => auth }));

const checkin = vi.hoisted(() => ({
  details: {
    email: null,
    phone: null,
    date_of_birth: null,
    emergency_contacts: [],
    insurance_policies: [],
  } as CheckinDetails,
  loadFailed: false,
  reload: vi.fn(),
  save: vi.fn(),
}));
vi.mock("@/contexts/CheckinDetailsContext", () => ({
  useCheckinDetails: () => checkin,
}));

vi.mock("@/lib/api/auth", () => ({
  authAPI: { updateProfile: vi.fn(), removePicture: vi.fn() },
}));

// The field's own picking and cropping is `UserFieldsForm`'s test; here it only has to
// hand the form an edit and show which one it holds.
vi.mock("@/components/user/picture-field", () => ({
  PictureField: ({
    edit,
    onChange,
  }: {
    edit: PictureEdit | null;
    onChange: (edit: PictureEdit | null) => void;
  }) => (
    <div>
      <button type="button" onClick={() => onChange({ kind: "remove" })}>
        Remove portrait
      </button>
      <span>pending: {edit?.kind ?? "none"}</span>
    </div>
  ),
}));

const toast = vi.fn();
vi.mock("@/components/ui/use-toast", () => ({
  useToast: () => ({ toast }),
}));

const { authAPI } = await import("@/lib/api/auth");

let sent: string[] = [];
const onSaved = vi.fn();

beforeEach(() => {
  sent = [];
  Object.assign(checkin.details, { email: null });
  checkin.save
    .mockReset()
    .mockImplementation(async (patch: CheckinDetailsUpdate) => {
      sent.push("patch details");
      Object.assign(checkin.details, patch);
      return checkin.details;
    });
  auth.refreshUser.mockReset().mockResolvedValue(undefined);
  vi.mocked(authAPI.removePicture)
    .mockReset()
    .mockImplementation(async () => {
      sent.push("delete");
      return { message: "ok" };
    });
  toast.mockReset();
  onSaved.mockReset();
});

function renderForm() {
  return render(
    <CheckinDetailsForm group="about" picture="portrait" onSaved={onSaved}>
      <UserFieldsSubmitButton />
    </CheckinDetailsForm>,
  );
}

const save = () =>
  userEvent.click(screen.getByRole("button", { name: /save changes/i }));

describe("a portrait that fails after the details saved", () => {
  it("stays picked, keeps the dialog open, and the next Save sends only it", async () => {
    vi.mocked(authAPI.removePicture).mockRejectedValueOnce(new Error("nope"));
    renderForm();

    await userEvent.type(screen.getByLabelText("Email"), "jane@dive.example");
    await userEvent.click(
      screen.getByRole("button", { name: "Remove portrait" }),
    );
    await save();

    await waitFor(() =>
      expect(toast).toHaveBeenCalledWith(
        expect.objectContaining({
          title: "Saved, but your portrait did not",
        }),
      ),
    );
    expect(sent).toEqual(["patch details"]);
    expect(screen.getByText("pending: remove")).toBeInTheDocument();
    expect(onSaved).not.toHaveBeenCalled();

    await save();

    await waitFor(() => expect(sent).toEqual(["patch details", "delete"]));
    expect(screen.getByText("pending: none")).toBeInTheDocument();
    expect(onSaved).toHaveBeenCalledTimes(1);
  });
});