deadhead-space-freight-tycoon/apps/web/test/notifications.test.ts

86 lines
3.4 KiB
TypeScript

import {
noNotifications,
type NotificationScheduler,
type ScheduledNotification,
} from "@deadhead/engine";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createEngineClient } from "../src/api/engine-client";
import { createGame } from "../src/platform/game";
import { platformNotifications } from "../src/platform/notifications";
import { DEFAULT_PREFERENCES, usePreferences } from "../src/state/preferences";
class FakeScheduler implements NotificationScheduler {
scheduled: ScheduledNotification[] | null = null;
permissionAsked = 0;
async replaceAll(list: ScheduledNotification[]) {
this.scheduled = list;
}
async requestPermission() {
this.permissionAsked++;
}
}
afterEach(() => usePreferences.setState(DEFAULT_PREFERENCES));
describe("local notifications on the device (Step 14.9)", () => {
it("uses the no-op scheduler on the web, and playing still works", async () => {
expect(platformNotifications()).toBe(noNotifications);
const game = createGame();
await game.lock.acquire();
const { state } = await game.engine.call("createGuest", {});
expect(state.tutorial).not.toBeNull();
await expect(game.engine.requestNotificationPermission()).resolves.toBeUndefined();
});
it("reschedules when the Settings toggle changes", async () => {
const scheduler = new FakeScheduler();
const game = createGame(scheduler);
await game.lock.acquire();
await game.engine.call("createGuest", {});
const [contract] = (await game.engine.call("getTutorialBoard", {})).contracts;
const { dispatch } = await game.engine.call("createDispatch", {
kind: "tutorial",
contractId: contract!.id,
});
expect(scheduler.scheduled).toMatchObject([{ at: dispatch.arrivalAt }]);
usePreferences.getState().set({ notifications: false });
await vi.waitFor(() => expect(scheduler.scheduled).toEqual([]));
usePreferences.getState().set({ notifications: true });
await vi.waitFor(() => expect(scheduler.scheduled).toMatchObject([{ at: dispatch.arrivalAt }]));
});
it("asks for permission after the deal, not before", async () => {
const scheduler = new FakeScheduler();
const game = createGame(scheduler);
await game.lock.acquire();
const api = createEngineClient(game.engine);
await api.call("createGuest", {});
expect(scheduler.permissionAsked).toBe(0);
// The deal itself is covered by the engine's tests; here it just succeeds.
vi.spyOn(game.engine, "call").mockResolvedValueOnce({} as never);
await api.call("acceptDeal", {});
await vi.waitFor(() => expect(scheduler.permissionAsked).toBe(1));
});
it("tells the skill point pop-up when the permission prompt is answered (Step 17.6)", async () => {
let answer!: () => void;
const scheduler = new FakeScheduler();
scheduler.requestPermission = () => new Promise<void>((resolve) => (answer = resolve));
const game = createGame(scheduler);
await game.lock.acquire();
const api = createEngineClient(game.engine);
// Before the deal there is nothing to wait for.
await expect(api.notificationPrompt!()).resolves.toBeUndefined();
vi.spyOn(game.engine, "call").mockResolvedValueOnce({} as never);
await api.call("acceptDeal", {});
let answered = false;
void api.notificationPrompt!().then(() => (answered = true));
await Promise.resolve();
expect(answered).toBe(false);
answer();
await vi.waitFor(() => expect(answered).toBe(true));
});
});