- Anchor the message list to the bottom (`reverse: true`) so new messages stay in view instead of landing below the fold. - Move chat history from the OS keystore (O(n²) JSON blob, silent 200-msg cap) to a separate encrypted Drift/SQLCipher DB (`ChatDatabase`): indexed append, uncapped history, dedup as a unique-key invariant. It's an ephemeral per-device cache, isolated from the inventory schema, its migrations, and its sync. No data migration (pre-release). - Add day separators (Today/Yesterday/locale date) and a per-bubble time, all via ICU (12/24h per locale; Localizations locale maps Asturian → Spanish for intl date symbols). - Add peer avatars (deterministic colour from the pubkey + name initial), surface send failures that were previously silent, and make bubble text selectable (addresses, links). - New i18n keys in en/es/pt/ast; tests for grouping, formatting, avatars, scroll anchoring, storage and send errors. Docs: docs/design/chat-storage.md + open-decisions.md.
120 lines
3.3 KiB
Dart
120 lines
3.3 KiB
Dart
import 'package:commons_core/commons_core.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:intl/date_symbol_data_local.dart';
|
|
import 'package:tane/domain/chat_timeline.dart';
|
|
|
|
void main() {
|
|
// In the app, flutter_localizations loads intl's locale symbols; a pure test
|
|
// must initialize them itself before any DateFormat.
|
|
setUpAll(initializeDateFormatting);
|
|
|
|
PrivateMessage at(DateTime when) =>
|
|
PrivateMessage(fromPubkey: 'p', text: 't', at: when);
|
|
|
|
group('chatTimeline', () {
|
|
test('empty in, empty out', () {
|
|
expect(chatTimeline(const []), isEmpty);
|
|
});
|
|
|
|
test('a day separator precedes the first message of each day', () {
|
|
final items = chatTimeline([
|
|
at(DateTime(2026, 6, 14, 10)),
|
|
at(DateTime(2026, 6, 14, 11)),
|
|
at(DateTime(2026, 6, 15, 9)),
|
|
]);
|
|
// sep, msg, msg, sep, msg
|
|
expect(items.map((i) => i is ChatDaySeparator), [
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
]);
|
|
expect((items[0] as ChatDaySeparator).day, DateTime(2026, 6, 14));
|
|
expect((items[3] as ChatDaySeparator).day, DateTime(2026, 6, 15));
|
|
});
|
|
|
|
test('messages stay oldest-first', () {
|
|
final items = chatTimeline([
|
|
at(DateTime(2026, 1, 1, 8)),
|
|
at(DateTime(2026, 1, 1, 9)),
|
|
]);
|
|
expect(items.whereType<ChatMessageRow>(), hasLength(2));
|
|
expect(items.whereType<ChatDaySeparator>(), hasLength(1));
|
|
});
|
|
});
|
|
|
|
group('chatDayLabel', () {
|
|
final now = DateTime(2026, 6, 15, 12);
|
|
|
|
test('the two most recent days use the given today/yesterday labels', () {
|
|
expect(
|
|
chatDayLabel(
|
|
DateTime(2026, 6, 15),
|
|
now: now,
|
|
today: 'Today',
|
|
yesterday: 'Yesterday',
|
|
localeCode: 'en',
|
|
),
|
|
'Today',
|
|
);
|
|
expect(
|
|
chatDayLabel(
|
|
DateTime(2026, 6, 14),
|
|
now: now,
|
|
today: 'Today',
|
|
yesterday: 'Yesterday',
|
|
localeCode: 'en',
|
|
),
|
|
'Yesterday',
|
|
);
|
|
});
|
|
|
|
test('older days use a locale date, not today/yesterday', () {
|
|
final label = chatDayLabel(
|
|
DateTime(2026, 6, 10),
|
|
now: now,
|
|
today: 'Today',
|
|
yesterday: 'Yesterday',
|
|
localeCode: 'en',
|
|
);
|
|
expect(label, isNot(anyOf('Today', 'Yesterday')));
|
|
expect(label, contains('10')); // the day number
|
|
});
|
|
|
|
test('a previous year is shown', () {
|
|
final label = chatDayLabel(
|
|
DateTime(2024, 6, 10),
|
|
now: now,
|
|
today: 'Today',
|
|
yesterday: 'Yesterday',
|
|
localeCode: 'en',
|
|
);
|
|
expect(label, contains('2024'));
|
|
});
|
|
|
|
test('respects the locale (Spanish month name)', () {
|
|
final label = chatDayLabel(
|
|
DateTime(2026, 6, 10),
|
|
now: now,
|
|
today: 'Hoy',
|
|
yesterday: 'Ayer',
|
|
localeCode: 'es',
|
|
);
|
|
expect(label.toLowerCase(), contains('junio'));
|
|
});
|
|
});
|
|
|
|
group('chatBubbleTime', () {
|
|
test('English uses a 12-hour clock', () {
|
|
final s = chatBubbleTime(DateTime(2026, 1, 1, 14, 5), 'en');
|
|
expect(s.toUpperCase(), contains('PM'));
|
|
});
|
|
|
|
test('Spanish uses a 24-hour clock', () {
|
|
final s = chatBubbleTime(DateTime(2026, 1, 1, 14, 5), 'es');
|
|
expect(s, contains('14'));
|
|
expect(s.toUpperCase(), isNot(contains('PM')));
|
|
});
|
|
});
|
|
}
|