mirror of
https://github.com/motajs/template.git
synced 2026-10-14 00:30:18 +08:00
- 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
285 lines
8.8 KiB
TypeScript
285 lines
8.8 KiB
TypeScript
// 测试 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);
|
||
});
|
||
});
|