import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { GroupCandidate, findMediaRun, planMediaGroups, splitRun } from './mediaGroups'; const media = ( sender: string, ts: number, extra: Partial = {}, ): GroupCandidate => ({ sender, ts, kind: 'media', hasRelation: false, redacted: false, mustEnd: false, ...extra, }); const text = (sender: string, ts: number): GroupCandidate => ({ ...media(sender, ts), kind: 'other', }); const skip = (ts: number): GroupCandidate => ({ ...media('x', ts), kind: 'skip' }); const seq = (items: GroupCandidate[]) => (i: number) => items[i]; describe('findMediaRun', () => { it('collects contiguous same-sender media within the gap, from any member', () => { const at = seq([ text('a', 0), media('a', 1000), media('a', 2000), media('a', 3000), text('a', 4000), ]); assert.deepEqual(findMediaRun(at, 1), [1, 2, 3]); assert.deepEqual(findMediaRun(at, 2), [1, 2, 3]); assert.deepEqual(findMediaRun(at, 3), [1, 2, 3]); }); it('breaks on a different sender, text in between, or a long gap', () => { const at = seq([ media('a', 0), media('b', 1000), media('a', 2000), text('a', 2500), media('a', 3000), media('a', 70_000), ]); assert.deepEqual(findMediaRun(at, 0), [0]); assert.deepEqual(findMediaRun(at, 2), [2]); assert.deepEqual(findMediaRun(at, 4), [4]); assert.deepEqual(findMediaRun(at, 5), [5]); }); it('skips invisible filler such as reactions and edits', () => { const at = seq([media('a', 0), skip(100), skip(200), media('a', 1000)]); assert.deepEqual(findMediaRun(at, 0), [0, 3]); }); it('never groups replies, thread messages, edits or redacted events', () => { const at = seq([ media('a', 0), media('a', 500, { hasRelation: true }), media('a', 1000), media('a', 1500, { redacted: true }), ]); assert.deepEqual(findMediaRun(at, 0), [0]); assert.deepEqual(findMediaRun(at, 1), []); assert.deepEqual(findMediaRun(at, 2), [2]); }); it('measures the gap between consecutive members, not from the first', () => { const at = seq([media('a', 0), media('a', 50_000), media('a', 100_000)]); assert.deepEqual(findMediaRun(at, 0), [0, 1, 2]); }); }); describe('splitRun', () => { it('caps group size and drops singles', () => { const items = Array.from({ length: 12 }, (_, i) => media('a', i * 1000)); const groups = splitRun( Array.from({ length: 12 }, (_, i) => i), seq(items), 10, ); assert.deepEqual(groups, [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11], ]); assert.deepEqual(splitRun([0, 1, 2, 3], seq(items), 3), [[0, 1, 2]]); }); it('closes a group at a member with reactions so they stay visible', () => { const items = [ media('a', 0), media('a', 1000, { mustEnd: true }), media('a', 2000), media('a', 3000), ]; assert.deepEqual(splitRun([0, 1, 2, 3], seq(items)), [ [0, 1], [2, 3], ]); }); }); describe('planMediaGroups', () => { it('marks only the last member as the renderer', () => { const at = seq([media('a', 0), media('a', 1000), media('a', 2000)]); const plan = planMediaGroups(at, 1); assert.equal(plan.get(0)?.renders, false); assert.equal(plan.get(1)?.renders, false); assert.equal(plan.get(2)?.renders, true); assert.deepEqual(plan.get(2)?.members, [0, 1, 2]); }); it('returns an empty plan for a lone image', () => { assert.equal(planMediaGroups(seq([text('a', 0), media('a', 1000)]), 1).size, 0); }); });