- Add watchCompletionsForTask(taskId) to TasksDao: Stream<List<TaskCompletion>> sorted newest first - Regenerate tasks_dao.g.dart with build_runner - Add taskHistoryTitle, taskHistoryEmpty, taskHistoryCount to app_de.arb - Regenerate app_localizations.dart and app_localizations_de.dart - All 5 new DAO tests pass, zero analyze issues
158 lines
5.0 KiB
Dart
158 lines
5.0 KiB
Dart
import 'package:drift/native.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:household_keeper/core/database/database.dart';
|
|
import 'package:household_keeper/features/tasks/domain/effort_level.dart';
|
|
import 'package:household_keeper/features/tasks/domain/frequency.dart';
|
|
|
|
void main() {
|
|
late AppDatabase db;
|
|
late int roomId;
|
|
|
|
setUp(() async {
|
|
db = AppDatabase(NativeDatabase.memory());
|
|
roomId = await db.roomsDao.insertRoom(
|
|
RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'),
|
|
);
|
|
});
|
|
|
|
tearDown(() async {
|
|
await db.close();
|
|
});
|
|
|
|
group('TasksDao.watchCompletionsForTask', () {
|
|
test('returns empty list when task has no completions', () async {
|
|
final taskId = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Staubsaugen',
|
|
intervalType: IntervalType.weekly,
|
|
effortLevel: EffortLevel.medium,
|
|
nextDueDate: DateTime(2026, 3, 15),
|
|
),
|
|
);
|
|
|
|
final completions =
|
|
await db.tasksDao.watchCompletionsForTask(taskId).first;
|
|
|
|
expect(completions, isEmpty);
|
|
});
|
|
|
|
test('returns completion after completeTask is called', () async {
|
|
final taskId = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Abspuelen',
|
|
intervalType: IntervalType.daily,
|
|
effortLevel: EffortLevel.low,
|
|
nextDueDate: DateTime(2026, 3, 15),
|
|
),
|
|
);
|
|
|
|
final completionTime = DateTime(2026, 3, 15, 10, 30);
|
|
await db.tasksDao.completeTask(taskId, now: completionTime);
|
|
|
|
final completions =
|
|
await db.tasksDao.watchCompletionsForTask(taskId).first;
|
|
|
|
expect(completions.length, 1);
|
|
expect(completions.first.taskId, taskId);
|
|
expect(completions.first.completedAt, completionTime);
|
|
});
|
|
|
|
test('returns multiple completions in reverse-chronological order', () async {
|
|
final taskId = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Fenster putzen',
|
|
intervalType: IntervalType.monthly,
|
|
effortLevel: EffortLevel.high,
|
|
nextDueDate: DateTime(2026, 1, 1),
|
|
),
|
|
);
|
|
|
|
// Complete multiple times with specific timestamps
|
|
final time1 = DateTime(2026, 1, 10, 9, 0);
|
|
final time2 = DateTime(2026, 2, 12, 14, 30);
|
|
final time3 = DateTime(2026, 3, 15, 8, 0);
|
|
|
|
// Insert out of order to verify ordering is enforced by query
|
|
await db.tasksDao.completeTask(taskId, now: time1);
|
|
await db.tasksDao.completeTask(taskId, now: time2);
|
|
await db.tasksDao.completeTask(taskId, now: time3);
|
|
|
|
final completions =
|
|
await db.tasksDao.watchCompletionsForTask(taskId).first;
|
|
|
|
expect(completions.length, 3);
|
|
// Newest first (reverse-chronological)
|
|
expect(completions[0].completedAt, time3);
|
|
expect(completions[1].completedAt, time2);
|
|
expect(completions[2].completedAt, time1);
|
|
});
|
|
|
|
test('completions for different tasks are isolated', () async {
|
|
final taskId1 = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Task A',
|
|
intervalType: IntervalType.daily,
|
|
effortLevel: EffortLevel.low,
|
|
nextDueDate: DateTime(2026, 3, 15),
|
|
),
|
|
);
|
|
|
|
final taskId2 = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Task B',
|
|
intervalType: IntervalType.daily,
|
|
effortLevel: EffortLevel.low,
|
|
nextDueDate: DateTime(2026, 3, 15),
|
|
),
|
|
);
|
|
|
|
await db.tasksDao.completeTask(taskId1, now: DateTime(2026, 3, 15));
|
|
|
|
final completionsForTask1 =
|
|
await db.tasksDao.watchCompletionsForTask(taskId1).first;
|
|
final completionsForTask2 =
|
|
await db.tasksDao.watchCompletionsForTask(taskId2).first;
|
|
|
|
expect(completionsForTask1.length, 1);
|
|
expect(completionsForTask1.first.taskId, taskId1);
|
|
expect(completionsForTask2, isEmpty);
|
|
});
|
|
|
|
test('stream emits updated list after new completion is added', () async {
|
|
final taskId = await db.tasksDao.insertTask(
|
|
TasksCompanion.insert(
|
|
roomId: roomId,
|
|
name: 'Bodenwischen',
|
|
intervalType: IntervalType.weekly,
|
|
effortLevel: EffortLevel.medium,
|
|
nextDueDate: DateTime(2026, 3, 15),
|
|
),
|
|
);
|
|
|
|
// Collect stream emissions
|
|
final emissions = <List<TaskCompletion>>[];
|
|
final subscription = db.tasksDao
|
|
.watchCompletionsForTask(taskId)
|
|
.listen(emissions.add);
|
|
|
|
// Wait for initial empty emission
|
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
|
expect(emissions.isNotEmpty, isTrue);
|
|
expect(emissions.last, isEmpty);
|
|
|
|
// Complete the task
|
|
await db.tasksDao.completeTask(taskId, now: DateTime(2026, 3, 15, 9, 0));
|
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
|
|
|
expect(emissions.last.length, 1);
|
|
|
|
await subscription.cancel();
|
|
});
|
|
});
|
|
}
|