209 lines
6.0 KiB
TypeScript
209 lines
6.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { DungeonRenderer } from '../DungeonRenderer';
|
|
import { type World } from '../../core/types';
|
|
|
|
// Mock Phaser
|
|
vi.mock('phaser', () => {
|
|
const mockSprite = {
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
setScale: vi.fn().mockReturnThis(),
|
|
play: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
destroy: vi.fn(),
|
|
};
|
|
|
|
const mockGraphics = {
|
|
clear: vi.fn().mockReturnThis(),
|
|
fillStyle: vi.fn().mockReturnThis(),
|
|
fillRect: vi.fn().mockReturnThis(),
|
|
lineStyle: vi.fn().mockReturnThis(),
|
|
strokeRect: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
const mockContainer = {
|
|
add: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
setScrollFactor: vi.fn().mockReturnThis(),
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
const mockRectangle = {
|
|
setStrokeStyle: vi.fn().mockReturnThis(),
|
|
setInteractive: vi.fn().mockReturnThis(),
|
|
};
|
|
|
|
return {
|
|
default: {
|
|
GameObjects: {
|
|
Sprite: vi.fn(() => mockSprite),
|
|
Graphics: vi.fn(() => mockGraphics),
|
|
Container: vi.fn(() => mockContainer),
|
|
Rectangle: vi.fn(() => mockRectangle),
|
|
},
|
|
Scene: vi.fn(),
|
|
Math: {
|
|
Clamp: vi.fn((v, min, max) => Math.min(Math.max(v, min), max)),
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
describe('DungeonRenderer', () => {
|
|
let mockScene: any;
|
|
let renderer: DungeonRenderer;
|
|
let mockWorld: World;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mockScene = {
|
|
add: {
|
|
graphics: vi.fn().mockReturnValue({
|
|
clear: vi.fn(),
|
|
fillStyle: vi.fn(),
|
|
fillRect: vi.fn(),
|
|
}),
|
|
sprite: vi.fn(() => ({
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
setScale: vi.fn().mockReturnThis(),
|
|
play: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
destroy: vi.fn(),
|
|
})),
|
|
container: vi.fn().mockReturnValue({
|
|
add: vi.fn(),
|
|
setPosition: vi.fn(),
|
|
setVisible: vi.fn(),
|
|
setScrollFactor: vi.fn(),
|
|
setDepth: vi.fn(),
|
|
}),
|
|
rectangle: vi.fn().mockReturnValue({
|
|
setStrokeStyle: vi.fn().mockReturnThis(),
|
|
setInteractive: vi.fn().mockReturnThis(),
|
|
}),
|
|
},
|
|
cameras: {
|
|
main: {
|
|
width: 800,
|
|
height: 600,
|
|
},
|
|
},
|
|
anims: {
|
|
create: vi.fn(),
|
|
exists: vi.fn().mockReturnValue(true),
|
|
generateFrameNumbers: vi.fn(),
|
|
},
|
|
make: {
|
|
tilemap: vi.fn().mockReturnValue({
|
|
addTilesetImage: vi.fn().mockReturnValue({}),
|
|
createLayer: vi.fn().mockReturnValue({
|
|
setDepth: vi.fn(),
|
|
forEachTile: vi.fn(),
|
|
}),
|
|
destroy: vi.fn(),
|
|
}),
|
|
},
|
|
|
|
};
|
|
|
|
mockWorld = {
|
|
width: 10,
|
|
height: 10,
|
|
tiles: new Array(100).fill(0),
|
|
actors: new Map(),
|
|
exit: { x: 9, y: 9 },
|
|
};
|
|
|
|
renderer = new DungeonRenderer(mockScene);
|
|
});
|
|
|
|
it('should track and clear corpse sprites on floor initialization', () => {
|
|
renderer.initializeFloor(mockWorld);
|
|
|
|
|
|
// Spawn a couple of corpses
|
|
renderer.spawnCorpse(1, 1, 'rat');
|
|
renderer.spawnCorpse(2, 2, 'bat');
|
|
|
|
// Get the mock sprites that were returned by scene.add.sprite
|
|
const corpse1 = mockScene.add.sprite.mock.results[1].value;
|
|
const corpse2 = mockScene.add.sprite.mock.results[2].value;
|
|
|
|
expect(mockScene.add.sprite).toHaveBeenCalledTimes(3);
|
|
|
|
// Initialize floor again (changing level)
|
|
renderer.initializeFloor(mockWorld);
|
|
|
|
|
|
// Verify destroy was called on both corpse sprites
|
|
expect(corpse1.destroy).toHaveBeenCalledTimes(1);
|
|
expect(corpse2.destroy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should render exp_orb as a circle and not as an enemy sprite', () => {
|
|
renderer.initializeFloor(mockWorld);
|
|
|
|
// Add an exp_orb to the world
|
|
mockWorld.actors.set(2, {
|
|
id: 2,
|
|
category: "collectible",
|
|
type: "exp_orb",
|
|
pos: { x: 2, y: 1 },
|
|
expAmount: 10
|
|
});
|
|
|
|
// Make the tile visible for it to render
|
|
(renderer as any).fovManager.visibleArray[1 * mockWorld.width + 2] = 1;
|
|
|
|
// Reset mocks
|
|
mockScene.add.sprite.mockClear();
|
|
|
|
// Mock scene.add.circle
|
|
mockScene.add.circle = vi.fn().mockReturnValue({
|
|
setStrokeStyle: vi.fn().mockReturnThis(),
|
|
setDepth: vi.fn().mockReturnThis(),
|
|
setPosition: vi.fn().mockReturnThis(),
|
|
setVisible: vi.fn().mockReturnThis(),
|
|
});
|
|
|
|
renderer.render([]);
|
|
|
|
// Should NOT have added an enemy sprite for the orb
|
|
const spriteCalls = mockScene.add.sprite.mock.calls;
|
|
// Any sprite added that isn't the player (which isn't in mockWorld.actors here except if we added it)
|
|
// The current loop skips a.isPlayer and then checks if type is in GAME_CONFIG.enemies
|
|
expect(spriteCalls.length).toBe(0);
|
|
|
|
// Should HAVE added a circle for the orb
|
|
expect(mockScene.add.circle).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should render any enemy type defined in config as a sprite', () => {
|
|
renderer.initializeFloor(mockWorld);
|
|
|
|
// Add a rat (defined in config)
|
|
mockWorld.actors.set(3, {
|
|
id: 3,
|
|
category: "combatant",
|
|
isPlayer: false,
|
|
type: "rat",
|
|
pos: { x: 3, y: 1 },
|
|
speed: 10,
|
|
energy: 0,
|
|
stats: { hp: 10, maxHp: 10, attack: 2, defense: 0 } as any
|
|
});
|
|
|
|
(renderer as any).fovManager.visibleArray[1 * mockWorld.width + 3] = 1;
|
|
mockScene.add.sprite.mockClear();
|
|
|
|
renderer.render([]);
|
|
|
|
// Should have added a sprite for the rat
|
|
const ratSpriteCall = mockScene.add.sprite.mock.calls.find((call: any) => call[2] === 'rat');
|
|
expect(ratSpriteCall).toBeDefined();
|
|
});
|
|
});
|