import assert from 'node:assert/strict'; import {createRequire} from 'node:module'; import {test} from 'node:test'; import {tsImport} from 'tsx/esm/api'; const {parse_inventory_item: parse} = await tsImport('../utils/inventory_item.ts', import.meta.url); const require = createRequire(import.meta.url); const mf = createRequire(require.resolve('@wp2508/mineflayer')); const registry = mf('prismarine-registry')('1.21.4'); const Item = mf('prismarine-item')(registry); const component = (type, data) => ({type, data}); const tag = (type, value) => ({type, value}); const plain = item => parse(item, 36, registry); test('modern custom names and lore become rich logs with explicit false and no executable events', () => { const item = new Item(registry.itemsByName.clock.id, 8); item.components = [component('custom_name', tag('compound', { text: tag('string', ''), bold: tag('byte', 1), italic: tag('byte', 1), extra: tag('list', {type: 'compound', value: [{ text: tag('string', '菜单'), color: tag('string', 'gold'), bold: tag('byte', 0), italic: tag('byte', 0), underlined: tag('byte', 0), clickEvent: tag('compound', {action: tag('string', 'run_command'), value: tag('string', '/op me')}), }]}), })), component('lore', [tag('compound', {text: tag('string', '右键打开'), color: tag('string', 'gray')}), tag('string', '')])]; const result = plain(item); assert.equal(result.count, 8); assert.deepEqual(result.i18n, [{lang: 'zh-CN', name: '时钟', description: ''}]); assert.equal(result.displayName[0].message, '菜单'); assert.equal(result.displayName[0].font_color, '#FFAA00'); assert.deepEqual(result.displayName[0].font_style, ['normal']); assert.equal(result.displayName[0].clickEvent, undefined); assert.equal(result.lore[0][0].message, '右键打开'); assert.deepEqual(result.lore[1], []); assert.equal('components' in result, false); assert.equal('nbt' in result, false); assert.equal(result.truncated, false); }); test('legacy NBT resolves names enchants damage and remaining unknown tags', () => { const item = { type: registry.itemsByName.diamond_sword.id, name: 'diamond_sword', count: 1, displayName: 'Diamond Sword', nbt: tag('compound', { display: tag('compound', {Name: tag('string', '{"text":"剑","color":"aqua"}'), Lore: tag('list', {type: 'string', value: ['{"text":"描述"}']})}), Enchantments: tag('list', {type: 'compound', value: [{id: tag('string', 'minecraft:sharpness'), lvl: tag('short', 5)}]}), Damage: tag('int', 20), Unbreakable: tag('byte', 1), HideFlags: tag('int', 1), plugin: tag('compound', {menu: tag('string', 'main'), timestamp: tag('long', 123n)}), }), }; const result = plain(item); assert.equal(result.displayName[0].message, '剑'); assert.equal(result.lore[0][0].message, '描述'); assert.deepEqual(result.enchants, [{name: 'sharpness', i18n: [{lang: 'zh-CN', name: '锋利', description: ''}], level: 5, hidden: true}]); assert.equal(result.durability.used, 20); assert.equal(result.durability.remaining, result.durability.max - 20); assert.equal(result.unbreakable, true); assert.deepEqual(result.details.extra.legacy.plugin, {menu: 'main', timestamp: '123'}); assert.equal(result.details.extra.legacy.Enchantments, undefined); }); test('modern enchantments potions attributes and flags have structured values', () => { const item = new Item(registry.itemsByName.diamond_sword.id, 1); const id = registry.enchantmentsByName.sharpness.id; item.components = [ component('item_name', {text: '物品名称'}), component('enchantments', {enchantments: [{id, level: 4}], showTooltip: false}), component('max_damage', 100), component('damage', 12), component('unbreakable', {}), component('enchantment_glint_override', false), component('potion_contents', {potionId: 3, customColor: 255, customEffects: [{id: 0, amplifier: 1, duration: 200}]}), component('attribute_modifiers', {showTooltip: false, attributes: [{typeId: 1, name: 'bonus', value: 3, operation: 'add', slot: 'main_hand'}]}), ]; const result = plain(item); assert.equal(result.displayName[0].message, '物品名称'); assert.deepEqual(result.enchants, [{id, name: 'sharpness', i18n: [{lang: 'zh-CN', name: '锋利', description: ''}], level: 4, hidden: true}]); assert.deepEqual(result.durability, {used: 12, max: 100, remaining: 88}); assert.equal(result.glint, false); assert.equal(result.details.potion.effects[0].amplifier, 1); assert.equal(result.details.potion.effects[0].duration, 200); assert.deepEqual(result.details.attributes.modifiers[0], {type: 1, name: 'bonus', amount: 3, operation: 'add', slot: 'main_hand', hidden: false, displayName: []}); }); test('nested modern containers and legacy bundles use parsed items rather than protocol slots', () => { const item = new Item(registry.itemsByName.shulker_box.id, 1); item.components = [component('container', {contents: [{itemCount: 4, itemId: registry.itemsByName.clock.id, components: [component('custom_name', {text: '嵌套时钟'})]}]})]; const result = plain(item); assert.equal(result.details.container[0].slot, 0); assert.equal(result.details.container[0].count, 4); assert.equal(result.details.container[0].displayName[0].message, '嵌套时钟'); const legacy = {type: registry.itemsByName.bundle.id, name: 'bundle', count: 1, nbt: tag('compound', {Items: tag('list', {type: 'compound', value: [{id: tag('string', 'minecraft:stone'), Count: tag('byte', 3)}]})})}; assert.equal(plain(legacy).details.bundle[0].name, 'stone'); assert.equal(plain(legacy).details.bundle[0].count, 3); }); test('written and writable books retain different page semantics and truncation is explicit', () => { const written = new Item(registry.itemsByName.written_book.id, 1); written.components = [component('written_book_content', {rawTitle: 'Book', author: 'Author', pages: [{content: tag('compound', {text: tag('string', 'Page')})}]})]; assert.equal(plain(written).book.pages[0][0].message, 'Page'); const writable = new Item(registry.itemsByName.writable_book.id, 1); writable.components = [component('writable_book_content', {pages: [{content: '{"text":"literal"}'}]})]; assert.equal(plain(writable).book.pages[0][0].message, '{"text":"literal"}'); written.components.push(component('custom_data', {long: 'x'.repeat(100_000)})); assert.equal(plain(written).truncated, true); assert.doesNotThrow(() => JSON.stringify(plain(written))); const cycle = {}; cycle.self = cycle; written.components = [component('custom_data', cycle)]; assert.equal(plain(written).details.extra.custom_data.self, null); assert.equal(plain(written).truncated, true); }); test('legacy section colors and current tooltip component IDs are resolved', () => { const legacy = {type: 1, count: 1, name: 'stone', displayName: 'Stone', nbt: tag('compound', {display: tag('compound', {Name: tag('string', '§6§l菜单§r 普通')})})}; const result = plain(legacy); assert.equal(result.displayName[0].message, '菜单'); assert.equal(result.displayName[0].font_color, '#FFAA00'); assert.deepEqual(result.displayName[0].font_style, ['bold']); const latest = mf('prismarine-registry')('26.2'); const mappings = latest.protocol.types.SlotComponentType[1].mappings; const enchantComponentId = Number(Object.keys(mappings).find(id => mappings[id] === 'enchantments')); const item = {type: latest.itemsByName.diamond_sword.id, count: 1, components: [ component('enchantments', {enchantments: [{id: 0, level: 1}]}), component('tooltip_display', {hideTooltip: false, hiddenComponents: [enchantComponentId]}), component('attribute_modifiers', [{typeId: 0, name: 'test', value: 2, operation: 'add', slot: 'main_hand', display: {type: 'hidden'}}]), ]}; const parsed = parse(item, 0, latest); assert.equal(parsed.enchants[0].hidden, true); assert.equal(parsed.details.attributes.modifiers[0].amount, 2); assert.equal(parsed.details.attributes.modifiers[0].hidden, true); assert.deepEqual(parsed.details.tooltip.hiddenComponents, ['enchantments']); }); test('item Chinese names use block keys then item keys and fall back to the identifier', () => { assert.equal(plain({type: 1, name: 'stone', count: 1}).i18n[0].name, '石头'); assert.equal(plain({type: 1, name: 'diamond_sword', count: 1}).i18n[0].name, '钻石剑'); assert.equal(plain({type: 1, name: 'plugin_unknown_item', count: 1}).i18n[0].name, 'plugin_unknown_item'); const nested = plain({type: registry.itemsByName.bundle.id, name: 'bundle', count: 1, components: [component('bundle_contents', {contents: [{itemId: 1, itemCount: 2}]})]}); assert.equal(nested.details.bundle[0].i18n[0].name, '石头'); }); test('stored and unknown enchantments include translated names or identifier fallbacks', () => { const parsed = plain({type: registry.itemsByName.enchanted_book.id, count: 1, components: [component('stored_enchantments', { enchantments: [{id: 'minecraft:mending', level: 1}, {id: 'plugin_custom', level: 2}, {id: 999999, level: 3}], })]}); assert.equal(parsed.enchants[0].i18n[0].name, '经验修补'); assert.equal(parsed.enchants[1].i18n[0].name, 'plugin_custom'); assert.equal(parsed.enchants[2].i18n[0].name, '999999'); });