| 1 | import assert from 'node:assert/strict'; |
| 2 | import { test } from 'node:test'; |
| 3 | import { enabledQuickReactions } from '../src/lib/pack-preferences-model.ts'; |
| 4 | import { |
| 5 | isSeparator, |
| 6 | normaliseQuick, |
| 7 | parseQuickReactions, |
| 8 | quickColumns, |
| 9 | quickGroups, |
| 10 | sameQuick |
| 11 | } from '../src/lib/quick-reactions-model.ts'; |
| 12 | import type { QuickEntry } from '../src/lib/types.ts'; |
| 13 | |
| 14 | const r = (key: string): QuickEntry => ({ key, shortcode: null }); |
| 15 | const sep = (): QuickEntry => ({ separator: true }); |
| 16 | const shape = (list: QuickEntry[]) => list.map((e) => (isSeparator(e) ? '|' : e.key)).join(' '); |
| 17 | |
| 18 | test('normalising drops leading, trailing and doubled separators', () => { |
| 19 | assert.equal(shape(normaliseQuick([sep(), r('a'), sep(), sep(), r('b'), sep()])), 'a | b'); |
| 20 | assert.equal(shape(normaliseQuick([sep(), sep()])), ''); |
| 21 | assert.equal(shape(normaliseQuick([])), ''); |
| 22 | }); |
| 23 | |
| 24 | test('normalising dedupes keys and keeps the break between survivors', () => { |
| 25 | assert.equal(shape(normaliseQuick([r('a'), sep(), r('a'), sep(), r('b')])), 'a | b'); |
| 26 | assert.equal(shape(normaliseQuick([r('a'), r('b'), sep(), r('a'), sep(), r('c')])), 'a b | c'); |
| 27 | }); |
| 28 | |
| 29 | test('normalising keeps separator identity and leaves the input alone', () => { |
| 30 | const middle = sep(); |
| 31 | const input = [r('a'), middle, r('b'), sep()]; |
| 32 | const out = normaliseQuick(input); |
| 33 | assert.equal(out[1], middle); |
| 34 | assert.equal(input.length, 4); |
| 35 | }); |
| 36 | |
| 37 | test('parsing keeps separators and tolerates what older builds wrote', () => { |
| 38 | const parsed = parseQuickReactions({ |
| 39 | reactions: [{ key: 'a', shortcode: 'x' }, { separator: true }, { key: 'b' }, 7, null, { key: '' }, { key: 'a' }, { separator: true }] |
| 40 | }); |
| 41 | assert.deepEqual(parsed, [{ key: 'a', shortcode: 'x' }, { separator: true }, { key: 'b', shortcode: null }]); |
| 42 | }); |
| 43 | |
| 44 | test('parsing rejects data without a reactions list', () => { |
| 45 | assert.equal(parseQuickReactions(null), null); |
| 46 | assert.equal(parseQuickReactions({ reactions: 'a' }), null); |
| 47 | assert.equal(parseQuickReactions('a'), null); |
| 48 | assert.deepEqual(parseQuickReactions({ reactions: [] }), []); |
| 49 | }); |
| 50 | |
| 51 | test('only a literal true makes a separator', () => { |
| 52 | assert.deepEqual(parseQuickReactions({ reactions: [{ separator: 'yes' }, { separator: false, key: 'a' }] }), [{ key: 'a', shortcode: null }]); |
| 53 | }); |
| 54 | |
| 55 | test('groups split at separators and skip empty runs', () => { |
| 56 | const groups = quickGroups([r('a'), r('b'), sep(), sep(), r('c'), sep()]); |
| 57 | assert.deepEqual(groups.map((g) => g.map((e) => e.key)), [['a', 'b'], ['c']]); |
| 58 | assert.deepEqual(quickGroups([]), []); |
| 59 | assert.deepEqual(quickGroups([sep()]), []); |
| 60 | }); |
| 61 | |
| 62 | test('the toolbar is as wide as its widest group up to the row limit', () => { |
| 63 | const groups = quickGroups([r('a'), r('b'), r('c'), sep(), r('d')]); |
| 64 | assert.equal(quickColumns(groups, 8), 3); |
| 65 | assert.equal(quickColumns(groups, 2), 2); |
| 66 | assert.equal(quickColumns([], 8), 0); |
| 67 | }); |
| 68 | |
| 69 | test('comparison looks at order, keys and separator positions', () => { |
| 70 | assert.ok(sameQuick([r('a'), sep(), r('b')], [r('a'), sep(), r('b')])); |
| 71 | assert.ok(!sameQuick([r('a'), sep(), r('b')], [r('a'), r('b')])); |
| 72 | assert.ok(!sameQuick([r('a'), r('b')], [r('b'), r('a')])); |
| 73 | assert.ok(!sameQuick([sep()], [r('a')])); |
| 74 | }); |
| 75 | |
| 76 | test('a hidden pack image leaves no empty group or doubled break', () => { |
| 77 | const pack = { |
| 78 | id: 'p', |
| 79 | display_name: null, |
| 80 | avatar: null, |
| 81 | attribution: 'source', |
| 82 | source: 'User' as const, |
| 83 | images: [{ shortcode: 'one', media_id: 'mxc://x/one', reaction_key: 'mxc://x/one', body: null, mimetype: null, width: null, height: null, usage: ['Emoticon' as const] }], |
| 84 | editable: true, |
| 85 | enabled_everywhere: false |
| 86 | }; |
| 87 | const list = [r('a'), sep(), r('mxc://x/one'), sep(), r('b')]; |
| 88 | const visible = normaliseQuick(enabledQuickReactions(list, [pack], { p: ['one'] })); |
| 89 | assert.equal(shape(visible), 'a | b'); |
| 90 | assert.equal(shape(normaliseQuick(enabledQuickReactions([r('mxc://x/one'), sep(), r('a')], [pack], { p: ['one'] }))), 'a'); |
| 91 | }); |