Files
dostalgia/frontend/src/pages/__tests__/Play.test.js
T

126 lines
3.9 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/svelte";
import Play from "../Play.svelte";
// Mock the API module that Play imports
vi.mock("../../lib/api.js", () => ({
getGame: vi.fn(),
bundleUrl: vi.fn(),
setupBundleUrl: vi.fn(),
pullSaves: vi.fn(),
pushSaves: vi.fn(),
deleteSaves: vi.fn(),
}));
// Mock router
vi.mock("../../lib/router.js", () => ({
push: vi.fn(),
}));
import * as api from "../../lib/api.js";
function makeGame(overrides = {}) {
return {
id: "game-1",
title: "Commander Keen",
platform: "dos",
ready: true,
...overrides,
};
}
describe("Play", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("shows loading state initially", () => {
api.getGame.mockReturnValue(new Promise(() => {}));
render(Play, { props: { id: "game-1" } });
expect(screen.getByText("Loading game data...")).toBeTruthy();
});
it("shows not-ready state when game is not processed", async () => {
api.getGame.mockResolvedValue(makeGame({ ready: false }));
render(Play, { props: { id: "game-1" } });
await vi.waitFor(() => {
expect(screen.getByText("Not ready")).toBeTruthy();
});
});
it("shows unsupported warning for Windows games", async () => {
api.getGame.mockResolvedValue(
makeGame({ platform: "windows", ready: true })
);
render(Play, { props: { id: "game-1" } });
await vi.waitFor(() => {
expect(screen.getByText("Windows game")).toBeTruthy();
});
});
it("shows error box when game fetch fails", async () => {
api.getGame.mockRejectedValue(new Error("Game not found"));
render(Play, { props: { id: "missing" } });
// The error should appear in the overlay as well
await vi.waitFor(() => {
expect(screen.getByText("⚠️")).toBeTruthy();
});
});
it("shows emulator booting state for ready DOS games", async () => {
api.getGame.mockResolvedValue(
makeGame({ platform: "dos", ready: true })
);
api.bundleUrl.mockReturnValue("/games/game-1.jsdos");
render(Play, { props: { id: "game-1" } });
await vi.waitFor(() => {
expect(screen.getByText("Starting emulator...")).toBeTruthy();
});
});
it("starts js-dos with server-backed save hooks", async () => {
api.getGame.mockResolvedValue(makeGame());
api.bundleUrl.mockReturnValue("/games/game-1.jsdos");
const dosMock = vi.fn().mockReturnValue({
save: vi.fn().mockResolvedValue(true),
stop: vi.fn().mockResolvedValue(undefined),
});
window.Dos = dosMock;
try {
render(Play, { props: { id: "game-1" } });
await vi.waitFor(() => {
expect(dosMock).toHaveBeenCalled();
});
const opts = dosMock.mock.calls[0][1];
expect(opts.url).toBe("/games/game-1.jsdos");
expect(opts.autoSave).toBe(true);
// Both normal and setup bundles map to the same per-game save key
await expect(opts.fsChanges.urlToKey("/games/game-1.jsdos")).resolves.toBe("game-1");
// pull() forwards to the API and returns null when there is no save
api.pullSaves.mockResolvedValue(null);
await expect(opts.fsChanges.pull("game-1")).resolves.toBeNull();
expect(api.pullSaves).toHaveBeenCalledWith("game-1");
// push()/delete() forward to the API
api.pushSaves.mockResolvedValue({ status: "saved" });
await opts.fsChanges.push("game-1", new Uint8Array([1]));
expect(api.pushSaves).toHaveBeenCalledWith("game-1", expect.any(Uint8Array));
api.deleteSaves.mockResolvedValue({ status: "deleted" });
await opts.fsChanges.delete("game-1");
expect(api.deleteSaves).toHaveBeenCalledWith("game-1");
// pull() survives API errors so the game still boots
api.pullSaves.mockRejectedValue(new Error("offline"));
await expect(opts.fsChanges.pull("game-1")).resolves.toBeNull();
} finally {
delete window.Dos;
}
});
});