import 'package:flutter_test/flutter_test.dart'; import 'package:life_helper/domain/models/tracker_entry.dart'; import 'package:life_helper/domain/streak/compute_streak.dart'; TrackerEntryModel _e(String date, TrackerValue v) => TrackerEntryModel(id: 'te_$date', habitId: 'hb', date: date, value: v); void main() { group('computeStreak', () { test('empty → all zero, T0, not broken', () { final s = computeStreak( entries: const [], asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); expect(s.currentStreak, 0); expect(s.currentTier, RewardTier.t0); expect(s.neverMissTwiceBroken, false); }); test('3 consecutive done → T1', () { final s = computeStreak( entries: [ _e('2026-06-09', TrackerValue.done), _e('2026-06-10', TrackerValue.done), _e('2026-06-11', TrackerValue.done), ], asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); expect(s.currentStreak, 3); expect(s.currentTier, RewardTier.t1); }); test('7 consecutive done → T2', () { final entries = []; for (var i = 0; i < 7; i++) { final d = DateTime(2026, 6, 5).add(Duration(days: i)); entries.add(_e(_ymd(d), TrackerValue.done)); } final s = computeStreak( entries: entries, asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); expect(s.currentStreak, 7); expect(s.currentTier, RewardTier.t2); }); test('OQ-5: 1 blank → streak=0, tier stays (not broken)', () { // 6/9 done, 6/10 blank entry, 6/11 done. final s = computeStreak( entries: [ _e('2026-06-09', TrackerValue.done), _e('2026-06-10', TrackerValue.blank), _e('2026-06-11', TrackerValue.done), ], asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); // Walks back: 6/11 done (+1), 6/10 blank → streak resets to 0. expect(s.currentStreak, 0); expect(s.neverMissTwiceBroken, false); }); test('OQ-5: 2 consecutive blank → neverMissTwiceBroken=true', () { final s = computeStreak( entries: [ _e('2026-06-09', TrackerValue.done), _e('2026-06-10', TrackerValue.blank), _e('2026-06-11', TrackerValue.blank), ], asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); expect(s.currentStreak, 0); expect(s.neverMissTwiceBroken, true); }); test('window30: 24/30 done → T3', () { final entries = []; // 24 done in last 30 days, but not as a streak. for (var i = 0; i < 24; i++) { final d = DateTime(2026, 6, 11).subtract(Duration(days: i)); entries.add(_e(_ymd(d), TrackerValue.done)); } final s = computeStreak( entries: entries, asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-05-01', ); expect(s.doneCountInWindow30, 24); expect(s.currentTier.rank, greaterThanOrEqualTo(RewardTier.t3.rank)); }); test('longestStreak picks largest run regardless of current', () { final s = computeStreak( entries: [ for (final d in ['2026-06-01', '2026-06-02', '2026-06-03', '2026-06-04']) _e(d, TrackerValue.done), _e('2026-06-05', TrackerValue.blank), _e('2026-06-11', TrackerValue.done), ], asOf: DateTime(2026, 6, 11), habitStartedAt: '2026-06-01', ); expect(s.longestStreak, 4); }); }); } String _ymd(DateTime d) => '${d.year.toString().padLeft(4, '0')}-' '${d.month.toString().padLeft(2, '0')}-' '${d.day.toString().padLeft(2, '0')}';