deadhead-space-freight-tycoon/apps/web/test/save-ui.test.tsx

105 lines
4.8 KiB
TypeScript

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",
);
});
});