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 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.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/rooms/data/rooms_dao.dart';
|
||||||
import '../../features/tasks/data/tasks_dao.dart';
|
import '../../features/tasks/data/tasks_dao.dart';
|
||||||
import '../../features/tasks/domain/effort_level.dart';
|
import '../../features/tasks/domain/effort_level.dart';
|
||||||
@@ -44,7 +45,7 @@ class TaskCompletions extends Table {
|
|||||||
|
|
||||||
@DriftDatabase(
|
@DriftDatabase(
|
||||||
tables: [Rooms, Tasks, TaskCompletions],
|
tables: [Rooms, Tasks, TaskCompletions],
|
||||||
daos: [RoomsDao, TasksDao],
|
daos: [RoomsDao, TasksDao, DailyPlanDao],
|
||||||
)
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase([QueryExecutor? executor])
|
AppDatabase([QueryExecutor? executor])
|
||||||
|
|||||||
@@ -1245,6 +1245,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
);
|
);
|
||||||
late final RoomsDao roomsDao = RoomsDao(this as AppDatabase);
|
late final RoomsDao roomsDao = RoomsDao(this as AppDatabase);
|
||||||
late final TasksDao tasksDao = TasksDao(this as AppDatabase);
|
late final TasksDao tasksDao = TasksDao(this as AppDatabase);
|
||||||
|
late final DailyPlanDao dailyPlanDao = DailyPlanDao(this as AppDatabase);
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
|
|||||||
24
lib/features/home/data/daily_plan_dao.dart
Normal file
24
lib/features/home/data/daily_plan_dao.dart
Normal file
@@ -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<AppDatabase>
|
||||||
|
with _$DailyPlanDaoMixin {
|
||||||
|
DailyPlanDao(super.attachedDatabase);
|
||||||
|
|
||||||
|
/// Watch all tasks joined with room name, sorted by nextDueDate ascending.
|
||||||
|
Stream<List<TaskWithRoom>> watchAllTasksWithRoomName() {
|
||||||
|
// TODO: implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count task completions recorded today.
|
||||||
|
Stream<int> watchCompletionsToday({DateTime? today}) {
|
||||||
|
// TODO: implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/features/home/data/daily_plan_dao.g.dart
Normal file
25
lib/features/home/data/daily_plan_dao.g.dart
Normal file
@@ -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<AppDatabase> {
|
||||||
|
$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,
|
||||||
|
);
|
||||||
|
}
|
||||||
31
lib/features/home/domain/daily_plan_models.dart
Normal file
31
lib/features/home/domain/daily_plan_models.dart
Normal file
@@ -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<TaskWithRoom> overdueTasks;
|
||||||
|
final List<TaskWithRoom> todayTasks;
|
||||||
|
final List<TaskWithRoom> tomorrowTasks;
|
||||||
|
final int completedTodayCount;
|
||||||
|
final int totalTodayCount;
|
||||||
|
|
||||||
|
const DailyPlanState({
|
||||||
|
required this.overdueTasks,
|
||||||
|
required this.todayTasks,
|
||||||
|
required this.tomorrowTasks,
|
||||||
|
required this.completedTodayCount,
|
||||||
|
required this.totalTodayCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
167
test/features/home/data/daily_plan_dao_test.dart
Normal file
167
test/features/home/data/daily_plan_dao_test.dart
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user