Ctrl K
ComunidadGuías…

src/components/auth/restore-account-card.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 { RestoreAccountCard } from "./restore-account-card";

// The screen is an offer, and the offer is the only thing standing between a diver
// and a logbook that gets erased on a date they can read here. So what is worth
// pinning is that the date survives to the screen in every shape the API can send it,
// that the click actually spends the token it was handed, and that a refusal is shown
// rather than swallowed - a failed restore leaves the account exactly as deleted as it
// was, and the diver has to be told which failure they hit.

const { router, restoreAccount, auth } = vi.hoisted(() => ({
  router: { push: vi.fn() },
  restoreAccount: vi.fn(),
  auth: {
    restore: null as {
      restoreToken: string;
      email: string;
      purgeAfter: string | null;
    } | null,
  },
}));

vi.mock("next/navigation", () => ({ useRouter: () => router }));
vi.mock("@/contexts/AuthContext", () => ({
  useAuth: () => ({ restore: auth.restore, restoreAccount }),
}));

const OFFER = {
  restoreToken: "res-1",
  email: "diver@example.com",
  // Midday UTC on purpose: the date renders in the reader's own timezone, and a
  // midnight one would land on a different day either side of the world.
  purgeAfter: "2026-09-04T12:00:00Z",
};

beforeEach(() => {
  vi.clearAllMocks();
  auth.restore = OFFER;
  restoreAccount.mockResolvedValue(undefined);
});

const pageText = () => document.body.textContent ?? "";
const restoreButton = () =>
  screen.getByRole("button", { name: /restore my account/i });

describe("the restore offer", () => {
  it("names the account and the day it stops being recoverable", () => {
    render(<RestoreAccountCard />);

    expect(pageText()).toContain("diver@example.com");
    expect(pageText()).toContain("September");
    expect(pageText()).toContain("2026");
    expect(pageText()).toContain("Your dives, dive drafts, dive sites");
  });

  // The API leaves `purge_after` out for a row flagged with no clock to count from.
  // The offer still stands - it just cannot name a day, and must not render one.
  it("still offers the account back with no date to name", () => {
    auth.restore = { ...OFFER, purgeAfter: null };

    render(<RestoreAccountCard />);

    expect(pageText()).not.toContain("Invalid Date");
    expect(pageText()).toContain("Your dives, dive drafts, dive sites");
    expect(restoreButton()).toBeVisible();
  });

  it("spends the token it was handed and signs the diver back in", async () => {
    const user = userEvent.setup();
    render(<RestoreAccountCard />);

    await user.click(restoreButton());

    expect(restoreAccount).toHaveBeenCalledWith("res-1");
    await waitFor(() => expect(router.push).toHaveBeenCalledWith("/home"));
  });

  // The one refusal a diver most needs stated plainly: the purge ran while the offer
  // was on screen. A generic "please try again" would send them looking for another
  // way in that cannot exist.
  it("shows the API's own explanation when the account is already gone", async () => {
    restoreAccount.mockRejectedValue({
      response: {
        data: {
          detail:
            "This account has already been permanently deleted and cannot be restored.",
        },
      },
    });
    const user = userEvent.setup();
    render(<RestoreAccountCard />);

    await user.click(restoreButton());

    expect(await screen.findByRole("alert")).toHaveTextContent(
      /already been permanently deleted/i,
    );
    expect(router.push).not.toHaveBeenCalled();
  });

  // Nothing to offer means nothing to render: the page above sends the visitor back
  // to sign in again, and a card drawing an empty offer in the meantime would ask
  // them to click a button holding no token.
  it("renders nothing without an offer", () => {
    auth.restore = null;

    const { container } = render(<RestoreAccountCard />);

    expect(container).toBeEmptyDOMElement();
  });
});