import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:habo/generated/l10n.dart'; import 'package:habo/habits/habit.dart'; import 'package:habo/habits/habits_manager.dart'; import 'package:habo/model/habit_data.dart'; import 'package:habo/repositories/category_repository.dart'; import 'package:habo/repositories/event_repository.dart'; import 'package:habo/repositories/habit_repository.dart'; import 'package:habo/services/backup_service.dart'; import 'package:habo/services/notification_service.dart'; import 'package:habo/services/ui_feedback_service.dart'; import 'package:mocktail/mocktail.dart'; class MockHabitRepository extends Mock implements HabitRepository {} class MockEventRepository extends Mock implements EventRepository {} class MockCategoryRepository extends Mock implements CategoryRepository {} class MockBackupService extends Mock implements BackupService {} class MockNotificationService extends Mock implements NotificationService {} class MockUIFeedbackService extends Mock implements UIFeedbackService {} void main() { late HabitsManager habitsManager; late MockHabitRepository mockHabitRepository; late MockEventRepository mockEventRepository; late MockCategoryRepository mockCategoryRepository; late MockBackupService mockBackupService; late MockNotificationService mockNotificationService; late MockUIFeedbackService mockUIFeedbackService; setUpAll(() async { // Initialize localization for tests TestWidgetsFlutterBinding.ensureInitialized(); await S.load(const Locale('en')); registerFallbackValue(Habit( habitData: HabitData( position: 0, title: 'Fallback', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', ), )); registerFallbackValue(TimeOfDay.now()); registerFallbackValue(Colors.grey); }); setUp(() { mockHabitRepository = MockHabitRepository(); mockEventRepository = MockEventRepository(); mockCategoryRepository = MockCategoryRepository(); mockBackupService = MockBackupService(); mockNotificationService = MockNotificationService(); mockUIFeedbackService = MockUIFeedbackService(); habitsManager = HabitsManager( habitRepository: mockHabitRepository, eventRepository: mockEventRepository, categoryRepository: mockCategoryRepository, backupService: mockBackupService, notificationService: mockNotificationService, uiFeedbackService: mockUIFeedbackService, ); }); group('HabitsManager Tests', () { test('should initialize with provided repositories', () { expect(habitsManager, isNotNull); }); test('should populate allHabits from repository', () async { // Arrange final mockHabits = [ Habit( habitData: HabitData( id: 1, position: 0, title: 'Test Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', ), ), ]; when(() => mockHabitRepository.getAllHabits()) .thenAnswer((_) async => mockHabits); // Act await habitsManager.initModel(); // Assert verify(() => mockHabitRepository.getAllHabits()).called(1); }); group('CRUD Operations', () { test('should add habit', () async { // Arrange when(() => mockHabitRepository.createHabit(any())) .thenAnswer((_) async => 1); // Act habitsManager.addHabit( 'Test Habit', false, 'Test cue', 'Test routine', 'Test reward', false, false, false, const TimeOfDay(hour: 9, minute: 0), 'Test sanction', false, 'Test accountant', ); // Assert verify(() => mockHabitRepository.createHabit(any())).called(1); }); test('should edit habit', () async { // Setup final testHabit = Habit( habitData: HabitData( id: 1, position: 0, title: 'Test Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', ), ); // Add habit to internal state habitsManager.allHabits.add(testHabit); when(() => mockHabitRepository.updateHabit(any())) .thenAnswer((_) async {}); // Act habitsManager.editHabit(testHabit.habitData); // Assert verify(() => mockHabitRepository.updateHabit(any())).called(1); }); test('should delete habit', () async { // Setup final testHabit = Habit( habitData: HabitData( id: 1, position: 0, title: 'Test Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', ), ); // Add habit to internal state habitsManager.allHabits.add(testHabit); when(() => mockHabitRepository.deleteHabit(any())) .thenAnswer((_) async {}); when(() => mockUIFeedbackService.showMessageWithAction( message: any(named: 'message'), actionLabel: any(named: 'actionLabel'), onActionPressed: any(named: 'onActionPressed'), backgroundColor: any(named: 'backgroundColor'), )).thenReturn(null); // Act habitsManager.deleteHabit(1); // Assert - verify internal state changes immediately expect(habitsManager.allHabits.length, 0); expect(habitsManager.toDelete.length, 1); }); }); group('Archive Operations', () { test('should archive habit', () async { // Setup final testHabit = Habit( habitData: HabitData( id: 1, position: 0, title: 'Test Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', archived: false, ), ); // Add habit to internal state habitsManager.allHabits.add(testHabit); when(() => mockHabitRepository.updateHabit(any())) .thenAnswer((_) async {}); when(() => mockNotificationService.disableHabitNotification(any())) .thenReturn(null); when(() => mockUIFeedbackService.showMessageWithAction( message: any(named: 'message'), actionLabel: any(named: 'actionLabel'), onActionPressed: any(named: 'onActionPressed'), backgroundColor: any(named: 'backgroundColor'), )).thenReturn(null); // Act habitsManager.archiveHabit(1); // Assert expect(testHabit.habitData.archived, true); verify(() => mockHabitRepository.updateHabit(any())).called(1); verify(() => mockNotificationService.disableHabitNotification(1)) .called(1); verify(() => mockUIFeedbackService.showMessageWithAction( message: any(named: 'message'), actionLabel: any(named: 'actionLabel'), onActionPressed: any(named: 'onActionPressed'), backgroundColor: any(named: 'backgroundColor'), )).called(1); }); test('should unarchive habit', () async { // Setup final testHabit = Habit( habitData: HabitData( id: 1, position: 0, title: 'Test Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: true, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', archived: true, ), ); // Add habit to internal state habitsManager.allHabits.add(testHabit); when(() => mockHabitRepository.updateHabit(any())) .thenAnswer((_) async {}); when(() => mockNotificationService.setHabitNotification( any(), any(), any(), any())).thenReturn(null); when(() => mockUIFeedbackService.showSuccess(any())).thenReturn(null); // Act habitsManager.unarchiveHabit(1); // Assert expect(testHabit.habitData.archived, false); verify(() => mockHabitRepository.updateHabit(any())).called(1); verify(() => mockNotificationService.setHabitNotification( 1, any(), 'Habo', 'Test Habit')).called(1); verify(() => mockUIFeedbackService.showSuccess(any())).called(1); }); test('should filter active habits correctly', () { // Setup final activeHabit = Habit( habitData: HabitData( id: 1, position: 0, title: 'Active Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', archived: false, ), ); final archivedHabit = Habit( habitData: HabitData( id: 2, position: 1, title: 'Archived Habit', twoDayRule: false, cue: '', routine: '', reward: '', showReward: false, advanced: false, notification: false, notTime: const TimeOfDay(hour: 9, minute: 0), events: SplayTreeMap(), sanction: '', showSanction: false, accountant: '', archived: true, ), ); // Add habits to internal state habitsManager.allHabits.addAll([activeHabit, archivedHabit]); // Act & Assert final activeHabits = habitsManager.activeHabits; final archivedHabits = habitsManager.archivedHabits; expect(activeHabits.length, 1); expect(activeHabits.first.habitData.title, 'Active Habit'); expect(archivedHabits.length, 1); expect(archivedHabits.first.habitData.title, 'Archived Habit'); }); }); }); }