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 = >[]; final subscription = db.tasksDao .watchCompletionsForTask(taskId) .listen(emissions.add); // Wait for initial empty emission await Future.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.delayed(const Duration(milliseconds: 50)); expect(emissions.last.length, 1); await subscription.cancel(); }); }); }