| 1 | import assert from 'node:assert/strict'; |
| 2 | import { test } from 'node:test'; |
| 3 | import { clonePackDestinations, cloneRequest, editableChoices, findSticker, freePackStateKey, packsForRoom, packSlug, reactionShortcode, shortcodeFor, stickersOf } from '../src/lib/packs.ts'; |
| 4 | |
| 5 | const image = (shortcode: string, usage: ('Emoticon' | 'Sticker')[]) => ({ |
| 6 | shortcode, |
| 7 | media_id: `mxc://x/${shortcode}`, |
| 8 | reaction_key: `mxc://x/${shortcode}`, |
| 9 | body: null, |
| 10 | mimetype: null, |
| 11 | width: null, |
| 12 | height: null, |
| 13 | usage |
| 14 | }); |
| 15 | |
| 16 | const roomPack = (state_key: string, editable: boolean) => ({ |
| 17 | id: `room:!r:x:${state_key}`, |
| 18 | display_name: state_key, |
| 19 | avatar: null, |
| 20 | attribution: null, |
| 21 | source: { Room: { room_id: '!r:x', state_key } }, |
| 22 | images: [image('a', ['Sticker']), image('b', ['Emoticon']), image('c', [])], |
| 23 | editable, |
| 24 | enabled_everywhere: false |
| 25 | }); |
| 26 | |
| 27 | test('shortcodes come from file names and never clash', () => { |
| 28 | const taken = new Set(['cat']); |
| 29 | assert.equal(shortcodeFor('Cat.PNG', taken), 'cat_2'); |
| 30 | assert.equal(shortcodeFor('my cool sticker!.webp', taken), 'my_cool_sticker'); |
| 31 | assert.equal(shortcodeFor('.png', taken), 'image'); |
| 32 | assert.equal(packSlug('Cats & Dogs'), 'cats-dogs'); |
| 33 | }); |
| 34 | |
| 35 | test('stickers include images with no usage of their own', () => { |
| 36 | assert.deepEqual(stickersOf(roomPack('s', true)).map((i) => i.shortcode), ['a', 'c']); |
| 37 | }); |
| 38 | |
| 39 | test('custom reaction nicknames fall back to known packs without changing the reaction key', () => { |
| 40 | const packs = [roomPack('mine', true)]; |
| 41 | assert.equal(reactionShortcode('mxc://x/a', undefined, packs), 'a'); |
| 42 | assert.equal(reactionShortcode('mxc://x/a', ' preferred ', packs), 'preferred'); |
| 43 | assert.equal(reactionShortcode('mxc://x/unknown', undefined, packs), undefined); |
| 44 | assert.equal(reactionShortcode('👍', 'thumbsup', packs), undefined); |
| 45 | }); |
| 46 | |
| 47 | test('sticker commands resolve names without matching emoticons or choosing ambiguous stickers', () => { |
| 48 | const pack = roomPack('s', true); |
| 49 | assert.equal(findSticker([pack], 'A').sticker.shortcode, 'a'); |
| 50 | assert.equal(findSticker([pack], 'c').pack, pack); |
| 51 | assert.throws(() => findSticker([pack], 'b'), /No sticker named b/); |
| 52 | assert.throws(() => findSticker([pack], 'missing'), /No sticker named missing/); |
| 53 | assert.throws(() => findSticker([pack, roomPack('other', false)], 'a'), /More than one sticker/); |
| 54 | }); |
| 55 | |
| 56 | test('your pack is always a destination, then editable room packs only', () => { |
| 57 | const packs = { global: [], rooms: { '!r:x': [roomPack('mine', true), roomPack('theirs', false)] } }; |
| 58 | const choices = editableChoices(packs, () => 'Room'); |
| 59 | assert.deepEqual(choices.map((c) => c.target), [{ kind: 'user' }, { kind: 'room', room_id: '!r:x', state_key: 'mine' }]); |
| 60 | const dests = clonePackDestinations(roomPack('Fancy Pack', false), [{ room_id: '!d:x', name: 'Dest' }], () => 'Room'); |
| 61 | assert.deepEqual(dests[1].target, { kind: 'room', room_id: '!d:x', state_key: 'fancy-pack' }); |
| 62 | }); |
| 63 | |
| 64 | test('new pack names avoid existing keys without changing merge destinations', () => { |
| 65 | const packs = { global: [], rooms: { '!r:x': [roomPack('cats', true), roomPack('cats-2', true)] } }; |
| 66 | assert.equal(freePackStateKey(packs, '!r:x', 'Cats'), 'cats-3'); |
| 67 | assert.equal(freePackStateKey(packs, '!other:x', 'Cats'), 'cats'); |
| 68 | const destination = editableChoices(packs, () => 'Room')[1]; |
| 69 | const request = cloneRequest({ kind: 'pack', pack: roomPack('source', false) }, destination.target, ['Sticker', 'Emoticon'], null, destination.pack); |
| 70 | assert.deepEqual(request, { op: 'clone_pack', from_pack: 'room:!r:x:source', to: destination.target, display_name: null, usage: ['Sticker', 'Emoticon'] }); |
| 71 | }); |
| 72 | |
| 73 | test('image and event clones keep explicit usage and event names avoid collisions', () => { |
| 74 | const pack = roomPack('source', false); |
| 75 | const to = { kind: 'user' as const }; |
| 76 | assert.deepEqual(cloneRequest({ kind: 'image', pack, image: pack.images[1] }, to, ['Sticker'], null, null), { |
| 77 | op: 'clone_pack_image', from_pack: pack.id, shortcode: 'b', to, usage: ['Sticker'] |
| 78 | }); |
| 79 | const source = { kind: 'sticker' as const, room_id: '!r:x', event_id: '$event', body: 'a' }; |
| 80 | assert.deepEqual(cloneRequest(source, to, ['Sticker', 'Emoticon'], null, pack), { |
| 81 | op: 'clone_sticker', room_id: '!r:x', event_id: '$event', shortcode: 'a_2', to, usage: ['Sticker', 'Emoticon'] |
| 82 | }); |
| 83 | assert.throws(() => cloneRequest(source, to, [], null, pack), /at least one use/); |
| 84 | }); |
| 85 | |
| 86 | test('channel packs include all ancestor spaces once and do not cross to unrelated spaces', () => { |
| 87 | const local = roomPack('local', false); |
| 88 | const parent = { ...roomPack('parent', false), id: 'space-pack' }; |
| 89 | const ancestor = { ...roomPack('ancestor', false), id: 'ancestor-pack' }; |
| 90 | const unrelated = { ...roomPack('unrelated', false), id: 'unrelated-pack' }; |
| 91 | const global = { ...roomPack('global', false), id: 'global-pack' }; |
| 92 | const packs = { global: [global, parent], rooms: { '!r:x': [local], '!s:x': [parent], '!a:x': [ancestor], '!u:x': [unrelated] } }; |
| 93 | const rooms = [ |
| 94 | { room_id: '!r:x', parent_spaces: ['!s:x'], children: [], space: false }, |
| 95 | { room_id: '!s:x', parent_spaces: ['!a:x'], children: ['!r:x'], space: true }, |
| 96 | { room_id: '!a:x', parent_spaces: ['!s:x'], children: ['!s:x'], space: true }, |
| 97 | { room_id: '!u:x', parent_spaces: [], children: [], space: true } |
| 98 | ]; |
| 99 | assert.deepEqual(packsForRoom(packs, '!r:x', rooms).map((pack) => pack.id), [local.id, parent.id, ancestor.id, global.id]); |
| 100 | assert.deepEqual(packsForRoom(packs, '!r:x', [{ ...rooms[0], parent_spaces: [] }, ...rooms.slice(1)]).map((pack) => pack.id), [local.id, parent.id, ancestor.id, global.id]); |
| 101 | }); |