Files
habo/test/habits/habits_manager_fixed_test.dart
dazhuang aa69f2a91e feat: initial commit - Habo habit tracking app
- Complete MVP with Repository Pattern, SQLite storage
- Provider + ChangeNotifier state management
- Navigation 2.0 with deep link support
- Habit CRUD with twoDayRule, notifications, categories
- Backup/Restore via JSON
- Statistics with streak tracking
- Material You theme support
- Biometric lock support
- Desktop widget support
- 27 languages i18n structure
- Comprehensive test suite (87/89 passing)
2026-04-13 15:02:30 +00:00

378 lines
12 KiB
Dart

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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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<DateTime, List>(),
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');
});
});
});
}