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:
AncTe committed 2026-10-08 14:45:10 +08:00
1 parent b932f8cecd
commit 47c6e2c8db
4 files changed
+235

No files matched your search

@@ -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();
@@ -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();
});
});