diff --git a/test/features/home/data/calendar_dao_test.dart b/test/features/home/data/calendar_dao_test.dart new file mode 100644 index 0000000..d830e99 --- /dev/null +++ b/test/features/home/data/calendar_dao_test.dart @@ -0,0 +1,286 @@ +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 room1Id; + late int room2Id; + + setUp(() async { + db = AppDatabase(NativeDatabase.memory()); + room1Id = await db.roomsDao.insertRoom( + RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'), + ); + room2Id = await db.roomsDao.insertRoom( + RoomsCompanion.insert(name: 'Badezimmer', iconName: 'bathroom'), + ); + }); + + tearDown(() async { + await db.close(); + }); + + group('CalendarDao.watchTasksForDate', () { + test('returns empty list when no tasks exist', () async { + final result = await db.calendarDao + .watchTasksForDate(DateTime(2026, 3, 16)) + .first; + expect(result, isEmpty); + }); + + test('returns only tasks due on the queried date', () async { + // Task due on March 16 + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Abspuelen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 16, 9, 30), + )); + // Task due on March 15 (should NOT appear) + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Staubsaugen', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 15), + )); + // Task due on March 17 (should NOT appear) + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Fenster putzen', + intervalType: IntervalType.monthly, + effortLevel: EffortLevel.high, + nextDueDate: DateTime(2026, 3, 17), + )); + + final result = await db.calendarDao + .watchTasksForDate(DateTime(2026, 3, 16)) + .first; + expect(result.length, 1); + expect(result.first.task.name, 'Abspuelen'); + }); + + test('returns tasks from multiple rooms with correct room pairing', + () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Kueche Aufgabe', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 20), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room2Id, + name: 'Bad Aufgabe', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 20, 14), + )); + + final result = await db.calendarDao + .watchTasksForDate(DateTime(2026, 3, 20)) + .first; + expect(result.length, 2); + + final names = result.map((t) => t.task.name).toList(); + expect(names, contains('Kueche Aufgabe')); + expect(names, contains('Bad Aufgabe')); + + final kuecheTask = + result.firstWhere((t) => t.task.name == 'Kueche Aufgabe'); + expect(kuecheTask.roomName, 'Kueche'); + expect(kuecheTask.roomId, room1Id); + + final badTask = + result.firstWhere((t) => t.task.name == 'Bad Aufgabe'); + expect(badTask.roomName, 'Badezimmer'); + expect(badTask.roomId, room2Id); + }); + + test('returns tasks sorted alphabetically by name', () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Zitrone putzen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 18), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Abspuelen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 18, 10), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room2Id, + name: 'Moppen', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 18, 8), + )); + + final result = await db.calendarDao + .watchTasksForDate(DateTime(2026, 3, 18)) + .first; + expect(result.length, 3); + expect(result[0].task.name, 'Abspuelen'); + expect(result[1].task.name, 'Moppen'); + expect(result[2].task.name, 'Zitrone putzen'); + }); + + test('does NOT include overdue carry-over for past dates', () async { + // Task due on March 10 (overdue relative to March 16) + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Alte Aufgabe', + intervalType: IntervalType.monthly, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 10), + )); + // Task due on March 15 (queried date) + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Richtige Aufgabe', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 15), + )); + + // Querying March 15 should only return the task due on March 15 + final result = await db.calendarDao + .watchTasksForDate(DateTime(2026, 3, 15)) + .first; + expect(result.length, 1); + expect(result.first.task.name, 'Richtige Aufgabe'); + }); + }); + + group('CalendarDao.watchOverdueTasks', () { + test('returns empty list when no overdue tasks exist', () async { + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result, isEmpty); + }); + + test('returns tasks whose nextDueDate is before referenceDate', () async { + // Task due March 15 — overdue relative to March 16 + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Ueberfaelliges Task', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 15), + )); + // Task due March 10 — also overdue + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Sehr altes Task', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 10), + )); + + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result.length, 2); + }); + + test('does NOT include tasks due ON the referenceDate', () async { + // Task due exactly on reference date (March 16) — should NOT appear + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Heutiges Task', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 16), + )); + // Task due yesterday (March 15) — should appear + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Gestriges Task', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 15), + )); + + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result.length, 1); + expect(result.first.task.name, 'Gestriges Task'); + }); + + test('does NOT include tasks due in the future', () async { + // Future task — should NOT appear + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Zukuenftiges Task', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 20), + )); + + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result, isEmpty); + }); + + test('returns overdue tasks sorted by nextDueDate ascending', () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Neues Overdue', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 15), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Altes Overdue', + intervalType: IntervalType.monthly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 1), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room2Id, + name: 'Mittleres Overdue', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 10), + )); + + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result.length, 3); + expect(result[0].task.name, 'Altes Overdue'); + expect(result[1].task.name, 'Mittleres Overdue'); + expect(result[2].task.name, 'Neues Overdue'); + }); + + test('returns overdue task with correct room pairing', () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room2Id, + name: 'Bad Overdue', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 14), + )); + + final result = await db.calendarDao + .watchOverdueTasks(DateTime(2026, 3, 16)) + .first; + expect(result.length, 1); + expect(result.first.task.name, 'Bad Overdue'); + expect(result.first.roomName, 'Badezimmer'); + expect(result.first.roomId, room2Id); + }); + }); +}