import 'package:flutter_test/flutter_test.dart'; import 'package:marianum_mobile/push/chat_thread_store.dart'; ThreadMessage _msg(int nid, {String sender = 'Max', String text = 'hi'}) => ThreadMessage(nid: nid, sender: sender, text: text, timestampMs: nid); void main() { group('appendThreadMessage', () { test('appends in order', () { var messages = []; messages = appendThreadMessage(messages, _msg(1)); messages = appendThreadMessage(messages, _msg(2)); expect(messages.map((m) => m.nid), [1, 2]); }); test('caps the history, dropping the oldest', () { var messages = []; for (var nid = 1; nid <= kChatThreadCap + 3; nid++) { messages = appendThreadMessage(messages, _msg(nid)); } expect(messages, hasLength(kChatThreadCap)); expect(messages.first.nid, 4); expect(messages.last.nid, kChatThreadCap + 3); }); test('a redelivered nid replaces the old entry instead of duplicating', () { var messages = [_msg(1), _msg(2)]; messages = appendThreadMessage(messages, _msg(1, text: 'edited')); expect(messages.map((m) => m.nid), [2, 1]); expect(messages.last.text, 'edited'); }); }); group('threadAfterIncoming (dismiss/read reset)', () { final existing = [_msg(1), _msg(2)]; final incoming = _msg(3); test('active notification → new message stacks onto history', () { final result = threadAfterIncoming(existing, incoming, true); expect(result.map((m) => m.nid), [1, 2, 3]); }); test('inactive notification → thread restarts with only the new one', () { final result = threadAfterIncoming(existing, incoming, false); expect(result.map((m) => m.nid), [3]); }); test('unknown active state (probe failed) → stacks defensively', () { final result = threadAfterIncoming(existing, incoming, null); expect(result.map((m) => m.nid), [1, 2, 3]); }); test('reset still honours the cap for a burst of messages', () { final result = threadAfterIncoming(existing, incoming, false, cap: 1); expect(result.map((m) => m.nid), [3]); }); }); group('removeThreadNid', () { test('removes only the matching message', () { final remaining = removeThreadNid([_msg(1), _msg(2), _msg(3)], 2); expect(remaining.map((m) => m.nid), [1, 3]); }); test('deleting the last message empties the thread (caller cancels)', () { final remaining = removeThreadNid([_msg(7)], 7); expect(remaining, isEmpty); }); test('unknown nid leaves the thread untouched', () { final remaining = removeThreadNid([_msg(1)], 99); expect(remaining.map((m) => m.nid), [1]); }); }); group('conversationHeader', () { test('1:1 chat (single sender, no room) gets no title', () { final header = conversationHeader([ _msg(1, sender: 'Max'), _msg(2, sender: 'Max'), ]); expect(header.conversationTitle, isNull); expect(header.groupConversation, isFalse); }); test('a known room name becomes the title exactly once', () { final header = conversationHeader([ ThreadMessage( nid: 1, sender: 'Max', text: 'hi', timestampMs: 1, roomName: 'Projektraum', ), _msg(2, sender: 'Max'), ]); expect(header.conversationTitle, 'Projektraum'); expect(header.groupConversation, isTrue); }); test('multiple senders without room name fall back to last sender', () { final header = conversationHeader([ _msg(1, sender: 'Max'), _msg(2, sender: 'Anna'), ]); expect(header.conversationTitle, 'Anna'); expect(header.groupConversation, isTrue); }); }); group('ThreadMessage roomName migration', () { test('entries without roomName field read as null', () { final restored = ThreadMessage.fromJson({ 'nid': 1, 'sender': 'Max', 'text': 'hi', 'timestampMs': 5, }); expect(restored.roomName, isNull); }); test('roomName round-trips when present', () { final restored = ThreadMessage.fromJson( const ThreadMessage( nid: 1, sender: 'Max', text: 'hi', timestampMs: 5, roomName: 'Raum', ).toJson(), ); expect(restored.roomName, 'Raum'); }); }); group('stable notification identity', () { test('same token always yields the same 31-bit id', () { final a = stableChatNotificationId('abc123'); expect(a, stableChatNotificationId('abc123')); expect(a, greaterThanOrEqualTo(0)); expect(a, lessThanOrEqualTo(0x7fffffff)); }); test('different tokens yield different ids and tags', () { expect( stableChatNotificationId('abc123'), isNot(stableChatNotificationId('xyz789')), ); expect(chatNotificationTag('abc123'), 'talk_abc123'); }); }); group('ThreadMessage json', () { test('round-trips', () { final restored = ThreadMessage.fromJson( ThreadMessage( nid: 5, sender: 'Max', text: 'Hallo', timestampMs: 1234, ).toJson(), ); expect(restored.nid, 5); expect(restored.sender, 'Max'); expect(restored.text, 'Hallo'); expect(restored.timestampMs, 1234); }); }); group('ChatThreadStore.docIdForToken', () { test('url-safe tokens are used verbatim, others encoded', () { expect(ChatThreadStore.docIdForToken('abc_1-2'), 'abc_1-2'); final encoded = ChatThreadStore.docIdForToken('a/b'); expect(encoded, isNot(contains('/'))); expect(encoded, ChatThreadStore.docIdForToken('a/b')); }); }); }