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.
100 lines
5.2 KiB
JavaScript
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);
|
|
});
|