mirror of
https://github.com/motajs/template.git
synced 2026-10-11 15:10:17 +08:00
test(08-10): cover data-system/data-state gap codes and add loader tests
- eventDispatch: assert codes 171 (missing event id) and 172 (non-boolean reduce result) - path/system: assert code 182 when binding a foreign CoreState layer - data-state event: assert code 1001 for an unknown setBlock tile - loader: new loader.test.ts covering PrefixedJSONCProcessor 66/67, DefaultDataLoaderHook 69/success, and MotaDataLoader public surface (66/67/68/69; 68 recorded unreachable)
This commit is contained in:
parent
b932f8cecd
commit
47c6e2c8db
@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { WebLoadStarter } from '@motajs/loader';
|
||||
import { logger } from '@motajs/common';
|
||||
import { Statement, StatementType } from '@motajs/anon-tokyo';
|
||||
import {
|
||||
FaceDirection,
|
||||
@ -155,6 +156,20 @@ describe('event built-ins', () => {
|
||||
expect(fixture.layer.getBlock(1, 0)).toBe(2);
|
||||
});
|
||||
|
||||
// 验证 setBlock 遇到未注册图块时告警码 1001 并跳过
|
||||
it('warns code 1001 when setBlock receives an unknown tile', async () => {
|
||||
const fixture = createFixture();
|
||||
const warn = vi.spyOn(logger, 'warn');
|
||||
|
||||
await invokeBuiltin(
|
||||
getRegistration('setBlock'),
|
||||
{ x: 1, y: 0, tile: 'missing' },
|
||||
fixture.env
|
||||
);
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(1001);
|
||||
});
|
||||
|
||||
// 验证真实注册项移动动态图块并按 safe 分支还原
|
||||
it('moves a dynamic block and respects safe static transfer', async () => {
|
||||
const fixture = createFixture();
|
||||
|
||||
176
packages-user/data-state/src/loader/__test__/loader.test.ts
Normal file
176
packages-user/data-state/src/loader/__test__/loader.test.ts
Normal file
@ -0,0 +1,176 @@
|
||||
// 测试 data-state loader:PrefixedJSONCProcessor 解析、DefaultDataLoaderHook 核心配置守卫与 MotaDataLoader 公开面
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ILoadTask, LoadDataType } from '@motajs/loader';
|
||||
import { logger } from '@motajs/common';
|
||||
import { PrefixedJSONCProcessor } from '../jsoncProcessor';
|
||||
import { DefaultDataLoaderHook } from '../hook';
|
||||
import { MotaDataLoader } from '../loader';
|
||||
|
||||
/** 与生产 loader 一致的 jsonc 前缀分隔串 */
|
||||
const PREFIX = '// --- SYSTEM PREFIX END --- //';
|
||||
|
||||
/** 构造一个仅暴露 url 的最小加载任务桩 */
|
||||
function createTaskStub(url: string): ILoadTask<LoadDataType.Text, unknown> {
|
||||
return { url } as never;
|
||||
}
|
||||
|
||||
/** 构造一个按任务 url 返回带前缀 JSON 的加载启动器桩 */
|
||||
function createStarter() {
|
||||
return {
|
||||
async start(request: { url: string }) {
|
||||
const identifier = request.url.split('/').pop();
|
||||
return {
|
||||
url: request.url,
|
||||
body: null,
|
||||
headers: { get: () => null },
|
||||
arrayBuffer: async () => new ArrayBuffer(0),
|
||||
blob: async () => new Blob(),
|
||||
bytes: async () => new Uint8Array(),
|
||||
json: async () => ({}),
|
||||
text: async () => `${PREFIX}${JSON.stringify({ identifier })}`
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 构造一个按需迭代任务 start() 的加载管理器桩 */
|
||||
function createManager() {
|
||||
const tasks: ILoadTask[] = [];
|
||||
return {
|
||||
addTask(task: ILoadTask): void {
|
||||
tasks.push(task);
|
||||
},
|
||||
async *load(): AsyncIterable<number> {
|
||||
for (const task of [...tasks]) {
|
||||
for await (const loaded of task.start()) {
|
||||
yield loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('PrefixedJSONCProcessor', () => {
|
||||
// 验证携带合法前缀的响应会被解析为对象
|
||||
it('parses the payload after the prefix split', async () => {
|
||||
const processor = new PrefixedJSONCProcessor<{ a: number }>(PREFIX);
|
||||
|
||||
await expect(
|
||||
processor.process(`${PREFIX}{"a":1}`, createTaskStub('x'))
|
||||
).resolves.toEqual({ a: 1 });
|
||||
});
|
||||
|
||||
// 验证响应缺少前缀分隔串时告警码 66 并返回空对象
|
||||
it('warns code 66 when the prefix split string is missing', () => {
|
||||
const processor = new PrefixedJSONCProcessor<unknown>(PREFIX);
|
||||
const result = logger.catch(() =>
|
||||
processor.process('no prefix at all', createTaskStub('core.jsonc'))
|
||||
);
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(66);
|
||||
});
|
||||
|
||||
// 验证前缀后的 JSON 无法解析时告警码 67 并返回空对象
|
||||
it('warns code 67 when the split payload is not valid json', () => {
|
||||
const processor = new PrefixedJSONCProcessor<unknown>(PREFIX);
|
||||
const result = logger.catch(() =>
|
||||
processor.process(
|
||||
`${PREFIX}{not json`,
|
||||
createTaskStub('core.jsonc')
|
||||
)
|
||||
);
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(67);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DefaultDataLoaderHook', () => {
|
||||
// 验证核心配置缺失时告警码 69 且不追加任何额外配置
|
||||
it('warns code 69 when the core config is missing', async () => {
|
||||
const hook = new DefaultDataLoaderHook();
|
||||
const loader = { getConfig: () => null } as never;
|
||||
const result = logger.catch(() => hook.onCoreConfigLoaded(loader));
|
||||
|
||||
await expect(result.ret).resolves.toBeUndefined();
|
||||
expect(result.info.map(info => info.code)).toContain(69);
|
||||
});
|
||||
|
||||
// 验证核心配置存在时按目录与默认值追加全部额外配置
|
||||
it('adds every extra config declared by the core config', async () => {
|
||||
const hook = new DefaultDataLoaderHook();
|
||||
const addExtraConfig = vi.fn();
|
||||
const coreConfig = {
|
||||
content: {
|
||||
enemyDir: 'enemy',
|
||||
itemDir: 'item',
|
||||
mapListDir: 'map-list',
|
||||
tileDir: 'tile',
|
||||
defaults: {
|
||||
enemyDefault: 'enemy-default',
|
||||
itemDefault: 'item-default',
|
||||
mapDefault: 'map-default',
|
||||
tileDefault: 'tile-default'
|
||||
}
|
||||
}
|
||||
};
|
||||
const loader = {
|
||||
getConfig: (id: string) => (id === 'core' ? coreConfig : null),
|
||||
addExtraConfig
|
||||
} as never;
|
||||
|
||||
await hook.onCoreConfigLoaded(loader);
|
||||
|
||||
expect(addExtraConfig).toHaveBeenCalledWith('enemy', 'enemy');
|
||||
expect(addExtraConfig).toHaveBeenCalledWith('item', 'item');
|
||||
expect(addExtraConfig).toHaveBeenCalledWith('map-list', 'map-list');
|
||||
expect(addExtraConfig).toHaveBeenCalledWith('tile', 'tile');
|
||||
expect(addExtraConfig).toHaveBeenCalledWith(
|
||||
'enemy-default',
|
||||
'enemy-default'
|
||||
);
|
||||
expect(addExtraConfig).toHaveBeenCalledWith(
|
||||
'item-default',
|
||||
'item-default'
|
||||
);
|
||||
expect(addExtraConfig).toHaveBeenCalledWith(
|
||||
'map-default',
|
||||
'map-default'
|
||||
);
|
||||
expect(addExtraConfig).toHaveBeenCalledWith(
|
||||
'tile-default',
|
||||
'tile-default'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MotaDataLoader', () => {
|
||||
// 验证加载器依次加载核心与额外配置、兑现 loaded 并暴露已加载配置
|
||||
it('loads core and extra configs, exposes them and resolves loaded', async () => {
|
||||
const loader = new MotaDataLoader(
|
||||
createManager() as never,
|
||||
createStarter() as never
|
||||
);
|
||||
const coreHook = vi.fn(async () => {});
|
||||
const extraHook = vi.fn(async () => {});
|
||||
loader.addHook({
|
||||
onCoreConfigLoaded: coreHook,
|
||||
onExtraConfigLoaded: extraHook
|
||||
});
|
||||
loader.addCoreConfig('core', 'core.jsonc');
|
||||
loader.addExtraConfig('enemy', 'enemy.jsonc');
|
||||
loader.addExtraConfig('item', 'item.jsonc');
|
||||
|
||||
for await (const loaded of loader.start()) {
|
||||
expect(typeof loaded).toBe('number');
|
||||
}
|
||||
await loader.loaded();
|
||||
|
||||
expect(coreHook).toHaveBeenCalledWith(loader);
|
||||
expect(extraHook).toHaveBeenCalledWith(loader);
|
||||
expect(loader.getConfig('enemy')).toEqual({
|
||||
identifier: 'enemy.jsonc'
|
||||
});
|
||||
expect(loader.getConfig('item')).toEqual({ identifier: 'item.jsonc' });
|
||||
expect(loader.getConfig('missing')).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -16,6 +16,7 @@ import {
|
||||
type IGameEventInvocation
|
||||
} from '../types';
|
||||
import { AnonTokyoInterpreter } from '@motajs/anon-tokyo';
|
||||
import { logger } from '@motajs/common';
|
||||
import { IStateBase } from '@user/data-base';
|
||||
|
||||
vi.hoisted(() => {
|
||||
@ -524,6 +525,37 @@ describe('event execute modes and reductions', () => {
|
||||
).resolves.toEqual([true]);
|
||||
expect(calls).toEqual(['valid']);
|
||||
});
|
||||
|
||||
// 验证事件 id 不在事件存储时告警码 171 并跳过该事件
|
||||
it('warns code 171 for an event id missing from the store', async () => {
|
||||
const fixture = createFixture();
|
||||
const warn = vi.spyOn(logger, 'warn');
|
||||
|
||||
await fixture.executor.execute(
|
||||
[invocation('missing', modules.EventTrigger.OnEnter)],
|
||||
{ custom: {} }
|
||||
);
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(171, 'missing');
|
||||
});
|
||||
|
||||
// 验证折叠时事件返回非布尔值告警码 172 并按短路语义继续
|
||||
it('warns code 172 for a non-boolean event result during reduction', async () => {
|
||||
const fixture = createFixture();
|
||||
const warn = vi.spyOn(logger, 'warn');
|
||||
fixture.events.set('non-bool', {
|
||||
trigger: modules.EventTrigger.OnEnter,
|
||||
execute: async () => 5
|
||||
});
|
||||
fixture.executor.setReduce(modules.EventReduceMode.OrReduce);
|
||||
|
||||
await fixture.executor.execute(
|
||||
[invocation('non-bool', modules.EventTrigger.OnEnter)],
|
||||
{ custom: {} }
|
||||
);
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(172, '5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('enter leave hit trigger hooks', () => {
|
||||
|
||||
@ -610,4 +610,16 @@ describe('pathfinding system', () => {
|
||||
await result!.controller.onEnd;
|
||||
expect(result!.controller.done).toBe(true);
|
||||
});
|
||||
|
||||
// 验证向求解器绑定属于其他 CoreState 的图层时告警码 182 并不建立绑定
|
||||
it('warns code 182 when binding a layer from another CoreState', () => {
|
||||
const a = createSystem([1, 1, 1], 3);
|
||||
const b = createSystem([1, 1, 1], 3);
|
||||
|
||||
const result = modules.logger.catch(() =>
|
||||
a.system.finder.useMapLayer(b.layer)
|
||||
);
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(182);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user