template/packages-user/data-base/src/map/__test__/gameMap.test.ts
unanmed 8d6f851727 test(08-05): align data-base map tests to shipped interfaces
- drop DirectionMapper; new MapState(state); addLayer(alias)+setEventLayer; remove addHook().load()

- align removed getLayerByAlias/getLayerAlias to layerList/alias and setDynamicDirection to tile.setFaceDirection

- dispose stale codes 62/63/64/84/130 per Q1=A; keep 60/61

- tests: expect default events for transferToDynamic(keepEvent=false); setMapList does not dedup
2026-10-05 15:19:56 +08:00

285 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 测试 GameMap 的图层生命周期、别名、背景、事件层、脏标记与尺寸变化
import { afterAll, describe, expect, it, vi } from 'vitest';
import {
type IDataCommon,
type ITileRawData,
Dir8FaceHandler,
FaceGroup,
FaceManager,
RoleFaceBinder,
TileStore,
TileType
} from '@user/data-common';
import { logger } from '@motajs/common';
import { type IMapLayer } from '../types';
import { GameMap } from '../gameMap';
vi.hoisted(() => {
vi.stubGlobal('main', { replayChecking: true });
vi.stubGlobal('location', { origin: 'http://localhost' });
Map.prototype.getOrInsert ??= function <K, V>(
this: Map<K, V>,
key: K,
value: V
): V {
const existing = this.get(key);
if (existing !== undefined) return existing;
this.set(key, value);
return value;
};
Map.prototype.getOrInsertComputed ??= function <K, V>(
this: Map<K, V>,
key: K,
callback: (key: K) => V
): V {
const existing = this.get(key);
if (existing !== undefined) return existing;
const value = callback(key);
this.set(key, value);
return value;
};
});
afterAll(() => {
vi.unstubAllGlobals();
});
/**
* 构造一条最小图块原始数据
* @param num 图块数字
* @param id 图块字符串 id
* @param events 默认事件映射
*/
function createTileData(
num: number,
id: string,
events: Record<number, string> = {}
): ITileRawData {
return {
num,
id,
events,
type: TileType.Terrain,
pass: { onlyEvents: false, outPass: 15, inPass: 15 },
eventPass: true
};
}
interface GameMapFixture {
map: GameMap;
tileStore: TileStore;
}
/** 构造一个空的 GameMap 及其底层公共层对象 */
function createFixture(): GameMapFixture {
const tileStore = new TileStore();
tileStore.addTile(createTileData(1, 'base', { 10: 'base-event' }));
const faceManager = new FaceManager();
faceManager.register(FaceGroup.Dir8, new Dir8FaceHandler());
const state: IDataCommon = {
tileStore,
itemStore: {},
mapStore: {},
eventStore: {},
roleFace: new RoleFaceBinder(),
faceManager,
saveSystem: {}
} as never;
return {
map: new GameMap(state, 'F1', 2, 2),
tileStore
};
}
describe('GameMap layer lifecycle', () => {
// 验证 addLayer/removeLayer/hasLayer 与图层列表钩子
it('adds and removes layers and notifies the layer list hook', () => {
const { map } = createFixture();
const sizes: number[] = [];
map.addHook({
onUpdateLayer: list => {
sizes.push(list.size);
}
});
const layer = map.addLayer('layer');
expect(map.hasLayer(layer)).toBe(true);
expect(sizes).toEqual([1]);
map.removeLayer(layer);
expect(map.hasLayer(layer)).toBe(false);
expect(sizes).toEqual([1, 0]);
});
// 验证图层别名随加层绑定并可由别名映射取回(旧码 84 已随 setLayerAlias 删除)
it('binds layer aliases at addLayer time', () => {
const { map } = createFixture();
const first = map.addLayer('event');
const second = map.addLayer('other');
expect(map.aliasLayerMap.get('event')).toBe(first);
expect(first.alias).toBe('event');
expect(map.aliasLayerMap.get('other')).toBe(second);
expect(second.alias).toBe('other');
});
// 验证移除带别名的图层会一并清除别名映射
it('drops the alias mapping when the layer is removed', () => {
const { map } = createFixture();
const layer = map.addLayer('event');
map.removeLayer(layer);
expect(map.aliasLayerMap.get('event')).toBeUndefined();
expect(map.hasLayer(layer)).toBe(false);
});
});
describe('GameMap settings', () => {
// 验证背景设置、读取与背景变化钩子
it('stores the background and notifies the background hook', () => {
const { map } = createFixture();
const backgrounds: number[] = [];
map.addHook({
onChangeBackground: tile => {
backgrounds.push(tile);
}
});
expect(map.getBackground()).toBe(0);
map.setBackground(7);
expect(map.getBackground()).toBe(7);
expect(backgrounds).toEqual([7]);
});
// 验证激活状态切换
it('toggles the active flag', () => {
const { map } = createFixture();
expect(map.active).toBe(false);
map.setActiveStatus(true);
expect(map.active).toBe(true);
});
// 验证事件层只接受本楼层的图层,越权告警 131,空值清空
it('accepts own layers, warns 131 for a foreign layer and clears on null', () => {
const { map } = createFixture();
const layer = map.addLayer('event');
const foreign = new GameMap(map.state, 'other', 2, 2).addLayer(
'foreign'
);
const result = logger.catch(() => map.setEventLayer(foreign));
expect(result.info.map(info => info.code)).toContain(131);
expect(map.eventLayer).toBeNull();
map.setEventLayer(layer);
expect(map.eventLayer).toBe(layer);
map.setEventLayer(null);
expect(map.eventLayer).toBeNull();
});
});
describe('GameMap dirty state and resizing', () => {
// 验证自身脏标记与图层脏标记的合并判定
it('reports dirty from itself or any layer', () => {
const { map } = createFixture();
const layer = map.addLayer('dirty');
expect(map.dirty()).toBe(false);
map.markDirty(true);
expect(map.dirty()).toBe(true);
map.markDirty(false);
expect(map.dirty()).toBe(false);
layer.setBlock(1, 0, 0);
expect(map.dirty()).toBe(true);
});
// 验证 resizeLayer 按 keepBlock 保留或清空图块并触发尺寸钩子
it('resizes layers keeping or clearing blocks and notifies hooks', () => {
const { map } = createFixture();
const layer = map.addLayer('resizable');
layer.setBlock(1, 0, 0);
const resizes: [number, number][] = [];
map.addHook({
onResizeLayer: (_layer: IMapLayer, width, height) => {
resizes.push([width, height]);
}
});
map.resizeLayer(3, 2, true);
expect(map.width).toBe(3);
expect(map.height).toBe(2);
expect(layer.getBlock(0, 0)).toBe(1);
expect(resizes).toEqual([[3, 2]]);
map.resizeLayer(1, 1, false);
expect(layer.getBlock(0, 0)).toBe(0);
expect(resizes).toEqual([
[3, 2],
[1, 1]
]);
});
// 验证 compareWith 对缺失参考的图层标脏,对匹配参考的图层保持干净
it('marks layers dirty for a missing reference and compares matching ones', () => {
const { map } = createFixture();
const layer = map.addLayer('layer');
layer.setZIndex(0);
map.compareWith(new Map());
expect(layer.dirty()).toBe(true);
const compared = createFixture().map;
const comparedLayer = compared.addLayer('compared');
comparedLayer.setZIndex(0);
compared.compareWith(new Map([[0, new Uint32Array(4)]]));
expect(comparedLayer.dirty()).toBe(false);
});
// 验证多个不同 zIndex 图层并存、各自块数据独立并按 zIndex 参考比较分别判定
it('keeps layers of different z-index coexisting with independent data', () => {
const { map } = createFixture();
const low = map.addLayer('low');
const mid = map.addLayer('mid');
const high = map.addLayer('high');
low.setZIndex(1);
mid.setZIndex(5);
high.setZIndex(9);
low.setBlock(3, 0, 0);
mid.setBlock(4, 1, 0);
high.setBlock(5, 0, 1);
expect(map.aliasLayerMap.get('low')).toBe(low);
expect(map.aliasLayerMap.get('mid')).toBe(mid);
expect(map.aliasLayerMap.get('high')).toBe(high);
expect(low.zIndex).toBe(1);
expect(mid.zIndex).toBe(5);
expect(high.zIndex).toBe(9);
expect(low.getBlock(0, 0)).toBe(3);
expect(low.getBlock(1, 0)).toBe(0);
expect(low.getBlock(0, 1)).toBe(0);
expect(mid.getBlock(1, 0)).toBe(4);
expect(mid.getBlock(0, 0)).toBe(0);
expect(high.getBlock(0, 1)).toBe(5);
expect(high.getBlock(0, 0)).toBe(0);
expect(high.getBlock(1, 0)).toBe(0);
expect(map.dirty()).toBe(true);
map.compareWith(
new Map([
[1, new Uint32Array([3, 0, 0, 0])],
[5, new Uint32Array([0, 4, 0, 0])]
])
);
expect(low.dirty()).toBe(false);
expect(mid.dirty()).toBe(false);
expect(high.dirty()).toBe(true);
});
});