import { parseMessagePayload, type AttachmentHandle, type DecryptedMessage, } from '@chat-app/shared/chat'; import { describe, expect, it } from 'vitest'; import { collectConversationAttachments, createPollPayload, summarizePollVotes, } from './conversationFeatures'; function handle(id: string, mimeType: string): AttachmentHandle { return { id, storagePath: 'conversation/' + id + '.bin', mimeType, sizeBytes: 2048, keyB64: 'key', nonceB64: 'nonce', }; } function message(id: string, createdAt: string, attachments: AttachmentHandle[]): DecryptedMessage { return { id, conversationId: 'conv-1', senderId: 'sender-1', senderDeviceId: 'device-1', replyToId: null, editedAt: null, deletedAt: null, createdAt, plaintext: JSON.stringify({ v: 1, type: 'text', text: '', attachments }), }; } describe('collectConversationAttachments', () => { it('indexes media, audio, and files newest first', () => { const first = message('m1', '2026-04-24T09:00:00.000Z', [ handle('img-1', 'image/png'), handle('file-1', 'application/pdf'), ]); const second = message('m2', '2026-04-24T10:00:00.000Z', [ handle('audio-1', 'audio/webm'), handle('video-1', 'video/mp4'), ]); const index = collectConversationAttachments([first, second]); expect(index.media.map((item) => item.handle.id)).toEqual(['video-1', 'img-1']); expect(index.audio.map((item) => item.handle.id)).toEqual(['audio-1']); expect(index.files.map((item) => item.handle.id)).toEqual(['file-1']); expect(index.all.map((item) => item.messageId)).toEqual(['m2', 'm2', 'm1', 'm1']); }); }); describe('createPollPayload', () => { it('creates a parseable encrypted-message poll payload', () => { const payload = createPollPayload(' Lieblings Feature? ', [' Media Drawer ', '', 'Polls']); const parsed = parseMessagePayload(payload); expect(parsed.kind).toBe('poll'); if (parsed.kind !== 'poll') throw new Error('expected poll payload'); expect(parsed.question).toBe('Lieblings Feature?'); expect(parsed.options).toEqual([ { id: 'option-1', emoji: '1️⃣', text: 'Media Drawer' }, { id: 'option-2', emoji: '2️⃣', text: 'Polls' }, ]); }); it('requires a question and at least two non-empty options', () => { expect(() => createPollPayload('', ['A', 'B'])).toThrow('question'); expect(() => createPollPayload('Feature?', ['A', ''])).toThrow('options'); }); }); describe('summarizePollVotes', () => { it('counts only configured poll options and marks the current user vote', () => { const summary = summarizePollVotes( [ { id: 'option-1', emoji: '1️⃣', text: 'Media Drawer' }, { id: 'option-2', emoji: '2️⃣', text: 'Polls' }, ], [ { emoji: '1️⃣', count: 3, mine: false }, { emoji: '2️⃣', count: 1, mine: true }, { emoji: '🔥', count: 99, mine: true }, ], ); expect(summary.totalVotes).toBe(4); expect(summary.options.map((option) => option.percent)).toEqual([75, 25]); expect(summary.options.map((option) => option.mine)).toEqual([false, true]); }); });