fix(ui-migration): 显式校验源数据顶层数组键,禁止静默兜底
添加 readArray() 辅助函数,对四处顶层数组键 (ObjectList, ImageFileList,
LayerList, GroupList) 显式校验,缺失或非数组时即刻抛 SourceError,
不再用 ?? [] 蒙混。遵循 CLAUDE.md 第二准则:数据缺失/非法一律显式暴露。
新增四个测试用例分别验证各键缺失时的错误行为。删除测试文件中未使用的
mkdirSync 导入。条目级缺省值 (entry.Event ?? {}, ObjectList ?? []) 保留,
因为单个对象/层没有事件/对象是合理的合法缺省。
真实数据护栏数字不变: 991 对象 / 55 界面 / 80 组 / 439 图 / 139 多帧图。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { join } from 'node:path';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { ROOT } from '../lib/paths.mjs';
|
||||
import { loadSource, SourceError } from '../ui-migration/gameabc-source.mjs';
|
||||
@@ -67,6 +67,50 @@ test('loadSource 对缺失文件显式抛错', () => {
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('loadSource 缺少顶层数组键 ObjectList 时显式抛错', () => {
|
||||
const dir = fakeSource({
|
||||
'gameabc_Object.json': { Property: [] }, // 缺 ObjectList
|
||||
});
|
||||
try {
|
||||
assert.throws(() => loadSource(dir), SourceError);
|
||||
assert.throws(() => loadSource(dir), /ObjectList/);
|
||||
assert.throws(() => loadSource(dir), /gameabc_Object\.json/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('loadSource 缺少顶层数组键 ImageFileList 时显式抛错', () => {
|
||||
const dir = fakeSource({
|
||||
'gameabc_Image.json': { id: 1 }, // 缺 ImageFileList
|
||||
});
|
||||
try {
|
||||
assert.throws(() => loadSource(dir), SourceError);
|
||||
assert.throws(() => loadSource(dir), /ImageFileList/);
|
||||
assert.throws(() => loadSource(dir), /gameabc_Image\.json/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('loadSource 缺少顶层数组键 LayerList 时显式抛错', () => {
|
||||
const dir = fakeSource({
|
||||
'gameabc_Layer.json': { Property: {} }, // 缺 LayerList
|
||||
});
|
||||
try {
|
||||
assert.throws(() => loadSource(dir), SourceError);
|
||||
assert.throws(() => loadSource(dir), /LayerList/);
|
||||
assert.throws(() => loadSource(dir), /gameabc_Layer\.json/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('loadSource 缺少顶层数组键 GroupList 时显式抛错', () => {
|
||||
const dir = fakeSource({
|
||||
'gameabc_GroupList.json': { data: [] }, // 缺 GroupList
|
||||
});
|
||||
try {
|
||||
assert.throws(() => loadSource(dir), SourceError);
|
||||
assert.throws(() => loadSource(dir), /GroupList/);
|
||||
assert.throws(() => loadSource(dir), /gameabc_GroupList\.json/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('loadSource 在真实源数据上得到已知规模(回归护栏)', () => {
|
||||
const s = loadSource(REAL_SOURCE);
|
||||
assert.equal(s.objects.size, 991, '对象总数');
|
||||
|
||||
@@ -16,20 +16,31 @@ function readJson(dir, name) {
|
||||
}
|
||||
}
|
||||
|
||||
function readArray(dir, fileName, key) {
|
||||
const json = readJson(dir, fileName);
|
||||
const arr = json[key];
|
||||
if (!Array.isArray(arr)) {
|
||||
throw new SourceError(
|
||||
`源数据 ${fileName} 缺少顶层数组 "${key}"(实际为 ${typeof arr})`,
|
||||
);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取并校验 gameabc 导出数据。
|
||||
* 源数组用 `{}` 占位空槽(下标即 ID),须跳过。
|
||||
*/
|
||||
export function loadSource(outputDir) {
|
||||
const objects = new Map();
|
||||
for (const entry of readJson(outputDir, 'gameabc_Object.json').ObjectList ?? []) {
|
||||
for (const entry of readArray(outputDir, 'gameabc_Object.json', 'ObjectList')) {
|
||||
if (!entry || !entry.Property) continue;
|
||||
const p = entry.Property;
|
||||
objects.set(p.ObjectID, { ...p, events: Object.keys(entry.Event ?? {}) });
|
||||
}
|
||||
|
||||
const images = new Map();
|
||||
for (const i of readJson(outputDir, 'gameabc_Image.json').ImageFileList ?? []) {
|
||||
for (const i of readArray(outputDir, 'gameabc_Image.json', 'ImageFileList')) {
|
||||
if (!i || i.id === undefined) continue;
|
||||
if (i.w * i.h !== i.frame_all) {
|
||||
throw new SourceError(
|
||||
@@ -39,7 +50,7 @@ export function loadSource(outputDir) {
|
||||
images.set(i.id, i);
|
||||
}
|
||||
|
||||
const layers = (readJson(outputDir, 'gameabc_Layer.json').LayerList ?? [])
|
||||
const layers = readArray(outputDir, 'gameabc_Layer.json', 'LayerList')
|
||||
.filter((l) => l && l.Property)
|
||||
.map((l) => ({
|
||||
layerId: l.Property.LayerID,
|
||||
@@ -47,7 +58,7 @@ export function loadSource(outputDir) {
|
||||
objectIds: l.ObjectList ?? [],
|
||||
}));
|
||||
|
||||
const groups = (readJson(outputDir, 'gameabc_GroupList.json').GroupList ?? [])
|
||||
const groups = readArray(outputDir, 'gameabc_GroupList.json', 'GroupList')
|
||||
.filter((g) => g && g.GroupID !== undefined)
|
||||
.map((g) => ({ groupId: g.GroupID, objectIds: g.ObjectList ?? [] }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user