// This test file has been split into smaller, more focused test files for better reliability: // - combat-movement-highlights.test.js (CoA 6, 7) - Tests highlight behavior // - combat-movement-highlights-5.test.js (CoA 5) - Tests mesh creation (currently hangs, needs investigation) // - combat-movement-calculation.test.js (CoA 8) - Tests reachable tile calculation // - combat-movement-execution.test.js (CoA 9, 10, 11) - Tests actual movement execution // // The split improves test reliability and performance by avoiding resource accumulation issues. // CoA 5 hangs even in isolation, suggesting an issue with THREE.js mesh creation/cleanup. import { expect } from "@esm-bundle/chai"; import * as THREE from "three"; import { GameLoop } from "../../../src/core/GameLoop.js"; import { createGameLoopSetup, cleanupGameLoop, createRunData, createMockGameStateManagerForCombat, setupCombatUnits, cleanupTurnSystem, } from "./helpers.js"; describe.skip("Core: GameLoop - Combat Movement (Legacy - Split into separate files)", function () { this.timeout(30000); let gameLoop; let container; let mockGameStateManager; let playerUnit; let enemyUnit; beforeEach(async () => { const setup = createGameLoopSetup(); gameLoop = setup.gameLoop; container = setup.container; // Clean up any existing state first if (gameLoop.turnSystemAbortController) { gameLoop.turnSystemAbortController.abort(); } gameLoop.stop(); if ( gameLoop.turnSystem && typeof gameLoop.turnSystem.reset === "function" ) { gameLoop.turnSystem.reset(); } gameLoop.init(container); mockGameStateManager = createMockGameStateManagerForCombat(); gameLoop.gameStateManager = mockGameStateManager; const runData = createRunData({ squad: [{ id: "u1", classId: "CLASS_VANGUARD" }], }); await gameLoop.startLevel(runData, { startAnimation: false }); // Mock updateCombatState to avoid slow file fetches that can cause hangs // Replace with a no-op that resolves immediately gameLoop.updateCombatState = async () => Promise.resolve(); const units = setupCombatUnits(gameLoop); playerUnit = units.playerUnit; enemyUnit = units.enemyUnit; }); afterEach(async () => { // Clear highlights first to free Three.js resources if (gameLoop.clearMovementHighlights) { gameLoop.clearMovementHighlights(); } if (gameLoop.clearSpawnZoneHighlights) { gameLoop.clearSpawnZoneHighlights(); } // Ensure turn system is properly cleaned up if (gameLoop.turnSystem) { try { if ( gameLoop.turnSystem.phase !== "INIT" && gameLoop.turnSystem.phase !== "COMBAT_END" ) { gameLoop.turnSystem.endCombat(); } } catch (e) { // Ignore errors during cleanup } } cleanupTurnSystem(gameLoop); cleanupGameLoop(gameLoop, container); // Small delay to allow cleanup to complete await new Promise((resolve) => setTimeout(resolve, 50)); }); it("CoA 5: should show movement highlights for player units in combat", () => { mockGameStateManager.getCombatState.returns({ activeUnit: { id: playerUnit.id, name: playerUnit.name, }, turnQueue: [], }); gameLoop.updateMovementHighlights(playerUnit); expect(gameLoop.movementHighlights.size).to.be.greaterThan(0); const highlightArray = Array.from(gameLoop.movementHighlights); expect(highlightArray.length).to.be.greaterThan(0); expect(highlightArray[0]).to.be.instanceOf(THREE.Mesh); }); it("CoA 6: should not show movement highlights for enemy units", () => { mockGameStateManager.getCombatState.returns({ activeUnit: { id: enemyUnit.id, name: enemyUnit.name, }, turnQueue: [], }); gameLoop.updateMovementHighlights(enemyUnit); expect(gameLoop.movementHighlights.size).to.equal(0); }); it("CoA 7: should clear movement highlights when not in combat", () => { mockGameStateManager.getCombatState.returns({ activeUnit: { id: playerUnit.id, name: playerUnit.name, }, turnQueue: [], }); gameLoop.updateMovementHighlights(playerUnit); expect(gameLoop.movementHighlights.size).to.be.greaterThan(0); mockGameStateManager.currentState = "STATE_DEPLOYMENT"; gameLoop.updateMovementHighlights(playerUnit); expect(gameLoop.movementHighlights.size).to.equal(0); }); it("CoA 8: should calculate reachable positions correctly", () => { const reachable = gameLoop.movementSystem.getReachableTiles(playerUnit, 4); expect(reachable).to.be.an("array"); expect(reachable.length).to.be.greaterThan(0); reachable.forEach((pos) => { expect(pos).to.have.property("x"); expect(pos).to.have.property("y"); expect(pos).to.have.property("z"); expect(gameLoop.grid.isValidBounds(pos)).to.be.true; }); }); it("CoA 9: should move player unit in combat when clicking valid position", async () => { // Set player unit to have high charge so it becomes active immediately playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; // High speed to ensure it goes first const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); // Verify player is active (should be after startCombat with high charge) const activeUnit = gameLoop.turnSystem.getActiveUnit(); expect(activeUnit).to.equal(playerUnit); const initialPos = { ...playerUnit.position }; const targetPos = { x: initialPos.x + 1, y: initialPos.y, z: initialPos.z, }; const initialAP = playerUnit.currentAP; await gameLoop.handleCombatMovement(targetPos); if ( playerUnit.position.x !== initialPos.x || playerUnit.position.z !== initialPos.z ) { expect(playerUnit.position.x).to.equal(targetPos.x); expect(playerUnit.position.z).to.equal(targetPos.z); expect(playerUnit.currentAP).to.be.lessThan(initialAP); } else { expect(playerUnit.currentAP).to.be.at.most(initialAP); } }); it("CoA 10: should not move unit if target is not reachable", async () => { // Set player unit to have high charge so it becomes active immediately playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); // Verify player is active expect(gameLoop.turnSystem.getActiveUnit()).to.equal(playerUnit); const initialPos = { ...playerUnit.position }; const targetPos = { x: 20, y: 1, z: 20 }; gameLoop.isRunning = false; gameLoop.inputManager = { getCursorPosition: () => targetPos, update: () => {}, isKeyPressed: () => false, setCursor: () => {}, }; await gameLoop.handleCombatMovement(targetPos); expect(playerUnit.position.x).to.equal(initialPos.x); expect(playerUnit.position.z).to.equal(initialPos.z); }); it("CoA 11: should not move unit if not enough AP", async () => { // Set player unit to have high charge so it becomes active immediately playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); // Verify player is active expect(gameLoop.turnSystem.getActiveUnit()).to.equal(playerUnit); playerUnit.currentAP = 0; const initialPos = { ...playerUnit.position }; const targetPos = { x: 6, y: 1, z: 5 }; gameLoop.isRunning = false; gameLoop.inputManager = { getCursorPosition: () => targetPos, update: () => {}, isKeyPressed: () => false, setCursor: () => {}, }; await gameLoop.handleCombatMovement(targetPos); expect(playerUnit.position.x).to.equal(initialPos.x); }); });