From 00736b5bfaad462ab833d238631845c9900235d3 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:09:39 -0500 Subject: [PATCH] Reuse global scene between tests Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> --- src/battle-scene.ts | 12 ++++++++ src/ui/starter-select-ui-handler.ts | 9 ++++++ test/abilities/unseen_fist.test.ts | 16 +++++----- test/achievements/achievement.test.ts | 22 ++++++++++++-- test/moves/fusion_flare_bolt.test.ts | 12 ++++---- test/phases/phases.test.ts | 6 ++-- test/testUtils/gameManager.ts | 30 +++++++++++++++++-- .../mocks/mocksContainer/mockRectangle.ts | 3 ++ 8 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 544dbc40350..38ff7c9712e 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2721,6 +2721,18 @@ export default class BattleScene extends SceneBase { this.phaseQueue.splice(0, this.phaseQueue.length); } + /** + * Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index + */ + clearAllPhases(): void { + for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { + queue.splice(0, queue.length); + } + this.currentPhase = null; + this.standbyPhase = null; + this.clearPhaseQueueSplice(); + } + /** * Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases */ diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 91940d3af76..1599c86aa87 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -4553,4 +4553,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { icon.setFrame(species.getIconId(female, formIndex, false, variant)); } } + + /** + * Clears this UI's starter preferences. + * + * Designed to be used for unit tests that utilize this UI. + */ + clearStarterPreferences() { + this.starterPreferences = {}; + } } diff --git a/test/abilities/unseen_fist.test.ts b/test/abilities/unseen_fist.test.ts index 73ae25ff3b0..459bb00628c 100644 --- a/test/abilities/unseen_fist.test.ts +++ b/test/abilities/unseen_fist.test.ts @@ -32,22 +32,22 @@ describe("Abilities - Unseen Fist", () => { game.override.enemyLevel(100); }); - it("should cause a contact move to ignore Protect", () => - testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, true)); + it("should cause a contact move to ignore Protect", async () => + await testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, true)); - it("should not cause a non-contact move to ignore Protect", () => - testUnseenFistHitResult(game, Moves.ABSORB, Moves.PROTECT, false)); + it("should not cause a non-contact move to ignore Protect", async () => + await testUnseenFistHitResult(game, Moves.ABSORB, Moves.PROTECT, false)); it("should not apply if the source has Long Reach", async () => { game.override.passiveAbility(Abilities.LONG_REACH); await testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); }); - it("should cause a contact move to ignore Wide Guard", () => - testUnseenFistHitResult(game, Moves.BREAKING_SWIPE, Moves.WIDE_GUARD, true)); + it("should cause a contact move to ignore Wide Guard", async () => + await testUnseenFistHitResult(game, Moves.BREAKING_SWIPE, Moves.WIDE_GUARD, true)); - it("should not cause a non-contact move to ignore Wide Guard", () => - testUnseenFistHitResult(game, Moves.BULLDOZE, Moves.WIDE_GUARD, false)); + it("should not cause a non-contact move to ignore Wide Guard", async () => + await testUnseenFistHitResult(game, Moves.BULLDOZE, Moves.WIDE_GUARD, false)); it("should cause a contact move to ignore Protect, but not Substitute", async () => { game.override.enemyLevel(1); diff --git a/test/achievements/achievement.test.ts b/test/achievements/achievement.test.ts index 26d33adb00a..5c53e38e208 100644 --- a/test/achievements/achievement.test.ts +++ b/test/achievements/achievement.test.ts @@ -14,7 +14,7 @@ import { NumberHolder } from "#app/utils"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; describe("check some Achievement related stuff", () => { it("should check Achievement creation", () => { @@ -77,6 +77,25 @@ describe("Achv", () => { }); describe("MoneyAchv", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + scene = game.scene; + }); + it("should create an instance of MoneyAchv", () => { const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10); expect(moneyAchv).toBeInstanceOf(MoneyAchv); @@ -85,7 +104,6 @@ describe("MoneyAchv", () => { it("should validate the achievement based on the money amount", () => { const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10); - const scene = new BattleScene(); scene.money = 5000; expect(moneyAchv.validate([])).toBe(false); diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index 9a379cb4588..f004057dae0 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -45,7 +45,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { }); it("FUSION_FLARE should double power of subsequent FUSION_BOLT", async () => { - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); game.move.select(fusionFlare.id, 0, BattlerIndex.ENEMY); game.move.select(fusionBolt.id, 1, BattlerIndex.ENEMY); @@ -65,7 +65,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { }, 20000); it("FUSION_BOLT should double power of subsequent FUSION_FLARE", async () => { - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); game.move.select(fusionBolt.id, 0, BattlerIndex.ENEMY); game.move.select(fusionFlare.id, 1, BattlerIndex.ENEMY); @@ -85,7 +85,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { }, 20000); it("FUSION_FLARE should double power of subsequent FUSION_BOLT if a move failed in between", async () => { - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); game.move.select(fusionFlare.id, 0, BattlerIndex.PLAYER); game.move.select(fusionBolt.id, 1, BattlerIndex.PLAYER); @@ -111,7 +111,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { it("FUSION_FLARE should not double power of subsequent FUSION_BOLT if a move succeeded in between", async () => { game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); game.move.select(fusionFlare.id, 0, BattlerIndex.ENEMY); game.move.select(fusionBolt.id, 1, BattlerIndex.ENEMY); @@ -156,7 +156,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { it("FUSION_FLARE and FUSION_BOLT alternating throughout turn should double power of subsequent moves", async () => { game.override.enemyMoveset([fusionFlare.id, fusionFlare.id, fusionFlare.id, fusionFlare.id]); - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); const party = game.scene.getPlayerParty(); const enemyParty = game.scene.getEnemyParty(); @@ -210,7 +210,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { it("FUSION_FLARE and FUSION_BOLT alternating throughout turn should double power of subsequent moves if moves are aimed at allies", async () => { game.override.enemyMoveset([fusionFlare.id, fusionFlare.id, fusionFlare.id, fusionFlare.id]); - await game.startBattle([Species.ZEKROM, Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.ZEKROM]); const party = game.scene.getPlayerParty(); const enemyParty = game.scene.getEnemyParty(); diff --git a/test/phases/phases.test.ts b/test/phases/phases.test.ts index 4aabeb55b9e..96225c9151c 100644 --- a/test/phases/phases.test.ts +++ b/test/phases/phases.test.ts @@ -31,7 +31,7 @@ describe("Phases", () => { it("should start the login phase", async () => { const loginPhase = new LoginPhase(); scene.unshiftPhase(loginPhase); - await game.phaseInterceptor.run(LoginPhase); + await game.phaseInterceptor.to(LoginPhase); expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); }); }); @@ -40,7 +40,7 @@ describe("Phases", () => { it("should start the title phase", async () => { const titlePhase = new TitlePhase(); scene.unshiftPhase(titlePhase); - await game.phaseInterceptor.run(TitlePhase); + await game.phaseInterceptor.to(TitlePhase); expect(scene.ui.getMode()).to.equal(Mode.TITLE); }); }); @@ -49,7 +49,7 @@ describe("Phases", () => { it("should start the unavailable phase", async () => { const unavailablePhase = new UnavailablePhase(); scene.unshiftPhase(unavailablePhase); - await game.phaseInterceptor.run(UnavailablePhase); + await game.phaseInterceptor.to(UnavailablePhase); expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE); }, 20000); }); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 3289c0ade01..7e0231f4232 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -55,6 +55,8 @@ import TextInterceptor from "#test/testUtils/TextInterceptor"; import { AES, enc } from "crypto-js"; import fs from "node:fs"; import { expect, vi } from "vitest"; +import { globalScene } from "#app/global-scene"; +import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; /** * Class to manage the game state and transitions between phases. @@ -84,10 +86,34 @@ export default class GameManager { ErrorInterceptor.getInstance().clear(); BattleScene.prototype.randBattleSeedInt = (range, min = 0) => min + range - 1; // This simulates a max roll this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); - this.scene = new BattleScene(); + + let firstTimeScene = false; + + if (globalScene) { + this.scene = globalScene; + } else { + this.scene = new BattleScene(); + this.gameWrapper.setScene(this.scene); + firstTimeScene = true; + } + this.phaseInterceptor = new PhaseInterceptor(this.scene); + + if (!firstTimeScene) { + this.scene.reset(false, true); + (this.scene.ui.handlers[Mode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences(); + this.scene.clearAllPhases(); + + // Must be run after phase interceptor has been initialized. + + this.scene.pushPhase(new LoginPhase()); + this.scene.pushPhase(new TitlePhase()); + this.scene.shiftPhase(); + + this.gameWrapper.scene = this.scene; + } + this.textInterceptor = new TextInterceptor(this.scene); - this.gameWrapper.setScene(this.scene); this.override = new OverridesHelper(this); this.move = new MoveHelper(this); this.classicMode = new ClassicModeHelper(this); diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index eec431d8ada..854baed5915 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -1,3 +1,4 @@ +import { off } from "process"; import type { MockGameObject } from "../mockGameObject"; export default class MockRectangle implements MockGameObject { @@ -72,4 +73,6 @@ export default class MockRectangle implements MockGameObject { setScale(_scale) { // return this.phaserText.setScale(scale); } + + off() {} }