import { screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it, vi } from "vitest"; import { ApiError } from "../src/api/client"; import { fakeApi, renderApp, samplePlayerState, unauthorized } from "./render"; const CODE = "ABCDE-FGHJK-MNPQR-STUVW"; const owner = () => samplePlayerState({ tutorial: null }); const restored = () => samplePlayerState({ tutorial: null, company: { id: "c2", name: "Restored Freight", credits: 77_000, loanBalance: 0 }, }); async function openRestore() { await userEvent.click(await screen.findByRole("button", { name: "Restore a company" })); return screen.getByRole("dialog", { name: "Restore a company" }); } describe("backup and restore (Step 10K.9)", () => { afterEach(() => vi.unstubAllGlobals()); it("shows a new code grouped, and Copy puts it on the clipboard", async () => { const writeText = vi.fn(() => Promise.resolve()); Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true }); renderApp( fakeApi({ getPlayerState: owner, getBackupCode: () => ({ hasCode: false, createdAt: null }), createBackupCode: () => ({ code: CODE, createdAt: 1 }), }), "/settings", ); await userEvent.click(await screen.findByRole("button", { name: "Back up company" })); const sheet = screen.getByRole("dialog", { name: "Back up your company" }); await userEvent.click( await within(sheet).findByRole("button", { name: "Create a backup code" }), ); expect(await within(sheet).findByLabelText("Backup code")).toHaveTextContent(CODE); expect(sheet).toHaveTextContent("Anyone with this code can open your company."); await userEvent.click(within(sheet).getByRole("button", { name: "Copy" })); expect(writeText).toHaveBeenCalledWith(CODE); expect(await within(sheet).findByRole("button", { name: "Copied" })).toBeInTheDocument(); }); it("warns about an existing code before replacing it", async () => { renderApp( fakeApi({ getPlayerState: owner, getBackupCode: () => ({ hasCode: true, createdAt: 1 }), }), "/settings", ); await userEvent.click(await screen.findByRole("button", { name: "Back up company" })); const sheet = screen.getByRole("dialog", { name: "Back up your company" }); expect( await within(sheet).findByRole("button", { name: "Generate a new code" }), ).toBeInTheDocument(); expect(sheet).toHaveTextContent("Your old code will stop working"); }); it("warns that a company past the tutorial is left behind, and not during the tutorial", async () => { const { unmount } = renderApp(fakeApi({ getPlayerState: owner }), "/settings"); expect(await openRestore()).toHaveTextContent("current company will be left behind"); unmount(); renderApp(fakeApi({ getPlayerState: () => samplePlayerState() }), "/settings"); expect(await openRestore()).not.toHaveTextContent("left behind"); }); it("restores and goes to HQ", async () => { let done = false; const api = fakeApi({ getPlayerState: () => (done ? restored() : owner()), restoreCompany: () => { done = true; return { playerId: "p2", state: restored() }; }, }); renderApp(api, "/settings"); const sheet = await openRestore(); await userEvent.type(within(sheet).getByRole("textbox"), CODE.toLowerCase()); await userEvent.click(within(sheet).getByRole("button", { name: "Restore" })); await waitFor(() => expect(api.call).toHaveBeenCalledWith("restoreCompany", { code: CODE.toLowerCase() }), ); expect(await screen.findByRole("heading", { level: 1, name: "HQ" })).toBeInTheDocument(); expect(screen.getByLabelText("Credits")).toHaveTextContent("77,000 cr"); }); it("shows a failed restore's error and stays put", async () => { renderApp( fakeApi({ getPlayerState: owner, restoreCompany: () => { throw new ApiError(409, "restore-failed", "That code didn't open a company."); }, }), "/settings", ); const sheet = await openRestore(); await userEvent.type(within(sheet).getByRole("textbox"), "WRONG"); await userEvent.click(within(sheet).getByRole("button", { name: "Restore" })); expect(await within(sheet).findByRole("alert")).toHaveTextContent("didn't open a company"); expect(screen.getByRole("dialog", { name: "Restore a company" })).toBeInTheDocument(); }); it("offers restore on the start screen too", async () => { let done = false; renderApp( fakeApi({ getPlayerState: () => (done ? restored() : unauthorized()), restoreCompany: () => { done = true; return { playerId: "p2", state: restored() }; }, }), ); const sheet = await openRestore(); expect(sheet).not.toHaveTextContent("left behind"); await userEvent.type(within(sheet).getByRole("textbox"), CODE); await userEvent.click(within(sheet).getByRole("button", { name: "Restore" })); expect(await screen.findByRole("heading", { level: 1, name: "HQ" })).toBeInTheDocument(); }); });