import { DeviceClock, GameEngine, MemoryBackend, SaveStorage } from "@deadhead/engine"; import { screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createEngineClient } from "../src/api/engine-client"; import { saveFile } from "../src/screens/SaveSettings"; import { renderApp } from "./render"; /** A device with its own storage, running the real engine. */ function device() { const engine = new GameEngine({ storage: new SaveStorage(new MemoryBackend()), clock: new DeviceClock(Date.now), }); return { engine, api: createEngineClient(engine) }; } async function exportFrom(path = "/settings") { const home = device(); await home.engine.call("createGuest", {}); const writeText = vi.fn((_: string) => Promise.resolve()); Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true }); const view = renderApp(home.api, path); await userEvent.click(await screen.findByRole("button", { name: "Export save" })); const sheet = screen.getByRole("dialog", { name: "Export save" }); const box = await within(sheet).findByLabelText("Save code"); await userEvent.click(within(sheet).getByRole("button", { name: "Copy" })); expect(await within(sheet).findByRole("button", { name: "Copied" })).toBeInTheDocument(); const code = (box as HTMLTextAreaElement).value; expect(writeText).toHaveBeenCalledWith(code); view.unmount(); return { home, code }; } describe("export and import save (Step 14.10)", () => { afterEach(() => vi.unstubAllGlobals()); it("exports a code that opens the same company on a fresh device", async () => { const { home, code } = await exportFrom(); const before = await home.engine.call("getPlayerState", {}); const fresh = device(); renderApp(fresh.api, "/"); await userEvent.click(await screen.findByRole("button", { name: "Import save" })); const sheet = screen.getByRole("dialog", { name: "Import save" }); // Nothing to replace on a fresh device, so no warning. expect(within(sheet).queryByRole("note")).toBeNull(); await userEvent.click(within(sheet).getByRole("textbox")); await userEvent.paste(code); await userEvent.click(within(sheet).getByRole("button", { name: "Import" })); await userEvent.click(within(sheet).getByRole("button", { name: "Open company" })); await waitFor(() => expect(screen.queryByRole("dialog", { name: "Import save" })).toBeNull()); expect(await fresh.engine.call("getPlayerState", {})).toEqual(before); }); it("opens a save file, warns before replacing, then replaces the company", async () => { const { home, code } = await exportFrom(); const other = device(); await other.engine.call("createGuest", {}); renderApp(other.api, "/settings"); await userEvent.click(await screen.findByRole("button", { name: "Import save" })); const sheet = screen.getByRole("dialog", { name: "Import save" }); expect(within(sheet).getByRole("note")).toHaveTextContent( "This replaces the company on this device.", ); const input = sheet.querySelector('input[type="file"]') as HTMLInputElement; await userEvent.upload(input, saveFile(code)); await waitFor(() => expect(within(sheet).getByRole("textbox")).toHaveValue(code)); await userEvent.click(within(sheet).getByRole("button", { name: "Import" })); expect(sheet).toHaveTextContent("Replace your company with this save?"); await userEvent.click(within(sheet).getByRole("button", { name: "Replace company" })); await waitFor(() => expect(screen.queryByRole("dialog", { name: "Import save" })).toBeNull()); expect((await other.engine.call("getPlayerState", {})).company).toEqual( (await home.engine.call("getPlayerState", {})).company, ); }); it("shows an error for garbage and changes nothing", async () => { const own = device(); await own.engine.call("createGuest", {}); const before = await own.engine.call("getPlayerState", {}); renderApp(own.api, "/settings"); await userEvent.click(await screen.findByRole("button", { name: "Import save" })); const sheet = screen.getByRole("dialog", { name: "Import save" }); await userEvent.type(within(sheet).getByRole("textbox"), "not a save at all"); await userEvent.click(within(sheet).getByRole("button", { name: "Import" })); await userEvent.click(within(sheet).getByRole("button", { name: "Replace company" })); expect(await within(sheet).findByRole("alert")).toHaveTextContent( "That isn't a Deadhead save code.", ); expect(await own.engine.call("getPlayerState", {})).toEqual(before); }); it("names the save file by date", () => { expect(saveFile("x", new Date("2101-02-03T04:05:06Z")).name).toBe( "deadhead-save-2101-02-03.txt", ); }); });