Files
HouseHoldKeaper/test/features/tasks/data/tasks_dao_test.dart
Jean-Luc Makiola d2e452655c feat(02-01): Drift tables, DAOs, scheduling utility, domain models with tests
- Add Rooms, Tasks, TaskCompletions Drift tables with schema v2 migration
- Create RoomsDao with CRUD, watchAll, watchWithStats, cascade delete, reorder
- Create TasksDao with CRUD, watchInRoom (sorted by due), completeTask, overdue detection
- Implement calculateNextDueDate and catchUpToPresent pure scheduling functions
- Define IntervalType enum (8 types), EffortLevel enum, FrequencyInterval model
- Add formatRelativeDate German formatter and curatedRoomIcons icon list
- Enable PRAGMA foreign_keys in beforeOpen migration strategy
- All 30 unit tests passing (17 scheduling + 6 rooms DAO + 7 tasks DAO)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:50:12 +01:00

209 lines
6.5 KiB
Dart

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 {
final id = 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);
});
});
}