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); }); }); }