test(06-01): add failing tests for watchCompletionsForTask DAO method
- Tests cover empty state, single completion, multiple completions in reverse order - Tests cover task isolation (different taskIds do not cross-contaminate) - Tests cover stream reactivity (new completion triggers emission)
This commit is contained in:
158
test/features/tasks/data/task_history_dao_test.dart
Normal file
158
test/features/tasks/data/task_history_dao_test.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:drift/drift.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();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user