import { describe, it, expect } from 'vitest';
import { Texture } from 'pixi.js';
import { IsometricTileMap, type TileMapData } from '../IsometricTileMap';
import { isoToScreen } from '../../math/iso';
function makeData(): TileMapData {
return {
width: 4,
height: 4,
tiles: new Array(16).fill(0),
blocked: [2],
tall: { 3: { height: 24, ground: 0 } }
};
}
describe('IsometricTileMap.setTile', () => {
it('обновляет данные и проходимость ячейки', () => {
const map = new IsometricTileMap(makeData(), new Map());
expect(map.isWalkable(1, 1)).toBe(true);
map.setTile(1, 1, 3); // высокий объект
expect(map.data.tiles[1 * 4 + 1]).toBe(3);
expect(map.isWalkable(1, 1)).toBe(true); // 3 не в blocked
map.setTile(1, 1, 2); // блокирующий id
expect(map.data.tiles[1 * 4 + 1]).toBe(2);
expect(map.isWalkable(1, 1)).toBe(false);
});
it('игнорирует координаты за границей карты', () => {
const map = new IsometricTileMap(makeData(), new Map());
expect(() => map.setTile(-1, 0, 2)).not.toThrow();
expect(() => map.setTile(4, 0, 2)).not.toThrow();
expect(() => map.setTile(0, 4, 2)).not.toThrow();
// данные не изменились
expect(map.data.tiles.every((t) => t === 0)).toBe(true);
});
});
describe('IsometricTileMap.variants', () => {
it('вариант выбирается по хэшу координат и стабилен при setTile', () => {
const map = new IsometricTileMap(makeDataWithVariants(), new Map());
const variant = map.variantOf(0, 3, 2);
expect([0, 1, 2]).toContain(variant);
// второй экземпляр той же карты — тот же выбор (детерминизм)
const again = new IsometricTileMap(makeDataWithVariants(), new Map());
expect(again.variantOf(0, 3, 2)).toBe(variant);
// setTile других id не меняет выбор для id с вариантами
map.setTile(1, 1, 2);
expect(map.variantOf(0, 3, 2)).toBe(variant);
// setTile на клетке с id из variants — вариант той же клетки стабилен
map.setTile(3, 2, 2);
map.setTile(3, 2, 0);
expect(map.variantOf(0, 3, 2)).toBe(variant);
});
it('setTileVariants задаёт таблицу рантаймом, пустой массив — no-op', () => {
const map = new IsometricTileMap(makeData(), new Map());
expect(map.variantOf(0, 2, 2)).toBe(0); // без вариантов id как есть
map.setTileVariants(0, [0, 5]);
// выбор детерминирован и покидает только варианты
const v22 = map.variantOf(0, 2, 2);
const v32 = map.variantOf(0, 3, 2);
expect([0, 5]).toContain(v22);
expect([0, 5]).toContain(v32);
// выбор стабилен
expect(map.variantOf(0, 2, 2)).toBe(v22);
expect(map.variantOf(0, 3, 2)).toBe(v32);
map.setTileVariants(0, []); // пустой массив — no-op (таблица не сбрасывается)
expect(map.variantOf(0, 2, 2)).toBe(v22);
// id без вариантов рисуется как есть
expect(map.variantOf(2, 3, 2)).toBe(2);
});
it('анимация сильнее: setTileVariants на анимированном id — no-op, setTileAnimation снимает варианты', () => {
const map = new IsometricTileMap(makeData(), new Map());
map.setTileAnimation(0, [Texture.WHITE], 4);
map.setTileVariants(0, [0, 5]); // no-op
expect(map.variantOf(0, 3, 2)).toBe(0);
const withVariants = new IsometricTileMap(makeDataWithVariants(), new Map());
withVariants.setTileAnimation(0, [Texture.WHITE], 4);
expect(withVariants.variantOf(0, 3, 2)).toBe(0); // варианты сняты
});
});
function makeDataWithVariants(): TileMapData {
return { ...makeData(), variants: { 0: [0, 1, 2] } };
}
describe('IsometricTileMap.decor', () => {
it('decorLayer между землёй и объектами, декор из данных рисуется', () => {
const data = makeData();
data.decor = [{ id: 7, x: 1, y: 2, dx: 2, dy: -1 }];
const map = new IsometricTileMap(data, new Map());
const layers = map.view.children;
expect(layers.length).toBe(3);
// порядок детей = порядок отрисовки: ground -> decor -> objects
const s = map.addDecor(0, 0, Texture.WHITE)!;
expect(s.parent).toBe(layers[1]);
expect(s.anchor.x).toBe(0.5);
// смещение dx/dy — от центра ромба
const shifted = map.addDecor(2, 1, Texture.WHITE, { dx: 3, dy: -2 })!;
const c = isoToScreen(2, 1, map.iso);
expect(shifted.position.x).toBe(c.x + 3);
expect(shifted.position.y).toBe(c.y + map.iso.tileH / 2 - 2);
});
it('addDecor кладёт один спрайт на тайл, повторный — заменяет; clearDecor убирает', () => {
const map = new IsometricTileMap(makeData(), new Map());
const first = map.addDecor(1, 1, Texture.WHITE)!;
const second = map.addDecor(1, 1, Texture.WHITE)!; // замена, а не второй спрайт
map.addDecor(2, 1, Texture.WHITE);
expect(first.parent).toBeNull(); // прежний спрайт убран
expect(second.parent).not.toBeNull();
expect(map.data.decor).toBeUndefined();
map.clearDecor(1, 1);
map.clearDecor(1, 1); // повторный clear — no-op
map.clearDecor(9, 9); // чужой тайл — no-op
expect(second.parent).toBeNull();
});
it('addDecor за границей карты игнорируется; setTile декор не трогает', () => {
const data = makeData();
data.decor = [{ id: 7, x: 2, y: 2 }];
const map = new IsometricTileMap(data, new Map());
expect(map.addDecor(-1, 0, Texture.WHITE)).toBeUndefined();
expect(map.addDecor(4, 0, Texture.WHITE)).toBeUndefined();
map.setTile(2, 2, 0); // перерисовка ячейки не должна задеть декор
map.setTile(2, 2, 2);
expect(() => map.clearDecor(2, 2)).not.toThrow();
});
});
describe('IsometricTileMap.props', () => {
it('блокирует весь footprint и нормализует размеры', () => {
const data = makeData();
data.props = [{ id: 7, x: 1, y: 1, w: 2, h: 2 }];
const map = new IsometricTileMap(data, new Map());
expect(map.props).toEqual([{ id: 7, x: 1, y: 1, w: 2, h: 2 }]);
// весь footprint 2×2 непроходим, вокруг — проходим
expect(map.isWalkable(1, 1)).toBe(false);
expect(map.isWalkable(2, 2)).toBe(false);
expect(map.isWalkable(0, 1)).toBe(true);
expect(map.isWalkable(1, 3)).toBe(true);
// спрайт пропа отдан сцене для depth-слоя
expect(map.propViews).toHaveLength(1);
expect(map.propViews[0]!.prop).toBe(map.props[0]);
});
it('footprint по умолчанию 1×1', () => {
const data = makeData();
data.props = [{ id: 7, x: 2, y: 0 }];
const map = new IsometricTileMap(data, new Map());
expect(map.props[0]!.w).toBe(1);
expect(map.props[0]!.h).toBe(1);
expect(map.isWalkable(2, 0)).toBe(false);
expect(map.isWalkable(3, 0)).toBe(true);
});
it('setTile внутри footprint не снимает блокировку', () => {
const data = makeData();
data.props = [{ id: 7, x: 1, y: 1 }];
const map = new IsometricTileMap(data, new Map());
map.setTile(1, 1, 0);
expect(map.isWalkable(1, 1)).toBe(false);
});
});