Fix APT CLI roundtrip visual and script preservation

Preserve edittext channels, alpha/color transforms, inactive placement fields,
zero ratio/clipping and null ClipAction presence. Keep unlabelled flag bits,
quoted/escaped script strings, export declarations and correctly typed CONST
and pushdata values. Validate signed integers and write byte-aligned UTF-8.

Add 23 public CLI regressions and record the native 174-resource batch plus
bounded retests: four discovered pages are repaired, while the sound placeholder
and native float formatting remain explicit accuracy limits. Rebuild the 1.0.1
Windows executable and leave unrelated SDK changes untouched.
This commit is contained in:
2026-10-13 02:40:37 +02:00
parent 5511354783
commit e0b8dedc04
18 changed files with 5111 additions and 115 deletions
+99
View File
@@ -0,0 +1,99 @@
'use strict';
const test = require('node:test');
const H = require('./public-cli-helper.cjs');
const fixture = H.fs.readFileSync(H.path.join(__dirname, 'visual-state.raw.xml'), 'utf8');
test('public roundtrip retains an active null clip-action pointer without adding an array', () => {
const work = H.testCase('empty-clip-actions');
const raw = fixture.replace('flags="126"', 'flags="254"');
H.assert.notEqual(raw, fixture);
const source = H.expanded(work, raw);
const rebuilt = H.build(work, source.xml);
H.succeeded(rebuilt.result);
H.assert.equal(Number(H.element(H.frame(rebuilt.raw, 0), 'placeobject', 'depth', 1).flags), 254);
H.assert.doesNotMatch(source.xml, /<clipactions\b/);
H.assert.doesNotMatch(rebuilt.raw, /<clipactions\b/);
});
test('public expand/build applies a height edit while retaining text RGBA and other text fields', () => {
const work = H.testCase('text-height');
const source = H.expanded(work, fixture);
H.assert.deepEqual(['red', 'green', 'blue', 'alpha'].map(k => H.element(source.xml, 'edittext', 'id', 2)[k]),
['17', '83', '201', '137']);
const edited = source.xml.replace('height="18"', 'height="16"');
H.assert.notEqual(edited, source.xml, 'the requested edit must actually alter the XML');
const rebuilt = H.build(work, edited);
H.succeeded(rebuilt.result);
const text = H.element(rebuilt.raw, 'edittext', 'id', 2);
H.assert.equal(Number(text.height), 16);
for (const [key, expected] of Object.entries({ red: '17', green: '83', blue: '201', alpha: '137',
top: '-2', left: '-2', bottom: '26', right: '317', alignment: '0', readonly: '1', multiline: '0', wordwrap: '0' })) {
H.assert.equal(text[key], expected, `${key} is unrelated to the height edit`);
}
H.assert.equal(H.element(rebuilt.raw, 'movieclip', 'id', 0).unknown, '33');
H.assert.equal(H.element(rebuilt.raw, 'ettext', 'text', 'A & B "menu"').text, 'A & B "menu"');
});
test('public build honors deliberately edited text RGB in the same contract as expand', () => {
const work = H.testCase('text-color-edit');
const source = H.expanded(work, fixture);
const edited = source.xml.replace(/(<edittext\b[^>]*?)red="17" green="83" blue="201"/,
'$1red="7" green="19" blue="233"');
H.assert.notEqual(edited, source.xml);
const rebuilt = H.build(work, edited);
H.succeeded(rebuilt.result);
const text = H.element(rebuilt.raw, 'edittext', 'id', 2);
H.assert.deepEqual(['red', 'green', 'blue', 'alpha'].map(k => text[k]), ['7', '19', '233', '137']);
H.assert.equal(Number(text.height), 18);
});
test('public roundtrip retains transparent/dim color transforms and additive color values', () => {
const work = H.testCase('placement-color');
const source = H.expanded(work, fixture);
const rebuilt = H.build(work, source.xml);
H.succeeded(rebuilt.result);
for (const depth of [1, 2]) {
const before = H.element(H.frame(fixture, 0), 'placeobject', 'depth', depth);
const after = H.element(H.frame(rebuilt.raw, 0), 'placeobject', 'depth', depth);
H.assert.equal(Number(after.flags), Number(before.flags));
H.assert.ok(Number(after.flags) & 8, 'HasColorTransform is semantically active');
for (const key of ['red', 'green', 'blue', 'alpha', 'unknown']) {
H.assert.equal(after[key], before[key], `depth ${depth}: preserve ${key}`);
}
}
});
test('public roundtrip retains Move-only and active zero ratio/clipdepth without inventing matrix/color state', () => {
const work = H.testCase('move-flags');
const source = H.expanded(work, fixture);
const move = H.element(H.frame(source.xml, 1), 'placeobject', 'depth', 1);
H.assert.equal(move.flags, 'Move');
for (const key of ['matrix', 'rotm00', 'rotm01', 'rotm10', 'rotm11', 'tx', 'ty',
'rgbamul', 'rgbaadd', 'red', 'green', 'blue', 'alpha', 'unknown']) {
H.assert.equal(move[key], undefined, `inactive raw dump field ${key} must not become an authored edit`);
}
const rebuilt = H.build(work, source.xml);
H.succeeded(rebuilt.result);
H.assert.equal(Number(H.element(H.frame(rebuilt.raw, 1), 'placeobject', 'depth', 1).flags), 1);
const zero = H.element(H.frame(rebuilt.raw, 2), 'placeobject', 'depth', 1);
H.assert.equal(Number(zero.flags), 81);
H.assert.equal(Number(zero.ratio), 0);
H.assert.equal(Number(zero.clipdepth), 0);
});
test('public build applies one translation edit while retaining the nonidentity matrix, ratio and clipping contract', () => {
const work = H.testCase('matrix-edit');
const source = H.expanded(work, fixture);
const edited = source.xml.replace('tx="12.25"', 'tx="32.25"');
H.assert.notEqual(edited, source.xml);
const rebuilt = H.build(work, edited);
H.succeeded(rebuilt.result);
const placement = H.element(H.frame(rebuilt.raw, 0), 'placeobject', 'depth', 1);
H.assert.equal(Number(placement.tx), 32.25);
H.assert.equal(Number(placement.flags), 126);
H.assert.deepEqual(['rotm00', 'rotm01', 'rotm10', 'rotm11', 'ty'].map(k => Number(placement[k])),
[0.85, 0.125, -0.25, 1.25, -3.5]);
H.assert.equal(Number(placement.ratio), 0);
H.assert.equal(Number(placement.clipdepth), 8);
});