From 8901221e091f64787d3b964ec7997d2648a021d9 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 28 Aug 2026 00:37:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui-migration):=20=E6=98=BE=E5=BC=8F?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E6=BA=90=E6=95=B0=E6=8D=AE=E9=A1=B6=E5=B1=82?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E9=94=AE=EF=BC=8C=E7=A6=81=E6=AD=A2=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 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) --- .../scripts/test/ui-source.test.mjs | 46 ++++++++++++++++++- .../scripts/ui-migration/gameabc-source.mjs | 19 ++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/cocoscreator_projects/scripts/test/ui-source.test.mjs b/cocoscreator_projects/scripts/test/ui-source.test.mjs index 6e1609c..4dd140c 100644 --- a/cocoscreator_projects/scripts/test/ui-source.test.mjs +++ b/cocoscreator_projects/scripts/test/ui-source.test.mjs @@ -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, '对象总数'); diff --git a/cocoscreator_projects/scripts/ui-migration/gameabc-source.mjs b/cocoscreator_projects/scripts/ui-migration/gameabc-source.mjs index b64d967..324c20d 100644 --- a/cocoscreator_projects/scripts/ui-migration/gameabc-source.mjs +++ b/cocoscreator_projects/scripts/ui-migration/gameabc-source.mjs @@ -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 ?? [] }));