import assert from 'node:assert/strict'; import {fileURLToPath} from 'node:url'; import test from 'node:test'; import {build} from 'esbuild'; const compiled = await build({ entryPoints: [fileURLToPath(new URL('../utils/message_json_parser.ts', import.meta.url))], bundle: true, platform: 'node', format: 'esm', write: false, }); const {MinecraftJsonParser: parser} = await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString('base64')}`); test('log fragments inherit styles and respect explicit false / zero overrides', () => { const input = {text: 'parent', color: 'red', bold: 1, extra: [ {text: 'child', italic: true}, {text: 'plain', bold: 0}, {text: 'tail', bold: false}, ]}; assert.deepEqual(parser.toLog(input), [ {message: 'parent', font_color: '#FF5555', font_style: ['bold']}, {message: 'child', font_color: '#FF5555', font_style: ['bold', 'italic']}, {message: 'plaintail', font_color: '#FF5555', font_style: ['normal']}, ]); assert.equal(input.extra[1].bold, 0); }); test('translations keep parameter styles, trailing extras and chat message contents', () => { const parsed = parser.parse({translate: 'chat.type.text', color: 'gold', with: [ {text: 'Alex', color: 'green'}, 'hello » world', ], extra: ['!']}); assert.equal(parsed.plainText, ' hello » world!'); assert.equal(parsed.username, 'Alex'); assert.equal(parsed.message, 'hello » world!'); assert.equal(parsed.log[1].font_color, '#55FF55'); assert.equal(parsed.log.at(-1).font_color, '#FFAA00'); assert.equal('html' in parsed, false); assert.equal(parser.toHTML, undefined); }); test('fallback translations support indexed parameters, literal percent and missing arguments', () => { assert.equal(parser.extractPlainText({translate: 'missing.key', fallback: '%2$s %% %s %3$s', with: [0, false]}), 'false % 0 %3$s'); }); test('ChatMessage uses lossless raw JSON, including arrays and inherited URL style', () => { const raw = [{text: '链接', color: '#123456', click_event: {action: 'open_url', url: 'https://example.com'}}, '😀', {text: '<&>', underlined: 1}]; const parsed = parser.parse({text: 'lossy', json: raw, warn() {}}); assert.equal(parsed.plainText, '链接😀<&>'); assert.deepEqual(parsed.log, [ {message: '链接😀', font_color: '#123456', font_style: ['url'], clickEvent: {action: 'open_url', value: 'https://example.com'}}, {message: '<&>', font_color: '#123456', font_style: ['underline', 'url'], clickEvent: {action: 'open_url', value: 'https://example.com'}}, ]); }); test('empty, primitive and cyclic inputs are bounded and preserve valid text', () => { assert.deepEqual(parser.toLog(null), []); assert.equal(parser.extractPlainText([0, false, '']), '0false'); const cyclic = {text: 'x'}; cyclic.extra = [cyclic]; const text = parser.extractPlainText(cyclic); assert.ok(text.length > 0 && text.length <= 65); assert.deepEqual(parser.parseUsernameAndMessage('[rank] Alex » hello » world'), {username: 'Alex', message: 'hello » world'}); }); test('logs retain inherited hover, click and insertion without merging different events', () => { const result = parser.toLog({text: '', hover_event: {action: 'show_text', contents: { translate: 'chat.type.text', with: ['Alex', 'tooltip'], }}, click_event: {action: 'suggest_command', command: '/msg Alex '}, insertion: 'Alex', extra: [ {text: 'A'}, {text: 'B', hoverEvent: {action: 'show_item', contents: {id: 'minecraft:stone', count: 1}}}, {text: 'C', click_event: {action: 'copy_to_clipboard', text: 'copied'}}, ]}); assert.equal(result.length, 3); assert.deepEqual(result[0].hoverEvent, {action: 'show_text', value: ' tooltip', segments: [{message: ' tooltip', font_color: '#FFFFFF', font_style: ['normal']}]}); assert.deepEqual(result[0].clickEvent, {action: 'suggest_command', value: '/msg Alex '}); assert.equal(result[0].insertion, 'Alex'); assert.deepEqual(JSON.parse(result[1].hoverEvent.value), {id: 'minecraft:stone', count: 1}); assert.deepEqual(result[2].clickEvent, {action: 'copy_to_clipboard', value: 'copied'}); }); test('NBT disconnect reasons unwrap compound, list and string tags', () => { const reason = {type: 'compound', value: { text: {type: 'string', value: '登录失败: '}, color: {type: 'string', value: 'red'}, extra: {type: 'list', value: {type: 'compound', value: [ {text: {type: 'string', value: '连接超时'}, bold: {type: 'byte', value: 1}}, ]}}, }}; assert.equal(parser.extractPlainText(reason), '登录失败: 连接超时'); assert.equal(parser.toLog(reason)[1].font_color, '#FF5555'); assert.deepEqual(parser.toLog(reason)[1].font_style, ['bold']); assert.equal(parser.extractPlainText({type: 'string', value: '服务器已关闭'}), '服务器已关闭'); }); test('hover text preserves inherited colors and formats without recursive events', () => { const result = parser.toLog({text: 'player', hoverEvent: {action: 'show_text', contents: { text: '称号 ', color: 'gold', bold: true, extra: [ {text: 'Alex', color: 'aqua', bold: false, italic: true, hoverEvent: {action: 'show_text', value: 'nested'}, clickEvent: {action: 'run_command', value: '/nested'}}, ], }}}); assert.equal(result[0].hoverEvent.value, '称号 Alex'); assert.deepEqual(result[0].hoverEvent.segments, [ {message: '称号 ', font_color: '#FFAA00', font_style: ['bold']}, {message: 'Alex', font_color: '#55FFFF', font_style: ['italic']}, ]); const long = parser.toLog({text: 'player', hoverEvent: {action: 'show_text', contents: {text: 'x'.repeat(5000)}}}); assert.equal(long[0].hoverEvent.segments[0].message.length, 4096); });