mirror of
https://github.com/motajs/template.git
synced 2026-11-09 19:54:30 +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
323 lines
11 KiB
TypeScript
323 lines
11 KiB
TypeScript
// 测试 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);
|
||
});
|
||
});
|