Files
restitution/tests/unit/BuildMenu.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

169 lines
5.3 KiB
JavaScript

/**
* BuildMenu.test.js — S5.1: Build menu HUD
*
* Tests:
* 1. Constructor creates a container with 4 building buttons fixed to camera
* 2. Each button shows building label and cost text
* 3. Clicking a button calls onSelect callback with building type
* 4. updateAffordability disables buttons when player can't afford
* 5. updateAffordability re-enables buttons when player can afford again
* 6. destroy cleans up all Phaser objects
*/
import BuildMenu from 'Systems/BuildMenu';
function buildMockScene() {
const objects = [];
const inputOnHandlers = [];
return {
add: {
container: jest.fn((x, y) => {
const c = {
x, y,
list: [],
add: jest.fn(function (obj) { this.list.push(obj); return this; }),
setPosition: jest.fn().mockReturnThis(),
setVisible: jest.fn().mockReturnThis(),
setScrollFactor: jest.fn().mockReturnThis(),
setDepth: jest.fn().mockReturnThis(),
destroy: jest.fn(),
active: true,
};
objects.push(c);
return c;
}),
rectangle: jest.fn((x, y, w, h) => {
const r = {
x, y, width: w, height: h,
setOrigin: jest.fn().mockReturnThis(),
setStrokeStyle: jest.fn().mockReturnThis(),
setInteractive: jest.fn().mockReturnThis(),
setFillStyle: jest.fn().mockReturnThis(),
setAlpha: jest.fn().mockReturnThis(),
on: jest.fn().mockReturnThis(),
off: jest.fn().mockReturnThis(),
destroy: jest.fn(),
active: true,
};
objects.push(r);
return r;
}),
text: jest.fn((x, y, text, style) => {
const t = {
x, y, text,
style,
setOrigin: jest.fn().mockReturnThis(),
setScrollFactor: jest.fn().mockReturnThis(),
setDepth: jest.fn().mockReturnThis(),
setText: jest.fn(function (v) { this.text = v; return this; }),
setColor: jest.fn().mockReturnThis(),
setAlpha: jest.fn().mockReturnThis(),
destroy: jest.fn(),
active: true,
};
objects.push(t);
return t;
}),
},
cameras: {
main: { width: 1280, height: 720 },
},
input: {
on: jest.fn((evt, cb) => inputOnHandlers.push({ evt, cb })),
},
_objects: objects,
_inputHandlers: inputOnHandlers,
};
}
describe('BuildMenu', () => {
let scene;
let menu;
let onSelect;
beforeEach(() => {
scene = buildMockScene();
onSelect = jest.fn();
menu = new BuildMenu(scene, { onSelect, playerId: 'Player' });
});
afterEach(() => {
menu?.destroy?.();
});
// -- 1. Constructor -------------------------------------------------
test('constructor creates a container fixed to camera', () => {
expect(scene.add.container).toHaveBeenCalled();
expect(menu.container).toBeDefined();
expect(menu.container.setScrollFactor).toHaveBeenCalledWith(0, 0);
});
test('container has 4 building buttons', () => {
expect(menu.buttons).toBeDefined();
expect(menu.buttons.length).toBe(4);
});
// -- 2. Button labels + cost --------------------------------------
test('each button has a text label', () => {
const labels = menu.buttons.map((b) => b.label?.text);
expect(labels).toContain('Barracks');
expect(labels).toContain('Vehicle Depot');
expect(labels).toContain('Logistics Center');
expect(labels).toContain('Ammunition Factory');
});
test('each button shows its resource cost', () => {
const costs = menu.buttons.map((b) => b.costText?.text);
expect(costs.some((c) => c && c.includes('Ammo'))).toBe(true);
expect(costs.some((c) => c && c.includes('Fuel'))).toBe(true);
});
// -- 3. Clicking a button -----------------------------------------
test('clicking a button calls onSelect with building type', () => {
const barracksBtn = menu.buttons.find((b) => b.type === 'BARRACKS');
expect(barracksBtn).toBeDefined();
// Simulate the rectangle.on('pointerdown') callback
const pointerdownCall = barracksBtn.bg.on.mock.calls.find((c) => c[0] === 'pointerdown');
expect(pointerdownCall).toBeDefined();
pointerdownCall[1]();
expect(onSelect).toHaveBeenCalledWith('BARRACKS');
});
// -- 4. updateAffordability (disabled) -----------------------------
test('updateAffordability disables buttons player cannot afford', () => {
const economy = {
getResources: jest.fn(() => ({ fuel: 0, ammo: 0 })),
canAfford: jest.fn(() => false),
};
menu.updateAffordability(economy, 'Player');
menu.buttons.forEach((btn) => {
expect(btn.bg.setAlpha).toHaveBeenCalledWith(0.4);
});
});
// -- 5. updateAffordability (re-enabled) --------------------------
test('updateAffordability enables affordable buttons', () => {
const economy = {
getResources: jest.fn(() => ({ fuel: 200, ammo: 200 })),
canAfford: jest.fn(() => true),
};
menu.updateAffordability(economy, 'Player');
menu.buttons.forEach((btn) => {
expect(btn.bg.setAlpha).toHaveBeenCalledWith(1);
});
});
// -- 6. destroy ----------------------------------------------------
test('destroy cleans up container and buttons', () => {
const containerDestroy = menu.container.destroy;
menu.destroy();
expect(containerDestroy).toHaveBeenCalled();
expect(menu.buttons.length).toBe(0);
});
});