Files
template/packages-user/data-base/src/map/__test__/mapState.test.ts
T
AncTe 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

323 lines
11 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.
// 测试 MapState 的楼层注册、原始数据校验、激活/分区与参考基准比较
import { afterAll, describe, expect, it, vi } from 'vitest';
import {
type IDataCommon,
type IMapRawData,
type ITileRawData,
Dir8FaceHandler,
FaceGroup,
FaceManager,
RoleFaceBinder,
TileStore,
TileType
} from '@user/data-common';
import { logger } from '@motajs/common';
import { MapState } from '../mapState';
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
};
}
/** 构造一个可用的 MapState 及其底层公共层对象 */
function createFixture(): MapState {
const tileStore = new TileStore();
tileStore.addTile(createTileData(1, 'base', { 10: 'base-event' }));
tileStore.addTile(
createTileData(2, 'alternate', { 20: 'alternate-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 new MapState(state);
}
/** 构造一份合法的楼层原始数据 */
function createRaw(): IMapRawData {
return {
floorId: 'F1',
width: 2,
map: { 0: [1, 2, 1, 2] },
layerAlias: { 0: 'event' },
events: { 0: { 1: { 5: 'point-event' } } }
};
}
describe('MapState floor registration', () => {
// 验证 createMap 注册楼层、重复注册告警 121 并覆盖旧实例
it('registers floors and warns 121 when the floor already exists', () => {
const mapState = createFixture();
const first = mapState.createMap('F1', 2, 2);
expect(mapState.getMap('F1')).toBe(first);
expect(mapState.maps).toEqual(['F1']);
expect(mapState.getMap('unknown')).toBeNull();
const repeated = logger.catch(() => mapState.createMap('F1', 2, 2));
expect(repeated.info.map(info => info.code)).toContain(121);
expect(mapState.getMap('F1')).not.toBe(first);
expect(mapState.maps).toEqual(['F1']);
});
// 验证 createMap 创建的地图可直接写入并读取图层、块、点事件与事件层绑定
it('generates and reads content on a map created by createMap', () => {
const mapState = createFixture();
const map = mapState.createMap('F1', 2, 2);
const layer = map.addLayer('event');
map.setEventLayer(layer);
layer.setBlock(5, 0, 0);
layer.setZIndex(0);
layer.event(1, 0)!.set(9, 'gen-event');
expect(mapState.getMap('F1')).toBe(map);
expect([...map.layerList].find(item => item.alias === 'event')).toBe(
layer
);
expect(map.eventLayer).toBe(layer);
expect(layer.getBlock(0, 0)).toBe(5);
expect(layer.getBlock(1, 0)).toBe(0);
expect(layer.event(1, 0)!.get()).toEqual(new Map([[9, 'gen-event']]));
expect(map.dirty()).toBe(true);
});
// 验证 setMapList 按传入顺序原样存储楼层 id(不去重,重复由调用方保证)
it('stores the ordered floor list as given', () => {
const mapState = createFixture();
mapState.setMapList(['A', 'B', 'A', 'C']);
expect(mapState.maps).toEqual(['A', 'B', 'A', 'C']);
});
// 验证 fromRaw 构建楼层、绑定事件层与点事件,并默认处于未激活
it('builds a floor, binds the event layer and stays inactive by default', () => {
const mapState = createFixture();
const map = mapState.fromRaw(createRaw());
expect(map).not.toBeNull();
const layer = [...map!.layerList].find(item => item.alias === 'event');
expect(layer).not.toBeNull();
expect(map!.eventLayer).toBe(layer);
expect(layer!.getPointEvent(1, 0)).toEqual(
new Map([[5, 'point-event']])
);
expect(map!.active).toBe(false);
expect(mapState.isMapActive('F1')).toBe(false);
expect(mapState.getActiveMap('F1')).toBeNull();
expect(mapState.getMap('F1')).toBe(map);
});
});
describe('MapState raw validation codes', () => {
interface MalformedCase {
name: string;
mutate(raw: IMapRawData): void;
code: number;
}
// 仅保留 shipped fromRaw 仍会发出的 60/61;62/63/64 已无发出点(Q1=A)
const cases: MalformedCase[] = [
{
name: 'unequal layer lengths',
mutate: raw => {
Reflect.set(raw, 'map', {
0: [1, 1, 1, 1],
1: [1, 1, 1]
});
Reflect.set(raw, 'layerAlias', { 0: 'a', 1: 'b' });
Reflect.set(raw, 'events', { 0: {}, 1: {} });
},
code: 60
},
{
name: 'area not divisible by width',
mutate: raw => {
Reflect.set(raw, 'map', { 0: [1, 1, 1, 1, 1] });
Reflect.set(raw, 'layerAlias', { 0: 'a' });
Reflect.set(raw, 'events', { 0: {} });
},
code: 61
}
];
it.each(cases)('$name is rejected with code $code', testCase => {
const mapState = createFixture();
const raw = createRaw();
testCase.mutate(raw);
const result = logger.catch(() => mapState.fromRaw(raw));
expect(result.ret).toBeNull();
expect(result.info.map(info => info.code)).toContain(testCase.code);
expect(mapState.getMap('F1')).toBeNull();
});
});
describe('MapState activation and areas', () => {
// 验证激活状态切换、getActiveMap 与三类迭代器
it('toggles activation and iterates active and inactive floors', () => {
const mapState = createFixture();
const first = mapState.createMap('F1', 2, 2);
const second = mapState.createMap('F2', 2, 2);
mapState.setMapActiveStatus('F1', true);
expect(mapState.isMapActive('F1')).toBe(true);
expect(mapState.getActiveMap('F1')).toBe(first);
expect(mapState.getActiveMap('F2')).toBeNull();
expect([...mapState.iterateActiveMaps()]).toEqual([['F1', first]]);
expect([...mapState.iterateInactiveMaps()]).toEqual([['F2', second]]);
expect([...mapState.iterateAllMaps()]).toEqual([
['F1', first],
['F2', second]
]);
mapState.setMapActiveStatus('unknown', true);
expect(mapState.isMapActive('unknown')).toBe(false);
});
// 验证按分区批量激活与取消激活
it('activates and deactivates every floor of an area', () => {
const mapState = createFixture();
mapState.createMap('A', 2, 2);
mapState.createMap('B', 2, 2);
mapState.createMap('C', 2, 2);
mapState.createMap('D', 2, 2);
mapState.setMapList(['A', 'B', 'C', 'D']);
mapState.setArea(new Set([[{ start: 0, end: 1 }]]));
mapState.activeArea('B');
expect(mapState.isMapActive('A')).toBe(true);
expect(mapState.isMapActive('B')).toBe(true);
expect(mapState.isMapActive('C')).toBe(false);
mapState.deactiveArea('A');
expect(mapState.isMapActive('A')).toBe(false);
expect(mapState.isMapActive('B')).toBe(false);
mapState.activeArea('unknown');
expect(mapState.isMapActive('C')).toBe(false);
});
// 验证自动分区激活器进入新分区时切换激活集
it('switches the active area when the automator enters another floor', () => {
const mapState = createFixture();
mapState.createMap('A', 2, 2);
mapState.createMap('B', 2, 2);
mapState.setMapList(['A', 'B']);
mapState.setArea(
new Set([[{ start: 0, end: 0 }], [{ start: 1, end: 1 }]])
);
mapState.autoActivateFloor('A');
expect(mapState.isMapActive('A')).toBe(false);
mapState.useAutoActivitor(true);
mapState.autoActivateFloor('A');
expect(mapState.isMapActive('A')).toBe(true);
mapState.autoActivateFloor('B');
expect(mapState.isMapActive('A')).toBe(false);
expect(mapState.isMapActive('B')).toBe(true);
mapState.useAutoActivitor(false);
mapState.autoActivateFloor('A');
expect(mapState.isMapActive('A')).toBe(false);
});
});
describe('MapState reference comparison', () => {
// 验证 compareWith 对缺失楼层标记为脏,且对已有楼层按参考数组判定
it('marks missing floors dirty and compares registered floors', () => {
const mapState = createFixture();
const map = mapState.fromRaw(createRaw())!;
mapState.compareWith(new Map());
expect(map.dirty()).toBe(true);
const compared = createFixture();
compared.fromRaw(createRaw());
compared.compareWith(
new Map([['F1', new Map([[0, new Uint32Array([1, 2, 1, 2])]])]])
);
expect(compared.getMap('F1')!.dirty()).toBe(false);
});
// 验证 compareWith 只生效一次,之后创建的新楼层直接视为全脏
it('applies the reference once and marks later floors dirty', () => {
const mapState = createFixture();
mapState.compareWith(new Map());
const late = mapState.createMap('F9', 2, 2);
expect(late.dirty()).toBe(true);
});
// 验证不活跃楼层不会被活跃判定选中,供通行谓词侧读取事件层
it('exposes the active floor event layer for pass predicates', () => {
const mapState = createFixture();
const map = mapState.fromRaw(createRaw())!;
const activeLayer = () =>
mapState.getActiveMap('F1')?.eventLayer ?? null;
expect(activeLayer()).toBeNull();
mapState.setMapActiveStatus('F1', true);
expect(activeLayer()).toBe(map.eventLayer);
});
});