Files
habo/test/habits/habits_manager_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

466 lines
16 KiB
Dart

import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/habit_repository.dart';
import 'package:habo/repositories/event_repository.dart';
import 'package:habo/repositories/category_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(() {
registerFallbackValue(Habit(
habitData: HabitData(
position: 0,
title: '',
twoDayRule: false,
cue: '',
routine: '',
reward: '',
showReward: false,
advanced: false,
events: SplayTreeMap<DateTime, List>(),
notification: false,
notTime: const TimeOfDay(hour: 0, minute: 0),
sanction: '',
showSanction: false,
accountant: '',
),
));
registerFallbackValue(HabitData(
position: 0,
title: '',
twoDayRule: false,
cue: '',
routine: '',
reward: '',
showReward: false,
advanced: false,
events: SplayTreeMap<DateTime, List>(),
notification: false,
notTime: const TimeOfDay(hour: 0, minute: 0),
sanction: '',
showSanction: false,
accountant: '',
));
});
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(
position: 1,
title: 'Test Habit 1',
twoDayRule: false,
cue: 'Test cue',
routine: 'Test routine',
reward: 'Test reward',
showReward: false,
advanced: false,
notification: false,
notTime: const TimeOfDay(hour: 9, minute: 0),
events: SplayTreeMap<DateTime, List<dynamic>>(),
sanction: 'Test sanction',
showSanction: false,
accountant: 'Test accountant',
),
),
];
when(() => mockHabitRepository.getAllHabits())
.thenAnswer((_) async => mockHabits);
// Act
await habitsManager.initModel();
// Assert
verify(() => mockHabitRepository.getAllHabits()).called(1);
expect(habitsManager.allHabits.length, 1);
expect(habitsManager.allHabits[0].habitData.title, 'Test Habit 1');
});
test('should handle empty habits list', () async {
// Arrange
when(() => mockHabitRepository.getAllHabits())
.thenAnswer((_) async => []);
// Act
await habitsManager.initModel();
// Assert
expect(habitsManager.allHabits, isEmpty);
});
group('CRUD Operations', () {
setUp(() async {
// Setup initial state with empty habits
when(() => mockHabitRepository.getAllHabits())
.thenAnswer((_) async => []);
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
await habitsManager.initModel();
});
group('Create Operations', () {
test('should add a new habit', () async {
// Arrange
const testTitle = 'Test Habit';
const testCue = 'Test cue';
const testRoutine = 'Test routine';
const testReward = 'Test reward';
const testSanction = 'Test sanction';
const testAccountant = 'Test accountant';
const testTime = TimeOfDay(hour: 9, minute: 0);
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
// Act
habitsManager.addHabit(
testTitle,
false, // twoDayRule
testCue,
testRoutine,
testReward,
false, // showReward
false, // advanced
false, // notification
testTime,
testSanction,
false, // showSanction
testAccountant,
);
await Future.delayed(
Duration.zero); // Allow async operations to complete
// Assert
expect(habitsManager.allHabits.length, 1);
expect(habitsManager.allHabits[0].habitData.title, testTitle);
expect(habitsManager.allHabits[0].habitData.cue, testCue);
expect(habitsManager.allHabits[0].habitData.routine, testRoutine);
verify(() => mockHabitRepository.createHabit(any())).called(1);
});
test('should add habit with correct position', () async {
// Arrange
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
// Act - Add first habit
habitsManager.addHabit('First Habit', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
// Manually set ID for testing
habitsManager.allHabits[0].habitData.id = 1;
// Act - Add second habit
habitsManager.addHabit('Second Habit', false, '', '', '', false,
false, false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
// Manually set ID for testing
habitsManager.allHabits[1].habitData.id = 2;
// Assert
expect(habitsManager.allHabits.length, 2);
expect(habitsManager.allHabits[0].habitData.position, 0);
expect(habitsManager.allHabits[1].habitData.position, 1);
});
});
group('Read Operations', () {
setUp(() async {
// Add some test habits
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
habitsManager.addHabit('Habit 1', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
habitsManager.allHabits[0].habitData.id = 1;
habitsManager.addHabit('Habit 2', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
habitsManager.allHabits[1].habitData.id = 2;
});
test('should find habit by id', () {
// Act
final habit = habitsManager.findHabitById(1);
// Assert
expect(habit, isNotNull);
expect(habit!.habitData.title, 'Habit 1');
});
test('should return null for non-existent habit id', () {
// Act
final habit = habitsManager.findHabitById(999);
// Assert
expect(habit, isNull);
});
test('should get habit name by id', () {
// Act
final name = habitsManager.getNameOfHabit(1);
// Assert
expect(name, 'Habit 1');
});
test('should return empty string for non-existent habit name', () {
// Act
final name = habitsManager.getNameOfHabit(999);
// Assert
expect(name, '');
});
});
group('Update Operations', () {
late Habit testHabit;
setUp(() async {
// Setup a test habit
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
when(() => mockHabitRepository.updateHabit(any()))
.thenAnswer((_) async {});
when(() => mockHabitRepository.getAllHabits())
.thenAnswer((_) async => [testHabit]);
habitsManager.addHabit(
'Original Title',
false,
'Original cue',
'Original routine',
'Original reward',
false,
false,
false,
const TimeOfDay(hour: 9, minute: 0),
'Original sanction',
false,
'Original accountant');
await Future.delayed(Duration.zero);
testHabit = habitsManager.allHabits[0];
testHabit.habitData.id = 1;
});
test('should edit existing habit', () async {
// Arrange
final updatedData = HabitData(
position: testHabit.habitData.position,
title: 'Updated Title',
twoDayRule: true,
cue: 'Updated cue',
routine: 'Updated routine',
reward: 'Updated reward',
showReward: true,
advanced: true,
notification: true,
notTime: const TimeOfDay(hour: 10, minute: 30),
events: SplayTreeMap<DateTime, List>(),
sanction: 'Updated sanction',
showSanction: true,
accountant: 'Updated accountant',
);
updatedData.id = 1;
// Act
habitsManager.editHabit(updatedData);
// Assert
expect(habitsManager.allHabits.length, 1);
expect(habitsManager.allHabits[0].habitData.title, 'Updated Title');
expect(habitsManager.allHabits[0].habitData.twoDayRule, true);
expect(habitsManager.allHabits[0].habitData.cue, 'Updated cue');
verify(() => mockHabitRepository.updateHabit(any())).called(1);
});
test('should update habit notification settings', () async {
// Arrange
final updatedData = HabitData(
position: testHabit.habitData.position,
title: testHabit.habitData.title,
twoDayRule: testHabit.habitData.twoDayRule,
cue: testHabit.habitData.cue,
routine: testHabit.habitData.routine,
reward: testHabit.habitData.reward,
showReward: testHabit.habitData.showReward,
advanced: testHabit.habitData.advanced,
notification: true, // Changed from false to true
notTime: const TimeOfDay(hour: 15, minute: 45),
events: SplayTreeMap<DateTime, List>(),
sanction: testHabit.habitData.sanction,
showSanction: testHabit.habitData.showSanction,
accountant: testHabit.habitData.accountant,
);
updatedData.id = 1;
// Act
habitsManager.editHabit(updatedData);
// Assert
expect(habitsManager.allHabits[0].habitData.notification, true);
expect(habitsManager.allHabits[0].habitData.notTime.hour, 15);
expect(habitsManager.allHabits[0].habitData.notTime.minute, 45);
});
});
group('Delete Operations', () {
late Habit testHabit;
setUp(() async {
// Setup a test habit
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
when(() => mockHabitRepository.deleteHabit(any()))
.thenAnswer((_) async {});
habitsManager.addHabit('Test Habit', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
testHabit = habitsManager.allHabits[0];
testHabit.habitData.id = 1;
});
test('should delete habit', () async {
// Act - Simulate the core deletion logic
final habitToDelete = habitsManager.findHabitById(1);
expect(habitToDelete, isNotNull);
habitsManager.allHabits.remove(habitToDelete);
habitsManager.updateOrder();
// Assert
expect(habitsManager.allHabits.length, 0);
expect(habitsManager.findHabitById(1), isNull);
});
test('should undo delete habit', () async {
// Arrange - Simulate deletion
final deletedHabit = habitsManager.findHabitById(1);
habitsManager.allHabits.remove(deletedHabit);
// Act - Undo
habitsManager.allHabits.insert(0, deletedHabit!);
habitsManager.updateOrder();
// Assert
expect(habitsManager.allHabits.length, 1);
expect(habitsManager.allHabits[0], deletedHabit);
});
});
group('Utility Methods', () {
setUp(() async {
// Add some test habits
when(() => mockHabitRepository.createHabit(any()))
.thenAnswer((_) async => 1);
habitsManager.addHabit('First Habit', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
habitsManager.allHabits[0].habitData.id = 1;
habitsManager.addHabit('Second Habit', false, '', '', '', false,
false, false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
habitsManager.allHabits[1].habitData.id = 2;
habitsManager.addHabit('Third Habit', false, '', '', '', false, false,
false, const TimeOfDay(hour: 9, minute: 0), '', false, '');
await Future.delayed(Duration.zero);
habitsManager.allHabits[2].habitData.id = 3;
});
test('should update habit positions correctly', () async {
// Act - Simulate deletion of middle habit
final habitToDelete = habitsManager.findHabitById(2);
habitsManager.allHabits.remove(habitToDelete);
habitsManager.updateOrder();
// Assert positions are updated
expect(habitsManager.allHabits.length, 2);
expect(habitsManager.allHabits[0].habitData.position, 0);
expect(habitsManager.allHabits[1].habitData.position, 1);
});
test('should maintain correct positions after undo', () async {
// Arrange
final deletedHabit = habitsManager.allHabits[1];
// Act - Simulate delete and undo
habitsManager.allHabits.remove(deletedHabit);
habitsManager.updateOrder();
habitsManager.allHabits.insert(1, deletedHabit);
habitsManager.updateOrder();
// Assert positions are correct
expect(habitsManager.allHabits.length, 3);
for (int i = 0; i < 3; i++) {
expect(habitsManager.allHabits[i].habitData.position, i);
}
});
});
});
});
}