179 lines
5.7 KiB
Dart
179 lines
5.7 KiB
Dart
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 = <ThreadMessage>[];
|
|
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 = <ThreadMessage>[];
|
|
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'));
|
|
});
|
|
});
|
|
}
|