Files
AptXmlAI/ra3apt-cli/test/visual-state.test.cjs
T
lanyi e0b8dedc04 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.
2026-10-13 02:40:37 +02:00

100 lines
5.2 KiB
JavaScript

'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);
});