fix(scripts): final review Critical+Important fixes (Widget alignment, full readLayerXml, Label _string, atlas selection, hard-coded paths, exit code, dead code removal)
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import iconv from 'iconv-lite';
|
||||
|
||||
const buf = readFileSync('framework-tests/legacy-layer-migration/fixtures/Layer00002.xml');
|
||||
// NOTE: 实际 Layer00002.xml 是 UTF-8 编码 (XML 头声明 + 字节验证),
|
||||
// spec/brief 要求的 GB18030 在该文件上会乱码 (微信登录背景 不可读).
|
||||
// 转换工具应支持两种编码, 优先按 XML 声明, 否则按 GB18030 fallback.
|
||||
const text = iconv.decode(buf, 'utf8');
|
||||
console.log('decoded length:', text.length);
|
||||
console.log('contains 微信登录背景:', text.includes('微信登录背景'));
|
||||
console.log('contains Login_Layer:', text.includes('Login_Layer'));
|
||||
+46
-1
@@ -1,6 +1,6 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { computeLpos, computeContentSize } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
import { computeLpos, computeContentSize, computeWidgetAlignment } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
|
||||
test('computeLpos 锚点转换(实测验证:渠道logo)', () => {
|
||||
const spirit = { x: '734', y: '143', w: '59', h: '148' };
|
||||
@@ -47,4 +47,49 @@ test('computeContentSize 默认 SizeCalcMode=0(字段缺失视作 0)', () =>
|
||||
test('computeContentSize SizeCalcMode=1 → throw(不静默)', () => {
|
||||
const spirit = { w: '100', h: '50', sizeCalcMode: '1' };
|
||||
assert.throws(() => computeContentSize(spirit), /SizeCalcMode=1.*reverse gameabc\.min\.js/);
|
||||
});
|
||||
|
||||
test('computeContentSize SizeCalcMode=2 uses ImgResID natural size and relative ratios', () => {
|
||||
const spirit = {
|
||||
id: '2', imgResID: '20', w: '1280', h: '720', sizeCalcMode: '2',
|
||||
sizeRelaWidth: '10000', sizeRelaHeight: '10000',
|
||||
};
|
||||
const size = computeContentSize(spirit);
|
||||
assert.equal(size.width, 1280);
|
||||
assert.equal(size.height, 720);
|
||||
});
|
||||
|
||||
test('computeWidgetAlignment derives full-screen, normal, and top-left alignment cases', () => {
|
||||
const fullScreen = computeWidgetAlignment({
|
||||
name: '微信登录背景', x: '0', y: '0', w: '1280', h: '720', lpos: { x: 0, y: 0 },
|
||||
});
|
||||
assert.equal(fullScreen._alignFlags, 45);
|
||||
assert.equal(fullScreen._left, 0);
|
||||
assert.equal(fullScreen._right, 0);
|
||||
assert.equal(fullScreen._top, 0);
|
||||
assert.equal(fullScreen._bottom, 0);
|
||||
assert.equal(fullScreen._horizontalCenter, undefined);
|
||||
assert.equal(fullScreen._verticalCenter, undefined);
|
||||
assert.equal(fullScreen._originalWidth, 100);
|
||||
assert.equal(fullScreen._originalHeight, 100);
|
||||
|
||||
const normal = computeWidgetAlignment({
|
||||
name: '渠道logo', x: '734', y: '143', w: '59', h: '148', lpos: { x: 123.5, y: 0 },
|
||||
});
|
||||
assert.equal(normal._alignFlags, 16);
|
||||
assert.equal(normal._horizontalCenter, 123.5);
|
||||
assert.equal(normal._verticalCenter, 0);
|
||||
assert.equal(normal._left, undefined);
|
||||
assert.equal(normal._right, undefined);
|
||||
assert.equal(normal._top, undefined);
|
||||
assert.equal(normal._bottom, undefined);
|
||||
|
||||
const topLeft = computeWidgetAlignment({
|
||||
name: '版本显示', x: '16', y: '9', w: '96', h: '31',
|
||||
});
|
||||
assert.equal(topLeft._alignFlags, 9);
|
||||
assert.equal(topLeft._left, -576);
|
||||
assert.equal(topLeft._top, 335.5);
|
||||
assert.equal(topLeft._horizontalCenter, undefined);
|
||||
assert.equal(topLeft._verticalCenter, undefined);
|
||||
});
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
"rationale": "直接读精灵自身 Width/Height 字段作为 _contentSize; gameabc.min.js 中 SizeCalcMode=0 是最简路径 (背景图 Layer00001/00002 全屏 1280×720 即此路径)"
|
||||
},
|
||||
"2": {
|
||||
"formula": "parent_size = lookup_Spirit(Spirit.SizeParentID)._contentSize; _contentSize = { width: parent_size.width * Spirit.SizeRelaWidth / 10000, height: parent_size.height * Spirit.SizeRelaHeight / 10000 }; 当 WidthCalcMode=0 时回退到 Spirit.Width (parseFloat), 当 WidthCalcMode=1 时用 SizeRelaWidth 公式; HeightCalcMode 同理",
|
||||
"formula": "image_natural_size = lookup gameabc_Image.json ImageFileList[imgResID === Spirit.ImgResID].w_all/h_all; parent_size = { width: image_natural_size.width, height: image_natural_size.height }; _contentSize = { width: parent_size.width * Spirit.SizeRelaWidth / 10000, height: parent_size.height * Spirit.SizeRelaHeight / 10000 }; 当 WidthCalcMode=0 时回退到 Spirit.Width (parseFloat), 当 WidthCalcMode=1 时用 SizeRelaWidth 公式; HeightCalcMode 同理",
|
||||
"rationale": "Layer00001 '友乐背景' (SizeCalcMode=2, SizeParentID=21, SizeRelaWidth=10000) / Layer00002 '微信登录背景' (SizeCalcMode=2, SizeParentID=20, SizeRelaWidth=10000) 全量采样: 全部 WidthCalcMode=1, HeightCalcMode=1, SizeRelaWidth=10000, SizeRelaHeight=10000; 即按 100% 比例继承父 spirit 尺寸; 这种 pattern 用于 '充满父容器' 的背景 (1280×720 全屏背景), SizeParentID 指向另一个 1280×720 的 spirit"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { readLayerXml, emitPrefabJson } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
import { ROOT, HOST_PROJECT } from '../../scripts/lib/paths.mjs';
|
||||
|
||||
// PoC 阶段 3 端到端: Layer00002.xml Login_Layer (18 spirits, group 2, 包含 Spirit405 渠道logo +
|
||||
// Spirit177 游客登录 anchor-test cases). 验证生成 prefab 与 fixtures/Login_Layer.prefab 在
|
||||
@@ -16,8 +18,8 @@ import { readLayerXml, emitPrefabJson } from '../../scripts/legacy-layer-to-coco
|
||||
// layer.spirits 只保留 id=405 (渠道logo) + id=177 (游客登录) 两个 anchor-test case, 完全避开
|
||||
// ImgResID 427 的依赖. 这是 Task 11 fix round 1 的 spec intent: 验证 anchor 公式精度,
|
||||
// 而不是验证 CLI 完整管线 (后者已由 poc-iconbutton.test.mjs / poc-protocol.test.mjs 覆盖).
|
||||
const FIXTURE_XML = 'G:/Works/YouleGamesCocosCreator/cocoscreator_projects/framework-tests/legacy-layer-migration/fixtures/Layer00002.xml';
|
||||
const REFERENCE_PREFAB = 'G:/Works/YouleGamesCocosCreator/cocoscreator_projects/YouleNexus/assets/framework/ui/prefabs/Login_Layer.prefab';
|
||||
const FIXTURE_XML = join(ROOT, 'framework-tests', 'legacy-layer-migration', 'fixtures', 'Layer00002.xml');
|
||||
const REFERENCE_PREFAB = join(HOST_PROJECT, 'assets', 'framework', 'ui', 'prefabs', 'Login_Layer.prefab');
|
||||
|
||||
function buildFilteredLayer() {
|
||||
const layer = readLayerXml(FIXTURE_XML);
|
||||
@@ -27,6 +29,43 @@ function buildFilteredLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
||||
function nodeComponent(json, node, componentType) {
|
||||
return json[node._components.map(ref => ref.__id__).find(index => json[index]?.__type__ === componentType)];
|
||||
}
|
||||
|
||||
test('Login_Layer full-screen and normal node geometry matches reference strictly', () => {
|
||||
const layer = readLayerXml(FIXTURE_XML);
|
||||
layer.spirits = layer.spirits.filter(s => s.id === '2' || s.id === '405');
|
||||
const generated = emitPrefabJson(layer);
|
||||
const expected = JSON.parse(readFileSync(REFERENCE_PREFAB, 'utf8'));
|
||||
|
||||
const background = generated.find(o => o.__type__ === 'cc.Node' && o._name === '微信登录背景');
|
||||
const backgroundExpected = expected.find(o => o.__type__ === 'cc.Node' && o._name === '微信登录背景');
|
||||
assert.ok(background);
|
||||
assert.ok(backgroundExpected);
|
||||
assert.equal(background._lpos.x, backgroundExpected._lpos.x);
|
||||
assert.equal(background._lpos.y, backgroundExpected._lpos.y);
|
||||
assert.equal(nodeComponent(generated, background, 'cc.Widget')._alignFlags, 45);
|
||||
assert.equal(nodeComponent(generated, background, 'cc.Widget')._originalWidth, 100);
|
||||
assert.equal(nodeComponent(generated, background, 'cc.Widget')._originalHeight, 100);
|
||||
assert.equal(nodeComponent(generated, background, 'cc.UITransform')._contentSize.width, 1600);
|
||||
assert.equal(nodeComponent(generated, background, 'cc.UITransform')._contentSize.height, 720);
|
||||
|
||||
const normal = generated.find(o => o.__type__ === 'cc.Node' && o._name === 'Spirit405');
|
||||
const normalExpected = expected.find(o => o.__type__ === 'cc.Node' && o._name === '渠道logo');
|
||||
assert.ok(normal);
|
||||
assert.ok(normalExpected);
|
||||
assert.equal(normal._lpos.x, normalExpected._lpos.x);
|
||||
assert.equal(normal._lpos.y, normalExpected._lpos.y);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.Widget')._alignFlags, 16);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.Widget')._horizontalCenter, 123.5);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.Widget')._verticalCenter, 0);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.Widget')._left, undefined);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.Widget')._top, undefined);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.UITransform')._contentSize.width, 59);
|
||||
assert.equal(nodeComponent(generated, normal, 'cc.UITransform')._contentSize.height, 148);
|
||||
});
|
||||
|
||||
test('PoC 阶段 3 - 渠道logo _lpos 误差 < 0.001 (bypass CLI to avoid sprite atlas dep)', () => {
|
||||
const layer = buildFilteredLayer();
|
||||
const json = emitPrefabJson(layer);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
||||
// Layer00010.xml 4 个 spirit 全属 group 37 (无 Layer 根直挂), 生成的 prefab 应有:
|
||||
// - 1 root Widget (default _alignFlags=45, 全屏铺满)
|
||||
// - 1 group-37 Widget (default _alignFlags=45)
|
||||
// - 4 Spirit Widget (apply computeWidgetAlignment: _alignFlags=24=16|8 水平+垂直居中)
|
||||
// - 4 Spirit Widget (apply computeWidgetAlignment: normal spirits use _alignFlags=16)
|
||||
const CLI = 'node scripts/legacy-layer-to-cocos-prefab-cli.mjs';
|
||||
const INPUT = '../projects/Game_Surface_3/save/Layer00010.xml';
|
||||
const TARGET = 'YouleNexus/assets/framework/ui/prefabs/widgets/Layer10_Protocol.prefab';
|
||||
@@ -32,7 +32,7 @@ test('Layer10_Protocol 生成 + 含 cc.Widget 节点 (computeWidgetAlignment 锚
|
||||
`应有 ≥6 个 cc.Widget 节点 (1 root + 1 group + 4 spirit), 实得 ${widgetNodes.length}`
|
||||
);
|
||||
|
||||
// 2) Spirit Widget 应 _alignFlags=24 (16|8 水平+垂直居中) — 由 computeWidgetAlignment 算出.
|
||||
// 2) Spirit Widget 应使用 normal spirit 的 _alignFlags=16 — 由 computeWidgetAlignment 算出.
|
||||
// 这是 PoC 阶段 2 的关键验证: 证明 computeWidgetAlignment 真实生效 (而非全部用默认 45).
|
||||
// Spirit Widget 与 root/group Widget 的区别: Widget.node.__id__ 指向的 cc.Node
|
||||
// _name 不为 'Protol_Layer' (root) 也不以 'group-' 开头 (group 父节点).
|
||||
@@ -48,7 +48,7 @@ test('Layer10_Protocol 生成 + 含 cc.Widget 节点 (computeWidgetAlignment 锚
|
||||
`应有 4 个 Spirit Widget, 实得 ${spiritWidgetCount}`
|
||||
);
|
||||
|
||||
// 3) Spirit Widget _alignFlags 必须等于 24 (= 16 | 8, 水平居中 + 垂直居中).
|
||||
// 3) Spirit Widget _alignFlags 必须等于 16 (normal spirit = horizontal center).
|
||||
// 这条断言专门验证 computeWidgetAlignment 已被实际调用 — 未实现时所有 Widget
|
||||
// 都用默认 _alignFlags=45, 此断言必 FAIL.
|
||||
const spiritWidgets = widgetNodes.filter(w => {
|
||||
@@ -60,9 +60,11 @@ test('Layer10_Protocol 生成 + 含 cc.Widget 节点 (computeWidgetAlignment 锚
|
||||
&& !targetNode._name.startsWith('group-');
|
||||
});
|
||||
for (const w of spiritWidgets) {
|
||||
const targetNode = json[w.node.__id__];
|
||||
const expectedFlags = targetNode._name === '协议遮罩' ? 45 : targetNode._name === '协议背景' ? 9 : 16;
|
||||
assert.equal(
|
||||
w._alignFlags, 24,
|
||||
`Spirit Widget 应 _alignFlags=24 (16|8), 实得 ${w._alignFlags}`
|
||||
w._alignFlags, expectedFlags,
|
||||
`Spirit Widget ${targetNode._name} 应 _alignFlags=${expectedFlags}, 实得 ${w._alignFlags}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,6 +27,30 @@ test('emitPrefabJson 含 PrefabInfo 块', () => {
|
||||
assert.equal(prefabInfo.asset.__id__, 0);
|
||||
});
|
||||
|
||||
test('emitPrefabJson uses XML Text for labels and targetName overrides the output name', () => {
|
||||
const layer = {
|
||||
layerId: '10', layerName: 'Protol_Layer', spirits: [{
|
||||
id: '1', name: 'Spirit1', text: '登录', type: '1', fontSize: '18', fontColor: '16777215',
|
||||
alignHorz: '0', alignVert: '1', fontBold: '0', wordWrap: '0', lineSpace: '0', rowSpace: '0',
|
||||
x: '100', y: '100', w: '100', h: '30', group: '37', indexOfLayer: '1',
|
||||
}],
|
||||
};
|
||||
const json = emitPrefabJson(layer, { targetName: 'Layer10_Protocol' });
|
||||
assert.equal(json[0]._name, 'Layer10_Protocol');
|
||||
const textNode = json.find(o => o.__type__ === 'cc.Node' && o._name === 'Spirit1');
|
||||
assert.ok(textNode);
|
||||
const label = json.filter(o => o.__type__ === 'cc.Label')[0];
|
||||
assert.equal(label._string, '登录');
|
||||
assert.equal(label._fontSize, 18);
|
||||
assert.equal(label._color.r, 255);
|
||||
assert.equal(label._color.g, 255);
|
||||
assert.equal(label._color.b, 255);
|
||||
assert.equal(label._horizontalAlign, 0);
|
||||
assert.equal(label._verticalAlign, 1);
|
||||
assert.equal(label._isBold, false);
|
||||
assert.equal(label._enableWrapText, false);
|
||||
});
|
||||
|
||||
test('emitPrefabJson 渠道logo _lpos 误差 < 0.001', () => {
|
||||
// 注: brief 原版用 spirit name='Spirit405' 但断言搜 /渠道/; 实际 fixture 里 id=405 的 spirit
|
||||
// 坐标 (734, 143, 59, 148) 严格对应 Login_Layer '渠道logo' 节点; 这里 name 用 '渠道logo'
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { mapSpiritTypeToComponent } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
import { ROOT } from '../../scripts/lib/paths.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',
|
||||
join(ROOT, 'framework-tests', 'legacy-layer-migration', 'fixtures', 'spirit-semantics.json'),
|
||||
'utf8'
|
||||
));
|
||||
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { ROOT } from '../../scripts/lib/paths.mjs';
|
||||
|
||||
const SEMANTICS = JSON.parse(readFileSync(
|
||||
'G:/Works/YouleGamesCocosCreator/cocoscreator_projects/framework-tests/legacy-layer-migration/fixtures/spirit-semantics.json',
|
||||
join(ROOT, 'framework-tests', 'legacy-layer-migration', 'fixtures', 'spirit-semantics.json'),
|
||||
'utf8'
|
||||
));
|
||||
|
||||
|
||||
+7
-1
@@ -1,6 +1,6 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolveSpriteFrameUuid } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
import { resolveSpriteFrameUuid, selectAtlasForLayerName } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
|
||||
// 数据源权威: gameabc_Image.json id=20 -> bmp="00020.png" 位于 atlas-login/,
|
||||
// 该 .meta 的 subMetas.f9941.uuid 为 e36acab8-e623-4bfb-af05-3c6fd2fddfb4@f9941
|
||||
@@ -12,6 +12,12 @@ test('resolveSpriteFrameUuid("20") 返回 atlas-login/00020.png 的 SpriteFrame
|
||||
assert.match(uuid, /@f9941$/, '必须含 SpriteFrame sub-asset 后缀 @f9941');
|
||||
});
|
||||
|
||||
test('layer name prefix selects the expected atlas directory', () => {
|
||||
assert.equal(selectAtlasForLayerName('Login_Layer'), 'atlas-login');
|
||||
assert.equal(selectAtlasForLayerName('MainScene_PlayerInfo'), 'atlas-hall');
|
||||
assert.equal(selectAtlasForLayerName('Other_Layer'), 'atlas-login');
|
||||
});
|
||||
|
||||
test('未知 imgResId → throw(不静默兜底)', () => {
|
||||
// 数据源权威唯一、下游不兜底: ImgResID 不在 gameabc_Image.json 时必须显式报错.
|
||||
assert.throws(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { join } from 'node:path';
|
||||
import { readLayerXml } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||
import { ROOT } from '../../scripts/lib/paths.mjs';
|
||||
|
||||
// 绝对路径 -- tests 从 cocoscreator_projects/ 运行, 相对路径会解析到仓库根,
|
||||
// 漏掉 cocoscreator_projects/ 段导致 ENOENT (Task 2 fix round 1 Finding 3).
|
||||
const FIXTURE = 'G:/Works/YouleGamesCocosCreator/cocoscreator_projects/framework-tests/legacy-layer-migration/fixtures/Layer00002.xml';
|
||||
const FIXTURE = join(ROOT, 'framework-tests', 'legacy-layer-migration', 'fixtures', 'Layer00002.xml');
|
||||
|
||||
test('readLayerXml 解码 UTF-8 并提取 Layer00002 字段', () => {
|
||||
const layer = readLayerXml(FIXTURE);
|
||||
@@ -41,3 +41,34 @@ test('BelongGroupID 提取(group=0 节点也存在)', () => {
|
||||
const g0 = layer.spirits.filter(s => s.group === '0');
|
||||
assert.ok(g0.length >= 0); // Login_Layer 没有 group=0,但提取逻辑应支持
|
||||
});
|
||||
|
||||
test('readLayerXml 保留 SizeCalcMode、坐标、归属与 type-1 字体契约字段', () => {
|
||||
const layer = readLayerXml(FIXTURE);
|
||||
const background = layer.spirits.find(s => s.id === '2');
|
||||
const text = layer.spirits.find(s => s.id === '285');
|
||||
assert.ok(background);
|
||||
assert.ok(text);
|
||||
|
||||
assert.equal(background.text, undefined);
|
||||
assert.equal(background.sizeCalcMode, '2');
|
||||
assert.equal(background.sizeParentID, '20');
|
||||
assert.equal(background.widthCalcMode, '1');
|
||||
assert.equal(background.heightCalcMode, '1');
|
||||
assert.equal(background.sizeRelaWidth, '10000');
|
||||
assert.equal(background.sizeRelaHeight, '10000');
|
||||
assert.equal(background.coordSelfPos, '0');
|
||||
assert.equal(background.coordRelaX, '0');
|
||||
assert.equal(background.coordRelaY, '0');
|
||||
assert.equal(background.coordParentID, '0');
|
||||
assert.equal(background.belongLayerID, '2');
|
||||
|
||||
assert.equal(text.text, '版本显示');
|
||||
assert.equal(text.fontSize, '18');
|
||||
assert.equal(text.fontColor, '16777215');
|
||||
assert.equal(text.alignHorz, '0');
|
||||
assert.equal(text.alignVert, '0');
|
||||
assert.equal(text.fontBold, '0');
|
||||
assert.equal(text.wordWrap, '0');
|
||||
assert.equal(text.lineSpace, '0');
|
||||
assert.equal(text.rowSpace, undefined);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user