import 'package:flutter_test/flutter_test.dart'; import 'package:warp/matrix/timeline.dart'; Entry msg(String sender, DateTime time) => (sender: sender, time: time, kind: EntryKind.message); Entry notice(DateTime time) => (sender: '@admin:x', time: time, kind: EntryKind.notice); Entry hidden(DateTime time) => (sender: '@a:x', time: time, kind: EntryKind.hidden); /// Describes items compactly: `D` for a day, `m`, /// `n` for a notice. List describe(List items) => [ for (final item in items) switch (item) { DayItem() => 'D', EventItem(:final index, :final kind) when kind == EntryKind.notice => 'n$index', EventItem(:final index, :final firstInGroup, :final lastInGroup) => 'm$index${firstInGroup ? 'F' : ''}${lastInGroup ? 'L' : ''}', }, ]; void main() { final t0 = DateTime(2026, 10, 18, 12); test('empty', () { expect(projectTimeline([]), isEmpty); }); test('a run from one sender is one group', () { // Newest first, as the SDK keeps them. final items = projectTimeline([ msg('@a:x', t0.add(const Duration(minutes: 2))), msg('@a:x', t0.add(const Duration(minutes: 1))), msg('@a:x', t0), ]); expect(describe(items), ['m0L', 'm1', 'm2F', 'D']); }); test('a new sender starts a new group', () { final items = projectTimeline([ msg('@b:x', t0.add(const Duration(minutes: 1))), msg('@a:x', t0), ]); expect(describe(items), ['m0FL', 'm1FL', 'D']); }); test('a long pause starts a new group', () { final items = projectTimeline([ msg('@a:x', t0.add(const Duration(minutes: 6))), msg('@a:x', t0), ]); expect(describe(items), ['m0FL', 'm1FL', 'D']); }); test('hidden events are dropped and do not break a group', () { final items = projectTimeline([ msg('@a:x', t0.add(const Duration(minutes: 2))), hidden(t0.add(const Duration(minutes: 1))), msg('@a:x', t0), ]); expect(describe(items), ['m0L', 'm2F', 'D']); }); test('a notice breaks a group', () { final items = projectTimeline([ msg('@a:x', t0.add(const Duration(minutes: 2))), notice(t0.add(const Duration(minutes: 1))), msg('@a:x', t0), ]); expect(describe(items), ['m0FL', 'n1', 'm2FL', 'D']); }); test('a day separator above the first event of each day', () { final yesterday = t0.subtract(const Duration(days: 1)); final items = projectTimeline([ msg('@a:x', t0), msg('@a:x', yesterday.add(const Duration(minutes: 1))), msg('@a:x', yesterday), ]); expect(describe(items), ['m0FL', 'D', 'm1L', 'm2F', 'D']); expect((items[1] as DayItem).day, DateTime(2026, 10, 18)); expect((items[4] as DayItem).day, DateTime(2026, 10, 17)); }); test('messages either side of midnight are separate groups', () { final midnight = DateTime(2026, 10, 18); final items = projectTimeline([ msg('@a:x', midnight.add(const Duration(minutes: 1))), msg('@a:x', midnight.subtract(const Duration(minutes: 1))), ]); expect(describe(items), ['m0FL', 'D', 'm1FL', 'D']); }); }