Files
restitution/test/systems/ControlPointManager.test.js
kaykayyali 8fc45968b5 M2.3 + M2.4 + TeamManager integration: projectile sprites, death handling, build menu, production panel, building placer
- ProjectileSprite.js: physics arcade sprite, faction tint, off-screen culling
- CombatSystem: refactored enemy selection to use TeamManager instead of legacy containers
- Death handling: DYING alpha tween (500ms), smoke puff (300ms), unit:killed event, cleanup
- TeamManager: centralized team registry replacing goodGuys/badGuys containers
- HealthBarSystem, ResourceBar, CaptureProgressUI, BuildMenu, BuildingPlacer, BuildingRenderer, ProductionPanel
- Map_Player: wired new subsystems, removed legacy container creation
- Tests: ProjectileSprite (4), DeathHandling (13), CombatSystem updated

47 tests passed at dev time (M2.3), 158/158 at dev time (M2.4)
2026-06-01 05:18:33 +00:00

159 lines
4.5 KiB
JavaScript

/**
* ControlPointManager.test.js — TeamManager-aware tests.
* Tests use Jest globals.
*/
// xstate mock with working state transitions
jest.mock('xstate', () => {
function createService(initialState = 'NEUTRAL', initialContext = {}) {
const ctx = {
owner: null,
captureProgress: 0,
captureTime: 60000,
unitsInRadius: {},
...initialContext,
};
const svc = {
state: { value: initialState, context: ctx },
send: jest.fn((event) => {
const e = typeof event === 'string' ? { type: event } : event;
const current = svc.state.value;
if (current === 'NEUTRAL' && e.type === 'UNITS_ENTERED') {
svc.state.value = 'CONTESTED';
} else if (current === 'CONTESTED' && e.type === 'PROGRESS_COMPLETE') {
svc.state.value = 'CAPTURED';
ctx.owner = e.owner || e.teamId || null;
} else if (current === 'CONTESTED' && e.type === 'UNITS_LEFT') {
svc.state.value = 'NEUTRAL';
} else if (current === 'CAPTURED' && e.type === 'ENEMY_UNITS_ENTERED') {
svc.state.value = 'CONTESTED';
}
if (e.owner != null) ctx.owner = e.owner;
if (e.teamId != null) ctx.owner = e.teamId;
}),
start: jest.fn(),
stop: jest.fn(),
status: 1,
};
return svc;
}
return {
createMachine: jest.fn((config) => ({
config,
id: config?.id,
initial: config?.initial,
})),
interpret: jest.fn((machine) => {
const cfg = machine?.config || machine;
return createService(cfg?.initial);
}),
assign: jest.fn((fn) => fn),
};
});
import ControlPointManager from 'Systems/ControlPointManager.js';
// ── Helpers ─────────────────────────────────────────────────────────
function mockTilemap() {
return {
tileToWorldXY: jest.fn((tx, ty) => ({ x: tx * 32, y: ty * 32 })),
tileWidth: 32,
tileHeight: 32,
};
}
function mockScene(teamManager = null) {
return {
add: {
zone: jest.fn((x, y, w, h) => ({
x: x ?? 0,
y: y ?? 0,
width: w ?? 0,
height: h ?? 0,
setName: jest.fn().mockReturnThis(),
destroy: jest.fn(),
body: { setCircle: jest.fn(), setOffset: jest.fn() },
})),
},
physics: {
world: { enableBody: jest.fn() },
},
events: { on: jest.fn(), emit: jest.fn(), off: jest.fn() },
teamManager,
children: { list: [] },
};
}
function mockEconomy() {
return {
addIncome: jest.fn(),
getResources: jest.fn(),
initPlayer: jest.fn(),
};
}
function mockTeamManager(units = {}) {
const map = new Map();
for (const [tid, list] of Object.entries(units)) {
map.set(tid, new Set(list));
}
return {
getAllUnitsGrouped: jest.fn(() => map),
getTeamUnits: jest.fn((tid) => map.get(tid) || new Set()),
};
}
// ── Tests ───────────────────────────────────────────────────────────
describe('ControlPointManager', () => {
let manager;
let scene;
let tilemap;
let economy;
let teamManager;
beforeEach(() => {
tilemap = mockTilemap();
economy = mockEconomy();
teamManager = mockTeamManager({});
scene = mockScene(teamManager);
manager = new ControlPointManager(scene, tilemap, economy, teamManager);
});
afterEach(() => {
if (manager) manager.destroy();
});
// ── Test 1: constructor accepts teamManager ─────────────────────
test('constructor accepts teamManager parameter', () => {
expect(manager.teamManager).toBe(teamManager);
});
// ── Test 2: update queries teamManager for unit counts ──────────
test('update delegates tick to each CP using teamManager for unit counts', () => {
const tickSpies = manager.controlPoints.map((cp) =>
jest.spyOn(cp, 'tick').mockImplementation(() => {}),
);
manager.update(1000, 16);
for (const spy of tickSpies) {
expect(spy).toHaveBeenCalledWith(1000, 16, scene);
}
});
test('each CP has a reference to teamManager on construction', () => {
for (const cp of manager.controlPoints) {
expect(cp.teamManager).toBe(teamManager);
}
});
test('falls back to scene.teamManager when no teamManager passed', () => {
const mgr = new ControlPointManager(scene, tilemap, economy);
expect(mgr.teamManager).toBe(scene.teamManager);
});
});