import type { PhaseString } from "#app/@types/phase-types"; import { Phase } from "#app/phase"; import type { Constructor } from "#app/utils/common"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { beforeAll, beforeEach, describe, expect, it } from "vitest"; let phaserGame: Phaser.Game; let game: GameManager; abstract class mockPhase extends Phase { public override readonly phaseName: any; override start() { this.end(); } } class bananaPhase extends mockPhase { public readonly phaseName = "bananaPhase"; } class applePhase extends mockPhase { public readonly phaseName = "applePhase"; } class oneSecTimerPhase extends mockPhase { public readonly phaseName = "oneSecTimerPhase"; override start() { setInterval(() => { super.start(); }, 1000); } } class unshifterPhase extends mockPhase { public readonly phaseName = "unshifterPhase"; override start() { game.scene.phaseManager.unshiftPhase(new bananaPhase() as unknown as Phase); game.scene.phaseManager.unshiftPhase(new applePhase() as unknown as Phase); super.start(); } } // reduce timeout so failed tests don't hang as long describe("Utils - Phase Interceptor", { timeout: 4000 }, () => { beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, }); }); beforeEach(() => { game = new GameManager(phaserGame); setPhases(bananaPhase, applePhase, oneSecTimerPhase, unshifterPhase, bananaPhase); }); function setPhases(...phases: Constructor[]) { game.scene.phaseManager.clearAllPhases(); game.scene.phaseManager.phaseQueue = phases.map(m => new m()); game.scene.phaseManager.shiftPhase(); // start the thing going } function getQueuedPhases(): string[] { return game.scene.phaseManager["phaseQueuePrepend"] .concat(game.scene.phaseManager.phaseQueue) .map(p => p.phaseName); } function getCurrentPhaseName(): string { return game.scene.phaseManager.getCurrentPhase()?.phaseName ?? "null"; } /** Wrapper function to make TS not complain about `PhaseString` stuff */ function to(phaseName: string, runTarget = true) { return game.phaseInterceptor.to(phaseName as unknown as PhaseString, runTarget); } describe("to", () => { it("should run the specified phase and halt after it ends", async () => { await to("bananaPhase"); expect(getCurrentPhaseName()).toBe("applePhase"); expect(getQueuedPhases()).toEqual(["oneSecTimerPhase", "unshifterPhase", "bananaPhase"]); expect(game.phaseInterceptor.log).toEqual(["bananaPhase"]); }); it("should run to the specified phase without starting/logging", async () => { await to("bananaPhase", false); expect(getCurrentPhaseName()).toBe("bananaPhase"); expect(getQueuedPhases()).toEqual(["applePhase", "oneSecTimerPhase", "unshifterPhase", "bananaPhase"]); expect(game.phaseInterceptor.log).toHaveLength(0); }); it("should start all phases between start and target", async () => { await to("oneSecTimerPhase"); expect(getQueuedPhases()).toEqual(["unshifterPhase", "bananaPhase"]); expect(game.phaseInterceptor.log).toEqual(["bananaPhase", "applePhase", "oneSecTimerPhase"]); }); it("should work on newly unshifted phases", async () => { setPhases(unshifterPhase); // adds bananaPhase and applePhase to queue await to("applePhase"); expect(game.phaseInterceptor.log).toEqual(["unshifterPhase", "bananaPhase", "applePhase"]); }); it("should wait until phase finishes before starting next", async () => { setPhases(oneSecTimerPhase, applePhase); setTimeout(() => expect(getCurrentPhaseName()).toBe("oneSecTimerPhase"), 500); await to("applePhase"); }); }); describe("shift", () => { it("should skip the next phase without starting", async () => { expect(getCurrentPhaseName()).toBe("bananaPhase"); expect(getQueuedPhases()).toEqual(["applePhase", "oneSecTimerPhase", "unshifterPhase", "bananaPhase"]); game.phaseInterceptor.shiftPhase(); expect(getCurrentPhaseName()).toBe("applePhase"); expect(getQueuedPhases()).toEqual(["oneSecTimerPhase", "unshifterPhase", "bananaPhase"]); expect(game.phaseInterceptor.log).toEqual([]); }); }); });