fix(01-12): repair event phase type fixtures

- Import the existing block event environment contract. - Type focused invocation, raw-event, and map lifecycle fixtures without changing behavior.
This commit is contained in:
unanmed 2026-09-09 11:33:34 +08:00
parent ce63966367
commit 15d9f25b44
4 changed files with 144 additions and 46 deletions

View File

@ -1,4 +1,9 @@
import { beforeAll, describe, expect, it, vi } from 'vitest';
import {
type IDataCommon,
type IMapRawData,
type ITileStore
} from '@user/data-common';
interface TestModules {
MapState: typeof import('./mapState').MapState;
@ -22,7 +27,7 @@ beforeAll(async () => {
};
});
function createRaw() {
function createRaw(): IMapRawData {
return {
floorId: 'F1',
width: 2,
@ -33,8 +38,8 @@ function createRaw() {
}
function createMapState() {
const tileStore = new modules.TileStore();
const state = {
const tileStore: ITileStore = new modules.TileStore() as never;
const state: IDataCommon = {
tileStore,
itemStore: {},
mapStore: {},
@ -42,7 +47,7 @@ function createMapState() {
roleFace: new modules.RoleFaceBinder(),
faceManager: {},
saveSystem: {}
};
} as never;
return new modules.MapState(tileStore, state);
}
@ -64,7 +69,13 @@ describe('MapState raw event path', () => {
});
describe('MapState malformed raw event structures', () => {
const cases = [
interface MalformedCase {
name: string;
mutate(raw: IMapRawData): void;
code: number;
}
const cases: MalformedCase[] = [
{
name: 'missing raw.map container',
mutate: raw => Reflect.set(raw, 'map', null),

View File

@ -1,4 +1,15 @@
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { type IResizableMapLayer } from './types';
import {
type IDataCommon,
type ITileStore,
SaveCompression
} from '@user/data-common';
vi.hoisted(() => {
vi.stubGlobal('main', { replayChecking: true });
vi.stubGlobal('location', { origin: 'http://localhost' });
});
interface TestModules {
MapState: typeof import('./mapState').MapState;
@ -6,7 +17,6 @@ interface TestModules {
RoleFaceBinder: typeof import('@user/data-common').RoleFaceBinder;
FaceManager: typeof import('@user/data-common').FaceManager;
Dir8FaceHandler: typeof import('@user/data-common').Dir8FaceHandler;
SaveCompression: typeof import('@user/data-common').SaveCompression;
}
let modules: TestModules;
@ -21,8 +31,7 @@ beforeAll(async () => {
TileStore: commonModule.TileStore,
RoleFaceBinder: commonModule.RoleFaceBinder,
FaceManager: commonModule.FaceManager,
Dir8FaceHandler: commonModule.Dir8FaceHandler,
SaveCompression: commonModule.SaveCompression
Dir8FaceHandler: commonModule.Dir8FaceHandler
};
});
@ -30,7 +39,7 @@ function createMapState(
pointEvents: Record<number, Record<number, string>> = {},
blocks: number[] = [1, 1, 1, 1]
) {
const tileStore = new modules.TileStore();
const tileStore: ITileStore = new modules.TileStore() as never;
tileStore.addTile({
num: 1,
id: 'base',
@ -49,7 +58,7 @@ function createMapState(
});
const faceManager = new modules.FaceManager();
faceManager.register(1, new modules.Dir8FaceHandler());
const state = {
const state: IDataCommon = {
tileStore,
itemStore: {},
mapStore: {},
@ -57,7 +66,7 @@ function createMapState(
roleFace: new modules.RoleFaceBinder(),
faceManager,
saveSystem: {}
};
} as never;
const mapState = new modules.MapState(tileStore, state);
const map = mapState.fromRaw({
floorId: 'F1',
@ -66,7 +75,10 @@ function createMapState(
layerAlias: { 0: 'event' },
events: { 0: pointEvents }
});
return { map: map!, layer: map!.getLayerByAlias('event')! };
return {
map: map!,
layer: map!.getLayerByAlias('event')! as IResizableMapLayer
};
}
describe('MapLayer tile defaults snapshots conversion and movement', () => {
@ -94,20 +106,22 @@ describe('MapLayer tile defaults snapshots conversion and movement', () => {
);
expect(dynamicTile.tileEvent().dirty()).toBe(false);
const cleanSave = dynamicTile.saveState();
const cleanSave = dynamicTile.saveState(SaveCompression.NoCompression);
expect(cleanSave.events).toBeUndefined();
dynamicTile.loadState(cleanSave);
dynamicTile.loadState(cleanSave, SaveCompression.NoCompression);
expect(dynamicTile.tileEvent().get()).toEqual(
new Map([[20, 'alternate-event']])
);
expect(dynamicTile.tileEvent().dirty()).toBe(false);
dynamicTile.tileEvent().set(30, 'override-event');
const overrideSave = dynamicTile.saveState();
const overrideSave = dynamicTile.saveState(
SaveCompression.NoCompression
);
const savedEvents = new Map(overrideSave.events!);
dynamicTile.tileEvent().set(30, 'changed-after-save');
expect(overrideSave.events).toEqual(savedEvents);
dynamicTile.loadState(overrideSave);
dynamicTile.loadState(overrideSave, SaveCompression.NoCompression);
expect(dynamicTile.tileEvent().get()).toEqual(
new Map([
[20, 'alternate-event'],
@ -169,9 +183,9 @@ describe('MapLayer point event lifecycle', () => {
expect(point.dirty()).toBe(false);
const compressionLevels = [
modules.SaveCompression.NoCompression,
modules.SaveCompression.LowCompression,
modules.SaveCompression.HighCompression
SaveCompression.NoCompression,
SaveCompression.LowCompression,
SaveCompression.HighCompression
];
for (const compression of compressionLevels) {
point.set(7, 'saved-point');
@ -208,7 +222,7 @@ describe('MapLayer point event lifecycle', () => {
height: 2,
fullMap: new Uint32Array([1, 1, 1, 1])
},
modules.SaveCompression.NoCompression
SaveCompression.NoCompression
);
expect(point.get()).toEqual(new Map([[5, 'raw-point']]));
expect(point.dirty()).toBe(false);
@ -224,8 +238,8 @@ describe('MapLayer point event lifecycle', () => {
expect(layer.dirty()).toBe(true);
const compressionLevels = [
modules.SaveCompression.LowCompression,
modules.SaveCompression.HighCompression
SaveCompression.LowCompression,
SaveCompression.HighCompression
];
for (const compression of compressionLevels) {
const save = map.saveState(compression);

View File

@ -1,17 +1,60 @@
import { beforeAll, describe, expect, it, vi } from 'vitest';
import {
BlockEventType,
type IBlockEventEnv,
type IGameEventInvocation
} from '@user/data-base';
import {
type IDataCommon,
type IGameEventStore,
type IReadonlyGameEvent,
type ITileStore,
EventTrigger
} from '@user/data-common';
import { type IStateSystem } from '../types';
import { EventExecuteMode, EventReduceMode } from './types';
import { AnonTokyoInterpreter } from 'anon-tokyo';
vi.hoisted(() => {
vi.stubGlobal('main', { replayChecking: true });
vi.stubGlobal('location', { origin: 'http://localhost' });
});
const eventTriggers = {
OnEnter: EventTrigger.OnEnter,
OnLeave: EventTrigger.OnLeave,
OnTouch: EventTrigger.OnTouch
} as const;
const blockEventTypes = {
PointEvent: BlockEventType.PointEvent,
TileEvent: BlockEventType.TileEvent
} as const;
const eventExecuteModes = {
Normal: EventExecuteMode.Normal,
CutIfFalsy: EventExecuteMode.CutIfFalsy,
CutIfTruthy: EventExecuteMode.CutIfTruthy
} as const;
const eventReduceModes = {
NoReduce: EventReduceMode.NoReduce,
OrReduce: EventReduceMode.OrReduce,
AndReduce: EventReduceMode.AndReduce
} as const;
interface TestModules {
EventExecutor: typeof import('./executor').EventExecutor;
DefaultHeroMoveTopImpl: typeof import('@user/data-state/hero/moverImpl').DefaultHeroMoveTopImpl;
DefaultHeroMoveTopImpl: typeof import('@user/data-state').DefaultHeroMoveTopImpl;
MapState: typeof import('@user/data-base').MapState;
TileStore: typeof import('@user/data-common').TileStore;
RoleFaceBinder: typeof import('@user/data-common').RoleFaceBinder;
FaceManager: typeof import('@user/data-common').FaceManager;
Dir8FaceHandler: typeof import('@user/data-common').Dir8FaceHandler;
EventTrigger: typeof import('@user/data-common').EventTrigger;
BlockEventType: typeof import('@user/data-base').BlockEventType;
EventExecuteMode: typeof import('./types').EventExecuteMode;
EventReduceMode: typeof import('./types').EventReduceMode;
EventTrigger: typeof eventTriggers;
BlockEventType: typeof blockEventTypes;
EventExecuteMode: typeof eventExecuteModes;
EventReduceMode: typeof eventReduceModes;
}
interface EventCall {
@ -29,22 +72,21 @@ beforeAll(async () => {
vi.stubGlobal('main', { replayChecking: true });
vi.stubGlobal('location', { origin: 'http://localhost' });
const executorModule = await import('./executor');
const moverModule = await import('@user/data-state/hero/moverImpl');
const stateModule = await import('../../../data-state/src/hero');
const baseModule = await import('@user/data-base');
const commonModule = await import('@user/data-common');
const eventModule = await import('./types');
modules = {
EventExecutor: executorModule.EventExecutor,
DefaultHeroMoveTopImpl: moverModule.DefaultHeroMoveTopImpl,
DefaultHeroMoveTopImpl: stateModule.DefaultHeroMoveTopImpl,
MapState: baseModule.MapState,
TileStore: commonModule.TileStore,
RoleFaceBinder: commonModule.RoleFaceBinder,
FaceManager: commonModule.FaceManager,
Dir8FaceHandler: commonModule.Dir8FaceHandler,
EventTrigger: commonModule.EventTrigger,
BlockEventType: baseModule.BlockEventType,
EventExecuteMode: eventModule.EventExecuteMode,
EventReduceMode: eventModule.EventReduceMode
EventTrigger: eventTriggers,
BlockEventType: blockEventTypes,
EventExecuteMode: eventExecuteModes,
EventReduceMode: eventReduceModes
};
});
@ -53,7 +95,7 @@ function createFixture(
staticEvent: string = 'static-enter',
dynamicEvent: string = 'dynamic-enter'
) {
const tileStore = new modules.TileStore();
const tileStore: ITileStore = new modules.TileStore() as never;
tileStore.addTile({
num: 1,
id: 'static',
@ -72,7 +114,7 @@ function createFixture(
});
const faceManager = new modules.FaceManager();
faceManager.register(1, new modules.Dir8FaceHandler());
const commonState = {
const commonState: IDataCommon = {
tileStore,
itemStore: {},
mapStore: {},
@ -80,7 +122,7 @@ function createFixture(
roleFace: new modules.RoleFaceBinder(),
faceManager,
saveSystem: {}
};
} as never;
const maps = new modules.MapState(tileStore, commonState);
const map = maps.fromRaw({
floorId: 'F1',
@ -93,18 +135,35 @@ function createFixture(
const dynamic = layer.createDynamic(2, 1, 0);
dynamic.set(2);
const events = new Map<string, object>();
const store = {
getEvent(id: string) {
return events.get(id) ?? null;
const store: IGameEventStore = {
addEvent() {},
getEvent<
P extends Record<string, any>,
E extends Record<string, any>,
R = void
>(id: string): IReadonlyGameEvent<P, E, R> | null {
return events.get(id) as never;
}
};
const executor = new modules.EventExecutor({}, () => store);
const executor = new modules.EventExecutor(
{} as AnonTokyoInterpreter,
() => store
);
const state = {
maps,
eventSystem: { executor }
};
const mover = new modules.DefaultHeroMoveTopImpl(state);
return { events, executor, layer, map: map!, dynamic, mover, state };
const fixtureState: IStateSystem & IDataCommon = state as never;
const mover = new modules.DefaultHeroMoveTopImpl(fixtureState);
return {
events,
executor,
layer,
map: map!,
dynamic,
mover,
state: fixtureState
};
}
function addEvent(
@ -139,8 +198,18 @@ function addEvent(
});
}
function invocation(id: string, trigger: number) {
return { id, env: { trigger } };
function invocation(id: string, trigger: EventTrigger): IGameEventInvocation {
const env: IBlockEventEnv = {
state: {} as IDataCommon,
type: BlockEventType.CommonEvent,
trigger,
heroLocator: { x: 0, y: 0 },
triggerLocator: null,
tile: null,
layer: null,
map: null
};
return { id, env };
}
describe('source-aware matching dispatch', () => {

View File

@ -1,5 +1,9 @@
import { logger } from '@motajs/common';
import { IBlockEventParam, IGameEventInvocation } from '@user/data-base';
import {
IBlockEventEnv,
IBlockEventParam,
IGameEventInvocation
} from '@user/data-base';
import { IGameEventStore } from '@user/data-common';
import { AnonTokyoInterpreter } from 'anon-tokyo';
import { EventExecuteMode, EventReduceMode, IGameEventExecutor } from './types';