From 74b3bd5543808ec0f641cb35b1e40cb9dbace594 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 12:28:21 +0100 Subject: [PATCH] test(03-01): add failing tests for DailyPlanDao cross-room query and completion count - 7 failing tests for watchAllTasksWithRoomName and watchCompletionsToday - DAO stub with UnimplementedError methods registered in AppDatabase - TaskWithRoom and DailyPlanState model classes defined Co-Authored-By: Claude Opus 4.6 --- lib/core/database/database.dart | 3 +- lib/core/database/database.g.dart | 1 + lib/features/home/data/daily_plan_dao.dart | 24 +++ lib/features/home/data/daily_plan_dao.g.dart | 25 +++ .../home/domain/daily_plan_models.dart | 31 ++++ .../home/data/daily_plan_dao_test.dart | 167 ++++++++++++++++++ 6 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 lib/features/home/data/daily_plan_dao.dart create mode 100644 lib/features/home/data/daily_plan_dao.g.dart create mode 100644 lib/features/home/domain/daily_plan_models.dart create mode 100644 test/features/home/data/daily_plan_dao_test.dart diff --git a/lib/core/database/database.dart b/lib/core/database/database.dart index 174b899..70ac4a2 100644 --- a/lib/core/database/database.dart +++ b/lib/core/database/database.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; import 'package:path_provider/path_provider.dart'; +import '../../features/home/data/daily_plan_dao.dart'; import '../../features/rooms/data/rooms_dao.dart'; import '../../features/tasks/data/tasks_dao.dart'; import '../../features/tasks/domain/effort_level.dart'; @@ -44,7 +45,7 @@ class TaskCompletions extends Table { @DriftDatabase( tables: [Rooms, Tasks, TaskCompletions], - daos: [RoomsDao, TasksDao], + daos: [RoomsDao, TasksDao, DailyPlanDao], ) class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) diff --git a/lib/core/database/database.g.dart b/lib/core/database/database.g.dart index 8878b41..bc01a7e 100644 --- a/lib/core/database/database.g.dart +++ b/lib/core/database/database.g.dart @@ -1245,6 +1245,7 @@ abstract class _$AppDatabase extends GeneratedDatabase { ); late final RoomsDao roomsDao = RoomsDao(this as AppDatabase); late final TasksDao tasksDao = TasksDao(this as AppDatabase); + late final DailyPlanDao dailyPlanDao = DailyPlanDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); diff --git a/lib/features/home/data/daily_plan_dao.dart b/lib/features/home/data/daily_plan_dao.dart new file mode 100644 index 0000000..a635858 --- /dev/null +++ b/lib/features/home/data/daily_plan_dao.dart @@ -0,0 +1,24 @@ +import 'package:drift/drift.dart'; + +import '../../../core/database/database.dart'; +import '../domain/daily_plan_models.dart'; + +part 'daily_plan_dao.g.dart'; + +@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions]) +class DailyPlanDao extends DatabaseAccessor + with _$DailyPlanDaoMixin { + DailyPlanDao(super.attachedDatabase); + + /// Watch all tasks joined with room name, sorted by nextDueDate ascending. + Stream> watchAllTasksWithRoomName() { + // TODO: implement + throw UnimplementedError(); + } + + /// Count task completions recorded today. + Stream watchCompletionsToday({DateTime? today}) { + // TODO: implement + throw UnimplementedError(); + } +} diff --git a/lib/features/home/data/daily_plan_dao.g.dart b/lib/features/home/data/daily_plan_dao.g.dart new file mode 100644 index 0000000..d5fd498 --- /dev/null +++ b/lib/features/home/data/daily_plan_dao.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_plan_dao.dart'; + +// ignore_for_file: type=lint +mixin _$DailyPlanDaoMixin on DatabaseAccessor { + $RoomsTable get rooms => attachedDatabase.rooms; + $TasksTable get tasks => attachedDatabase.tasks; + $TaskCompletionsTable get taskCompletions => attachedDatabase.taskCompletions; + DailyPlanDaoManager get managers => DailyPlanDaoManager(this); +} + +class DailyPlanDaoManager { + final _$DailyPlanDaoMixin _db; + DailyPlanDaoManager(this._db); + $$RoomsTableTableManager get rooms => + $$RoomsTableTableManager(_db.attachedDatabase, _db.rooms); + $$TasksTableTableManager get tasks => + $$TasksTableTableManager(_db.attachedDatabase, _db.tasks); + $$TaskCompletionsTableTableManager get taskCompletions => + $$TaskCompletionsTableTableManager( + _db.attachedDatabase, + _db.taskCompletions, + ); +} diff --git a/lib/features/home/domain/daily_plan_models.dart b/lib/features/home/domain/daily_plan_models.dart new file mode 100644 index 0000000..fa6a018 --- /dev/null +++ b/lib/features/home/domain/daily_plan_models.dart @@ -0,0 +1,31 @@ +import 'package:household_keeper/core/database/database.dart'; + +/// A task paired with its room for daily plan display. +class TaskWithRoom { + final Task task; + final String roomName; + final int roomId; + + const TaskWithRoom({ + required this.task, + required this.roomName, + required this.roomId, + }); +} + +/// Daily plan data categorized into sections with progress tracking. +class DailyPlanState { + final List overdueTasks; + final List todayTasks; + final List tomorrowTasks; + final int completedTodayCount; + final int totalTodayCount; + + const DailyPlanState({ + required this.overdueTasks, + required this.todayTasks, + required this.tomorrowTasks, + required this.completedTodayCount, + required this.totalTodayCount, + }); +} diff --git a/test/features/home/data/daily_plan_dao_test.dart b/test/features/home/data/daily_plan_dao_test.dart new file mode 100644 index 0000000..5ade837 --- /dev/null +++ b/test/features/home/data/daily_plan_dao_test.dart @@ -0,0 +1,167 @@ +import 'package:drift/drift.dart'; +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('DailyPlanDao.watchAllTasksWithRoomName', () { + test('returns empty list when no tasks exist', () async { + final result = + await db.dailyPlanDao.watchAllTasksWithRoomName().first; + expect(result, isEmpty); + }); + + test('returns tasks with correct room name from join', () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Abspuelen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 16), + )); + + final result = + await db.dailyPlanDao.watchAllTasksWithRoomName().first; + expect(result.length, 1); + expect(result.first.task.name, 'Abspuelen'); + expect(result.first.roomName, 'Kueche'); + expect(result.first.roomId, room1Id); + }); + + test('returns tasks sorted by nextDueDate ascending', () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Later', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 20), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Earlier', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 10), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Middle', + intervalType: IntervalType.biweekly, + effortLevel: EffortLevel.high, + nextDueDate: DateTime(2026, 3, 15), + )); + + final result = + await db.dailyPlanDao.watchAllTasksWithRoomName().first; + expect(result.length, 3); + expect(result[0].task.name, 'Earlier'); + expect(result[1].task.name, 'Middle'); + expect(result[2].task.name, 'Later'); + }); + + test('returns tasks from multiple rooms with correct room name pairing', + () async { + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Kueche Task', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 16), + )); + await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room2Id, + name: 'Bad Task', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 15), + )); + + final result = + await db.dailyPlanDao.watchAllTasksWithRoomName().first; + expect(result.length, 2); + // Sorted by due date: Bad Task (Mar 15) before Kueche Task (Mar 16) + expect(result[0].task.name, 'Bad Task'); + expect(result[0].roomName, 'Badezimmer'); + expect(result[0].roomId, room2Id); + expect(result[1].task.name, 'Kueche Task'); + expect(result[1].roomName, 'Kueche'); + expect(result[1].roomId, room1Id); + }); + }); + + group('DailyPlanDao.watchCompletionsToday', () { + test('returns 0 when no completions exist', () async { + final count = await db.dailyPlanDao + .watchCompletionsToday(today: DateTime(2026, 3, 16)) + .first; + expect(count, 0); + }); + + test('returns correct count of completions recorded today', () async { + final taskId = await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Abspuelen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 16), + )); + + // Complete the task (which records a completion at the given time) + await db.tasksDao.completeTask(taskId, now: DateTime(2026, 3, 16, 10)); + + // Create another task and complete it today too + final taskId2 = await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Staubsaugen', + intervalType: IntervalType.weekly, + effortLevel: EffortLevel.medium, + nextDueDate: DateTime(2026, 3, 16), + )); + await db.tasksDao.completeTask(taskId2, now: DateTime(2026, 3, 16, 14)); + + final count = await db.dailyPlanDao + .watchCompletionsToday(today: DateTime(2026, 3, 16)) + .first; + expect(count, 2); + }); + + test('does not count completions from yesterday', () async { + final taskId = await db.tasksDao.insertTask(TasksCompanion.insert( + roomId: room1Id, + name: 'Abspuelen', + intervalType: IntervalType.daily, + effortLevel: EffortLevel.low, + nextDueDate: DateTime(2026, 3, 15), + )); + + // Complete task yesterday + await db.tasksDao.completeTask(taskId, now: DateTime(2026, 3, 15, 18)); + + // Query for today (March 16) - should not include yesterday's completion + final count = await db.dailyPlanDao + .watchCompletionsToday(today: DateTime(2026, 3, 16)) + .first; + expect(count, 0); + }); + }); +}