228 lines
7.2 KiB
TypeScript
228 lines
7.2 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// Mock Phaser
|
|
vi.mock('phaser', () => {
|
|
const mockGraphics = {
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
clear: vi.fn().mockReturnThis(),
|
|
lineStyle: vi.fn().mockReturnThis(),
|
|
lineBetween: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
const mockSprite = {
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
setAlpha: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
return {
|
|
default: {
|
|
GameObjects: {
|
|
Sprite: vi.fn(() => mockSprite),
|
|
Graphics: vi.fn(() => mockGraphics),
|
|
},
|
|
Scene: class {
|
|
add = {
|
|
graphics: vi.fn(() => mockGraphics),
|
|
sprite: vi.fn(() => mockSprite),
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// Mock CombatLogic
|
|
vi.mock('../../../engine/gameplay/CombatLogic', () => ({
|
|
traceProjectile: vi.fn(),
|
|
getClosestVisibleEnemy: vi.fn(),
|
|
}));
|
|
|
|
import { TargetingSystem } from '../TargetingSystem';
|
|
import { traceProjectile, getClosestVisibleEnemy } from '../../../engine/gameplay/CombatLogic';
|
|
import { TILE_SIZE } from '../../../core/constants';
|
|
import type { EntityId } from '../../../core/types';
|
|
|
|
describe('TargetingSystem', () => {
|
|
let targetingSystem: TargetingSystem;
|
|
let mockWorld: any;
|
|
let mockScene: any;
|
|
let mockGraphics: any;
|
|
let mockSprite: any;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mockGraphics = {
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
clear: vi.fn().mockReturnThis(),
|
|
lineStyle: vi.fn().mockReturnThis(),
|
|
lineBetween: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
mockSprite = {
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
setAlpha: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
mockScene = {
|
|
add: {
|
|
graphics: vi.fn(() => mockGraphics),
|
|
sprite: vi.fn(() => mockSprite),
|
|
},
|
|
} as any;
|
|
|
|
targetingSystem = new TargetingSystem(mockScene);
|
|
mockWorld = { width: 10, height: 10 };
|
|
|
|
// Default return for traceProjectile
|
|
(traceProjectile as any).mockReturnValue({
|
|
blockedPos: { x: 0, y: 0 },
|
|
hitActorId: undefined,
|
|
path: []
|
|
});
|
|
});
|
|
|
|
it('should initialize with graphics and crosshair sprite hidden', () => {
|
|
expect(mockScene.add.graphics).toHaveBeenCalled();
|
|
expect(mockScene.add.sprite).toHaveBeenCalled();
|
|
expect(mockSprite.setVisible).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('should start targeting and auto-select closest enemy', () => {
|
|
const playerPos = { x: 1, y: 1 };
|
|
const enemyPos = { x: 3, y: 3 };
|
|
(getClosestVisibleEnemy as any).mockReturnValue(enemyPos);
|
|
|
|
const mockAccessor = {
|
|
getCombatant: vi.fn().mockReturnValue({
|
|
pos: playerPos,
|
|
inventory: { items: [{ id: 'item-1' }] }
|
|
}),
|
|
context: {}
|
|
};
|
|
|
|
targetingSystem.startTargeting(
|
|
'item-1',
|
|
playerPos,
|
|
mockWorld,
|
|
mockAccessor as any,
|
|
1 as EntityId, // playerId
|
|
new Uint8Array(100),
|
|
10
|
|
);
|
|
|
|
expect(targetingSystem.isActive).toBe(true);
|
|
expect(targetingSystem.itemId).toBe('item-1');
|
|
expect(targetingSystem.cursorPos).toEqual(enemyPos);
|
|
expect(mockSprite.setVisible).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it('should fallback to mouse position if no enemy found', () => {
|
|
const playerPos = { x: 1, y: 1 };
|
|
const mousePos = { x: 5, y: 5 };
|
|
(getClosestVisibleEnemy as any).mockReturnValue(null);
|
|
|
|
const mockAccessor = {
|
|
getCombatant: vi.fn().mockReturnValue({
|
|
pos: playerPos,
|
|
inventory: { items: [{ id: 'item-1' }] }
|
|
}),
|
|
context: {}
|
|
};
|
|
|
|
targetingSystem.startTargeting(
|
|
'item-1',
|
|
playerPos,
|
|
mockWorld,
|
|
mockAccessor as any,
|
|
1 as EntityId, // playerId
|
|
new Uint8Array(100),
|
|
10,
|
|
mousePos
|
|
);
|
|
|
|
expect(targetingSystem.cursorPos).toEqual(mousePos);
|
|
});
|
|
|
|
it('should update visuals with predictive impact point', () => {
|
|
const playerPos = { x: 1, y: 1 };
|
|
const targetPos = { x: 5, y: 1 };
|
|
const blockedPos = { x: 3, y: 1 }; // Wall at 3,1
|
|
|
|
(traceProjectile as any).mockReturnValue({
|
|
blockedPos: blockedPos,
|
|
hitActorId: undefined,
|
|
path: []
|
|
});
|
|
|
|
const mockAccessor = {
|
|
getCombatant: vi.fn().mockReturnValue({
|
|
pos: playerPos,
|
|
inventory: { items: [{ id: 'item-1' }] }
|
|
}),
|
|
context: {}
|
|
};
|
|
|
|
// Start targeting
|
|
targetingSystem.startTargeting(
|
|
'item-1',
|
|
playerPos,
|
|
mockWorld,
|
|
mockAccessor as any,
|
|
1 as EntityId,
|
|
new Uint8Array(100),
|
|
10,
|
|
targetPos
|
|
);
|
|
|
|
// The crosshair should be at blockedPos, not targetPos
|
|
const expectedX = blockedPos.x * TILE_SIZE + TILE_SIZE / 2;
|
|
const expectedY = blockedPos.y * TILE_SIZE + TILE_SIZE / 2;
|
|
expect(mockSprite.setPosition).toHaveBeenCalledWith(expectedX, expectedY);
|
|
|
|
// Verify dashed line was drawn (multiple lineBetween calls)
|
|
expect(mockGraphics.lineBetween).toHaveBeenCalled();
|
|
expect(mockGraphics.lineBetween.mock.calls.length).toBeGreaterThan(1);
|
|
});
|
|
|
|
it('should clear visuals on cancel', () => {
|
|
targetingSystem.cancel();
|
|
expect(targetingSystem.isActive).toBe(false);
|
|
expect(mockGraphics.clear).toHaveBeenCalled();
|
|
expect(mockSprite.setVisible).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('should prevent targeting self', () => {
|
|
const playerPos = { x: 1, y: 1 };
|
|
|
|
// Setup targeting
|
|
targetingSystem.startTargeting(
|
|
'item-1',
|
|
playerPos,
|
|
mockWorld,
|
|
{ getCombatant: vi.fn().mockReturnValue({ pos: playerPos, inventory: { items: [{ id: 'item-1' }] } }) } as any,
|
|
1 as EntityId,
|
|
new Uint8Array(100),
|
|
10
|
|
);
|
|
|
|
// Manually set cursor to player pos (startTargeting might do it, but we ensure it)
|
|
targetingSystem.updateCursor(playerPos, playerPos);
|
|
|
|
const callback = vi.fn();
|
|
const result = targetingSystem.executeThrow(
|
|
mockWorld,
|
|
1 as EntityId,
|
|
{ getCombatant: vi.fn().mockReturnValue({ pos: playerPos, inventory: { items: [{ id: 'item-1' }] } }) } as any,
|
|
callback
|
|
);
|
|
|
|
expect(result).toBe(false);
|
|
expect(callback).not.toHaveBeenCalled();
|
|
});
|
|
});
|