diff --git a/cocoscreator_projects/framework-tests/legacy-layer-migration/spirit-to-node.test.mjs b/cocoscreator_projects/framework-tests/legacy-layer-migration/spirit-to-node.test.mjs new file mode 100644 index 0000000..a3e261d --- /dev/null +++ b/cocoscreator_projects/framework-tests/legacy-layer-migration/spirit-to-node.test.mjs @@ -0,0 +1,56 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { mapSpiritTypeToComponent } from '../../scripts/legacy-layer-to-cocos-prefab.mjs'; + +// 绝对路径 -- tests 从 cocoscreator_projects/ 运行, 相对路径会解析到仓库根, +// 漏掉 cocoscreator_projects/ 段导致 ENOENT (Task 2 fix round 1 Finding 3). +const SEMANTICS = JSON.parse(readFileSync( + 'G:/Works/YouleGamesCocosCreator/cocoscreator_projects/framework-tests/legacy-layer-migration/fixtures/spirit-semantics.json', + 'utf8' +)); + +test('type=0 → cc.Sprite', () => { + assert.equal(mapSpiritTypeToComponent('0'), 'cc.Sprite'); +}); + +test('type=1 → cc.Label', () => { + assert.equal(mapSpiritTypeToComponent('1'), 'cc.Label'); +}); + +test('type=3 → cc.ProgressBar', () => { + assert.equal(mapSpiritTypeToComponent('3'), 'cc.ProgressBar'); +}); + +test('type=4 → 已知组件(不能是 unknown)', () => { + // 数据源权威: SpiritType 4 已在 fixtures/spirit-semantics.json 逆向为 cc.EditBox (Task 5). + assert.notEqual(mapSpiritTypeToComponent('4'), 'unknown'); +}); + +test('type=5 → 已知组件(不能是 unknown)', () => { + // 数据源权威: SpiritType 5 已在 fixtures/spirit-semantics.json 逆向为 cc.Sprite (SLICED) (Task 5). + assert.notEqual(mapSpiritTypeToComponent('5'), 'unknown'); +}); + +test('未知 type → throw(不静默兜底)', () => { + // 数据源权威唯一、下游不兜底: 未知 SpiritType 必须显式报错. + assert.throws(() => mapSpiritTypeToComponent('99'), /Unknown SpiritType/); +}); + +test('fixtures 中 component="unknown" 的 type 也必须 throw(safety net for Task 5 未完成时)', () => { + // 若 Task 5 留有 spirit_types[*].component === 'unknown' 的条目, + // mapSpiritTypeToComponent 必须 throw 而非静默返回 'unknown'. + // 当前 fixtures 全部 5 项已逆向完成 (Type 0/1/3/4/5 均有 component), + // 该测试作为 future regression safety net 自动通过. + for (const [type, entry] of Object.entries(SEMANTICS.spirit_types)) { + if (entry.component === 'unknown') { + assert.throws( + () => mapSpiritTypeToComponent(type), + new RegExp(`SpiritType "${type}" component not yet reverse-engineered`) + ); + } + } + // 当前 fixtures 5/5 全部已逆向, 循环不触发 throw, 测试通过. + assert.equal(Object.keys(SEMANTICS.spirit_types).length, 5, + 'fixtures 期望 5 个 SpiritType (0/1/3/4/5) 全部已逆向'); +}); diff --git a/cocoscreator_projects/scripts/legacy-layer-to-cocos-prefab.mjs b/cocoscreator_projects/scripts/legacy-layer-to-cocos-prefab.mjs index ba30514..1f6544b 100644 --- a/cocoscreator_projects/scripts/legacy-layer-to-cocos-prefab.mjs +++ b/cocoscreator_projects/scripts/legacy-layer-to-cocos-prefab.mjs @@ -1,6 +1,23 @@ import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { DOMParser } from '@xmldom/xmldom'; +// SpiritType → cc.* 组件映射表 (数据源). 读取 fixtures/spirit-semantics.json, +// 该文件由 Task 5 (gameabc.min.js 逆向) 维护, 下游不得兜底. +// 路径: scripts/legacy-layer-to-cocos-prefab.mjs → ../framework-tests/legacy-layer-migration/fixtures/spirit-semantics.json +const __dirname = dirname(fileURLToPath(import.meta.url)); +const SEMANTICS_PATH = join( + __dirname, + '../framework-tests/legacy-layer-migration/fixtures/spirit-semantics.json' +); + +let _semantics = null; +function loadSemantics() { + if (!_semantics) _semantics = JSON.parse(readFileSync(SEMANTICS_PATH, 'utf8')); + return _semantics; +} + /** * 读取 Game_Surface_3 风格的 Layer XML (UTF-8 编码), 解析 Layer + Spirit 字段. * @@ -137,3 +154,25 @@ export function buildGroupParents(spirits) { return { groupParents, rootChildSpiritIds }; } + +/** + * 将 SpiritType (XML 中的字符串 "0"-"5") 映射到 Cocos 组件类名. + * + * 数据源权威唯一: 读取 fixtures/spirit-semantics.json (Task 5 逆向产出); + * 下游不兜底 — 未知 type 或 component="unknown" 一律 throw, 不静默返回 fallback. + * + * @param {string} type - SpiritType 字符串 (e.g. "0", "1", "3", "4", "5") + * @returns {string} Cocos 组件类名 (e.g. "cc.Sprite", "cc.Label", "cc.ProgressBar", "cc.EditBox") + * @throws {Error} 未知 SpiritType 或 component 字段为 "unknown" 时抛错 + */ +export function mapSpiritTypeToComponent(type) { + const semantics = loadSemantics(); + const entry = semantics.spirit_types[type]; + if (!entry) { + throw new Error(`Unknown SpiritType "${type}" - not in fixtures/spirit-semantics.json`); + } + if (entry.component === 'unknown') { + throw new Error(`SpiritType "${type}" component not yet reverse-engineered - complete Task 5 (gameabc.min.js 逆向) first`); + } + return entry.component; +}