'use strict'; // These cover issues independently reproduced by the real-tool audit. const test = require('node:test'); const H = require('./public-cli-helper.cjs'); function rawAction(nodes, exports = '', extra = '') { return ` ${exports} ${nodes} ${extra} `; } test('public build identifies a sound character payload not exported by cc3tools', () => { const work = H.testCase('unsupported-sound'); const source = H.expanded(work, rawAction('', '', '')); const rebuilt = H.build(work, source.xml); H.assert.notEqual(rebuilt.result.status, 0); H.assert.match(rebuilt.result.stderr, /cc3tools did not export the sound character payload \(code 6\)/); }); test('public roundtrip retains an unlabelled button-action flag bit', () => { const work = H.testCase('opaque-button-flag'); const button = ''; const source = H.expanded(work, rawAction('', '', button)); H.assert.equal(H.element(source.xml, 'buttonaction', 'keycode', 0).flags, '0x40'); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.equal(Number(H.element(rebuilt.raw, 'buttonaction', 'keycode', 0).flags), 64); }); test('public action-string roundtrip retains quotes, comma, backslash and &Outline literally', () => { const work = H.testCase('quoted-action'); const literal = 'say "Q", &Outline \\literal'; const source = H.expanded(work, rawAction('')); H.assert.match(source.xml, / { const work = H.testCase('entity-literal'); const source = H.expanded(work, rawAction('')); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.equal(H.element(rebuilt.raw, 'string', 'action', 161).str, '" & &Outline'); }); test('public action-string roundtrip preserves a literal CDATA closing delimiter', () => { const work = H.testCase('cdata-delimiter'); const source = H.expanded(work, rawAction('')); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.equal(H.element(rebuilt.raw, 'string', 'action', 161).str, ']]>'); }); test('public action-string roundtrip retains actual LF and CR rather than replacing them with slash letters', () => { const work = H.testCase('newlines'); const source = H.expanded(work, rawAction('')); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.equal(H.element(rebuilt.raw, 'string', 'action', 161).str, 'before\nafter\rlast'); }); test('public geturl roundtrip does not split a comma inside an escaped quoted argument', () => { const work = H.testCase('geturl-comma'); const source = H.expanded(work, rawAction('')); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); const url = H.element(rebuilt.raw, 'geturl', 'action', 131); H.assert.equal(url.str1, 'x"y,z?a&b'); H.assert.equal(url.str2, 'target"room'); }); test('public constant pooling accepts constructor/__proto__/toString as ordinary string values', () => { const work = H.testCase('constant-object-keys'); const source = H.expanded(work, rawAction(` `)); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.deepEqual(H.constRecords(rebuilt.output).map(record => record.value).sort(), ['__proto__', 'constructor', 'toString']); for (const value of ['constructor', '__proto__', 'toString']) { H.assert.equal(H.element(rebuilt.raw, 'constant', 'str', value).str, value); } }); test('public build writes Chinese string constants with UTF-8 byte lengths and distinct aligned pointers', () => { const work = H.testCase('utf8-pool'); const source = H.expanded(work, rawAction(` `)); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); const records = H.constRecords(rebuilt.output); H.assert.deepEqual(records.map(record => record.value).sort(), ['ASCII&Outline', '网络标题']); for (const record of records) H.assert.equal(record.offset % 4, 0); const byOffset = records.slice().sort((a, b) => a.offset - b.offset); H.assert.ok(byOffset[1].offset >= byOffset[0].offset + Buffer.byteLength(byOffset[0].value, 'utf8') + 1); }); test('public roundtrip writes the actual type-7 -1 sentinel as 0xffffffff without RangeError', () => { const work = H.testCase('signed-integer-pool'); const source = H.expanded(work, rawAction(` `)); H.assert.match(source.xml, /pushconst -1/); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); H.assert.deepEqual(H.constRecords(rebuilt.output), [{ type: 7, bits: 0xffffffff, value: -1 }]); H.assert.equal(H.element(rebuilt.raw, 'constant', 'num', '-1').num, '-1'); }); test('public build explicitly rejects fractional constant-pool values instead of silently truncating', () => { const work = H.testCase('fractional-pool'); const human = ` `; const rebuilt = H.build(work, human); H.assert.notEqual(rebuilt.result.status, 0); H.assert.match(rebuilt.result.stderr, /constant|non.?integer|fraction|unsupported|numeric/i); }); test('public build rejects an integer that would wrap into a different signed type-7 value', () => { const work = H.testCase('integer-pool-overflow'); const human = ` `; const rebuilt = H.build(work, human); H.assert.notEqual(rebuilt.result.status, 0); H.assert.match(rebuilt.result.stderr, /signed 32-bit integers/); H.assert.equal(H.fs.existsSync(rebuilt.output), false); }); test('public texture-manifest roundtrip decodes and encodes an ampersand path once', () => { const work = H.testCase('texture-entity'); const input = H.writeRaw(work, rawAction('', '', '')); H.fs.writeFileSync(input.replace(/\.apt$/, '.xml'), ''); H.fs.writeFileSync(input.replace(/\.apt$/, '.dat'), '1->1\n'); const before = H.path.join(work.directory, 'image_edit.xml'); H.succeeded(H.cli(work, 'expand', input, before)); const human = H.fs.readFileSync(before, 'utf8'); H.assert.equal(H.element(human, 'image', 'id', 1).image, 'art/A&B.tga'); const rebuilt = H.build(work, human); H.succeeded(rebuilt.result); const after = H.path.join(work.directory, 'after_edit.xml'); H.succeeded(H.cli(work, 'expand', rebuilt.output, after)); H.assert.equal(H.element(H.fs.readFileSync(after, 'utf8'), 'image', 'id', 1).image, 'art/A&B.tga'); }); test('public expand/build preserves all original export names and repeated declarations in order', () => { const work = H.testCase('export-object-keys'); const exports = '' + ''; const source = H.expanded(work, rawAction('', exports, '')); const rebuilt = H.build(work, source.xml); H.succeeded(rebuilt.result); for (const xml of [source.xml, rebuilt.raw]) { H.assert.deepEqual(Array.from(xml.matchAll(/]*>/g), match => H.attributes(match[0]).name), ['constructor', 'toString', 'repeated', 'repeated']); } });