Ctrl K
ComunidadGuías…

src/components/checkin/shared-checkin-page.render.test.tsx

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

Archivo original ↗Preguntar a KiBot
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { SharedCheckInPage } from "./shared-checkin-page";
import { API_BASE_URL } from "@/lib/api-base";
import type { SharedCheckIn } from "@/lib/api/checkin-links";

// The page a desk opens. What it may never do is reach for the session: no signed-in
// client, no auth guard - the token in the URL is the only credential, and a desk's
// phone has no account to offer anyway.
const spies = vi.hoisted(() => ({
  apiGet: vi.fn(),
  useAuth: vi.fn(),
  useAuthGuard: vi.fn(),
}));
vi.mock("@/lib/api/client", async (importOriginal) => ({
  ...(await importOriginal<object>()),
  apiClient: { get: spies.apiGet, post: spies.apiGet, delete: spies.apiGet },
}));
vi.mock("@/contexts/AuthContext", () => ({ useAuth: spies.useAuth }));
vi.mock("@/hooks/useAuthGuard", () => ({ useAuthGuard: spies.useAuthGuard }));

const SUMMARY: SharedCheckIn = {
  expires_at: "2026-09-27T10:00:00Z",
  diver: {
    name: "Sam Reef",
    portrait_sha256: "portrait1",
    units: "imperial",
    email: "desk@example.org",
    date_of_birth: "1988-04-02",
    phone: "+44 7700 900000",
    insurance_policies: [
      { provider: "DAN Europe", number: "P-42", expires_on: null },
      { provider: "DiveAssure", number: "D-7", expires_on: "2027-03-01" },
    ],
    emergency_contacts: [
      { name: "Alex Reef", phone: "+44 7700 900111", relationship: null },
      { name: "Robin Reef", phone: "+44 7700 900222", relationship: "Parent" },
    ],
  },
  diving: { total_dives: 310, max_depth: 39.6, last_dive_on: "2026-08-14" },
  certifications: [
    {
      uuid: "cert-1",
      agency: "padi",
      name: "Rescue Diver",
      instructor_name: "Alex Diver",
      contact_name: "Blue Ocean",
      front_content_type: "image/png",
    },
  ],
};

const fetchMock = vi.fn();
const originalCreate = URL.createObjectURL;
const originalRevoke = URL.revokeObjectURL;

beforeEach(() => {
  URL.createObjectURL = vi.fn(() => "blob:link");
  URL.revokeObjectURL = vi.fn();
  vi.spyOn(console, "error").mockImplementation(() => {});
  fetchMock.mockReset();
  vi.stubGlobal("fetch", fetchMock);
  for (const spy of Object.values(spies)) spy.mockClear();
});

afterEach(() => {
  URL.createObjectURL = originalCreate;
  URL.revokeObjectURL = originalRevoke;
  vi.unstubAllGlobals();
});

const answer = (status: number, body: unknown) =>
  new Response(JSON.stringify(body), { status });

describe("SharedCheckInPage", () => {
  it("draws the diver's sheet from the summary, read-only", async () => {
    fetchMock.mockImplementation(async () => answer(200, SUMMARY));
    const { container } = render(<SharedCheckInPage token="tok" />);

    expect(await screen.findByText("PADI Rescue Diver")).toBeInTheDocument();
    // The figures the link was made with, in the diver's own units.
    expect(screen.getByText("310")).toBeInTheDocument();
    expect(screen.getByText("130 ft")).toBeInTheDocument();
    // The names the link prints where the diver's own page holds uuids.
    expect(screen.getByText("Blue Ocean")).toBeInTheDocument();
    expect(screen.getByText("Alex Diver")).toBeInTheDocument();
    expect(screen.getByText(/This link stops working on/)).toBeInTheDocument();
    // The email the diver gives out, and every policy and contact in their order.
    expect(screen.getByText("desk@example.org")).toBeInTheDocument();
    const text = container.textContent ?? "";
    const order = ["DAN Europe", "DiveAssure", "Alex Reef", "Robin Reef"].map(
      (value) => text.indexOf(value),
    );
    expect(order.every((at) => at >= 0)).toBe(true);
    expect([...order].sort((a, b) => a - b)).toEqual(order);

    expect(
      screen.getAllByRole("button").map((button) => button.textContent),
    ).toEqual(["Print"]);
    await waitFor(() =>
      expect(container.querySelectorAll("img")).toHaveLength(2),
    );

    // The summary and both pictures, every one of them read with the token alone.
    expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
      `${API_BASE_URL}/checkin/tok`,
      `${API_BASE_URL}/checkin/tok/portrait`,
      `${API_BASE_URL}/checkin/tok/certification/cert-1/front`,
    ]);
    for (const [, init] of fetchMock.mock.calls) {
      expect(init.credentials).toBe("omit");
    }
    for (const spy of Object.values(spies)) {
      expect(spy).not.toHaveBeenCalled();
    }
  });

  it("says a dead link is dead, without saying why", async () => {
    fetchMock.mockResolvedValue(answer(404, { detail: "Not found" }));
    render(<SharedCheckInPage token="gone" />);

    expect(
      await screen.findByRole("heading", {
        name: "This check-in link is no longer valid",
      }),
    ).toBeInTheDocument();
    expect(
      screen.getByText(/It has expired or was revoked/),
    ).toBeInTheDocument();
    expect(screen.queryByText("Diver Check-in")).toBeNull();
  });

  it("offers another try when the request itself fails", async () => {
    fetchMock.mockRejectedValueOnce(new TypeError("Failed to fetch"));
    fetchMock.mockResolvedValueOnce(answer(200, SUMMARY));
    render(<SharedCheckInPage token="tok" />);

    await userEvent.click(
      await screen.findByRole("button", { name: "Try again" }),
    );
    expect(await screen.findByText("PADI Rescue Diver")).toBeInTheDocument();
  });
});