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()); // Create a room for task tests roomId = await db.roomsDao.insertRoom( RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'), ); }); tearDown(() async { await db.close(); }); group('TasksDao', () { test('insertTask returns a valid id', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); expect(id, greaterThan(0)); }); test('watchTasksInRoom emits tasks sorted by nextDueDate ascending', () async { // Insert tasks with different due dates (out of order) await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Later Task', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 20), ), ); await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Early Task', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 10), ), ); await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Middle Task', intervalType: IntervalType.biweekly, effortLevel: EffortLevel.high, nextDueDate: DateTime(2026, 3, 15), ), ); final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; expect(tasks.length, 3); expect(tasks[0].name, 'Early Task'); expect(tasks[1].name, 'Middle Task'); expect(tasks[2].name, 'Later Task'); }); test('updateTask changes name, description, interval, effort', () async { await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); // Get the task, modify it, and update final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; final task = tasks.first; final updated = task.copyWith( name: 'Geschirr spuelen', description: Value('Mit Spuelmittel'), intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, ); await db.tasksDao.updateTask(updated); final result = await db.tasksDao.watchTasksInRoom(roomId).first; expect(result.first.name, 'Geschirr spuelen'); expect(result.first.description, 'Mit Spuelmittel'); expect(result.first.intervalType, IntervalType.weekly); expect(result.first.effortLevel, EffortLevel.medium); }); test('deleteTask removes the task', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); await db.tasksDao.deleteTask(id); final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; expect(tasks, isEmpty); }); test('completeTask records completion and updates nextDueDate', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15)); // Check that the next due date was updated (daily: +1 day) final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; expect(tasks.first.nextDueDate, DateTime(2026, 3, 16)); }); test('completeTask with overdue task catches up to present', () async { // Task was due 2 weeks ago with weekly interval final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Staubsaugen', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 1), ), ); // Complete on March 15 await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15)); // Should catch up: Mar 1 -> Mar 8 (still past) -> Mar 15 (equals today, done) final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; final nextDue = tasks.first.nextDueDate; // Next due should be on or after Mar 15 expect( nextDue.isAfter(DateTime(2026, 3, 14)) || nextDue.isAtSameMomentAs(DateTime(2026, 3, 15)), isTrue, ); }); test('tasks with nextDueDate before today are detected as overdue', () async { // Insert an overdue task await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Overdue Task', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 10), ), ); // Insert a task due today (not overdue) await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Today Task', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); // Insert a future task (not overdue) await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Future Task', intervalType: IntervalType.monthly, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 20), ), ); final overdueCount = await db.tasksDao.getOverdueTaskCount( roomId, today: DateTime(2026, 3, 15), ); // Only the task due Mar 10 is overdue (before Mar 15) expect(overdueCount, 1); }); test('softDeleteTask sets isActive to false without removing the task', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); await db.tasksDao.softDeleteTask(id); // Task should still exist in DB but isActive == false final allTasks = await (db.select(db.tasks)).get(); expect(allTasks.length, 1); expect(allTasks.first.isActive, isFalse); }); test('getCompletionCount returns 0 for task with no completions', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); final count = await db.tasksDao.getCompletionCount(id); expect(count, 0); }); test('getCompletionCount returns correct count after completions', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 13), ), ); await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 13)); await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 14)); await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15)); final count = await db.tasksDao.getCompletionCount(id); expect(count, 3); }); test('watchTasksInRoom excludes soft-deleted (isActive=false) tasks', () async { final activeId = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Active Task', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 15), ), ); final inactiveId = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Inactive Task', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 10), ), ); await db.tasksDao.softDeleteTask(inactiveId); final tasks = await db.tasksDao.watchTasksInRoom(roomId).first; expect(tasks.length, 1); expect(tasks.first.id, activeId); expect(tasks.first.name, 'Active Task'); }); test('getOverdueTaskCount excludes soft-deleted (isActive=false) tasks', () async { // Active overdue task await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Active Overdue', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 10), ), ); // Inactive overdue task final inactiveId = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Inactive Overdue', intervalType: IntervalType.weekly, effortLevel: EffortLevel.medium, nextDueDate: DateTime(2026, 3, 5), ), ); await db.tasksDao.softDeleteTask(inactiveId); final overdueCount = await db.tasksDao.getOverdueTaskCount( roomId, today: DateTime(2026, 3, 15), ); expect(overdueCount, 1); }); test('hard deleteTask still removes task and its completions', () async { final id = await db.tasksDao.insertTask( TasksCompanion.insert( roomId: roomId, name: 'Abspuelen', intervalType: IntervalType.daily, effortLevel: EffortLevel.low, nextDueDate: DateTime(2026, 3, 13), ), ); await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 13)); await db.tasksDao.deleteTask(id); final tasks = await (db.select(db.tasks)).get(); final completions = await (db.select(db.taskCompletions)).get(); expect(tasks, isEmpty); expect(completions, isEmpty); }); }); }