import 'package:flutter_test/flutter_test.dart'; import 'package:zenxercise/src/streak/day.dart'; import 'package:zenxercise/src/streak/streak.dart'; /// A complete Session ending on October [day] at [hour]:[minute]. DateTime oct(int day, [int hour = 18, int minute = 0]) => DateTime(2026, 10, day, hour, minute); void main() { group('Streak Day', () { test('a Session counts for its calendar day', () { expect(streakDays([oct(3, 18)]), {Day(2026, 10, 3)}); }); test('in the Grace Hour it counts for the previous day if that day is not done yet', () { expect(streakDays([oct(4, 0, 30)]), {Day(2026, 10, 3)}); }); test('in the Grace Hour it counts for the current day once the previous day is done', () { expect(streakDays([oct(3, 20), oct(4, 0, 30)]), {Day(2026, 10, 3), Day(2026, 10, 4)}); }); test('from 01:00 on it is the current day again', () { expect(streakDays([oct(4, 1, 0)]), {Day(2026, 10, 4)}); }); }); group('counting', () { test('no Sessions, no Streak', () { final s = Streak.compute(const [], now: oct(10, 9)); expect((s.current, s.longest, s.freezes, s.todayDone), (0, 0, 0, false)); }); test('consecutive days count up and today being done lights it', () { final s = Streak.compute([oct(8), oct(9), oct(10)], now: oct(10, 20)); expect(s.current, 3); expect(s.todayDone, isTrue); }); test('today still open keeps yesterday\'s Streak alive but unlit', () { final s = Streak.compute([oct(8), oct(9)], now: oct(10, 12)); expect(s.current, 2); expect(s.todayDone, isFalse); }); test('a missed day without a Freeze resets the Streak, the longest is kept', () { final s = Streak.compute([oct(1), oct(2), oct(3), oct(5)], now: oct(5, 20)); expect(s.current, 1); expect(s.longest, 3); }); test('yesterday is not missed yet during the Grace Hour', () { final s = Streak.compute([oct(8), oct(9)], now: oct(11, 0, 20)); expect(s.current, 2); }); }); group('Streak Freezes', () { test('one is earned per 7 Streak Days, holding at most 2', () { final days = [for (var d = 1; d <= 21; d++) oct(d)]; expect(Streak.compute(days.take(6).toList(), now: oct(6, 20)).freezes, 0); expect(Streak.compute(days.take(7).toList(), now: oct(7, 20)).freezes, 1); expect(Streak.compute(days, now: oct(21, 20)).freezes, 2); }); test('a Freeze covers a missed day automatically and shows on the calendar', () { final days = [for (var d = 1; d <= 7; d++) oct(d), oct(9)]; final s = Streak.compute(days, now: oct(9, 20)); expect(s.current, 8); expect(s.freezes, 0); expect(s.frozenDays, {Day(2026, 10, 8)}); }); test('more missed days than Freezes reset the Streak', () { final days = [for (var d = 1; d <= 7; d++) oct(d), oct(10)]; final s = Streak.compute(days, now: oct(10, 20)); expect(s.current, 1); expect(s.freezes, 0); }); }); }