import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:melo/player/sleep_timer.dart'; void main() { group('SleepTimer with mocked clock', () { late DateTime currentTime; late SleepTimer sleepTimer; late int elapsedCount; setUp(() { currentTime = DateTime(2024, 1, 1, 12, 0, 0); elapsedCount = 0; sleepTimer = SleepTimer( onElapsed: () => elapsedCount++, now: () => currentTime, ); }); tearDown(() { sleepTimer.cancel(); }); test('remaining is null when inactive', () { expect(sleepTimer.remaining.value, isNull); }); test('start sets remaining to duration', () { sleepTimer.start(const Duration(minutes: 5)); expect(sleepTimer.remaining.value, isNotNull); expect(sleepTimer.remaining.value!.inMinutes, equals(5)); }); test('remaining decrements as time passes', () { sleepTimer.start(const Duration(minutes: 1)); expect(sleepTimer.remaining.value!.inSeconds, equals(60)); // Simulate 30 seconds passing currentTime = currentTime.add(const Duration(seconds: 30)); sleepTimer.tick(); // Manually call tick to simulate timer expect(sleepTimer.remaining.value!.inSeconds, equals(30)); }); test('fires callback when timer elapses', () { sleepTimer.start(const Duration(minutes: 5)); expect(elapsedCount, equals(0)); // Simulate 5 minutes passing currentTime = currentTime.add(const Duration(minutes: 5)); sleepTimer.tick(); expect(elapsedCount, equals(1), reason: 'onElapsed should fire once'); expect(sleepTimer.remaining.value, isNull); }); test('cancel stops timer and clears remaining', () { sleepTimer.start(const Duration(minutes: 5)); expect(sleepTimer.remaining.value, isNotNull); sleepTimer.cancel(); expect(sleepTimer.remaining.value, isNull); expect(elapsedCount, equals(0), reason: 'Should not fire after cancel'); }); test('does not fire if duration is zero', () { sleepTimer.start(Duration.zero); expect(elapsedCount, equals(0)); expect(sleepTimer.remaining.value, isNull); }); test('does not fire if duration is negative', () { sleepTimer.start(const Duration(seconds: -5)); expect(elapsedCount, equals(0)); expect(sleepTimer.remaining.value, isNull); }); test('start while running replaces old timer', () { sleepTimer.start(const Duration(minutes: 10)); final firstDuration = sleepTimer.remaining.value!; sleepTimer.start(const Duration(minutes: 5)); final secondDuration = sleepTimer.remaining.value!; expect(secondDuration < firstDuration, isTrue); expect(elapsedCount, equals(0)); }); test('fires exactly once when elapsed', () { sleepTimer.start(const Duration(minutes: 5)); // Advance time past deadline currentTime = currentTime.add(const Duration(minutes: 6)); sleepTimer.tick(); expect(elapsedCount, equals(1)); // Even if we call tick again, should not fire twice sleepTimer.tick(); expect(elapsedCount, equals(1), reason: 'Should fire only once'); }); test('does not fire if cancelled before elapse', () { sleepTimer.start(const Duration(minutes: 5)); currentTime = currentTime.add(const Duration(minutes: 3)); sleepTimer.cancel(); currentTime = currentTime.add(const Duration(minutes: 3)); sleepTimer.tick(); expect(elapsedCount, equals(0)); }); }); }