feat(11-02): add DAO queries, update CalendarDayState, implement pre-population provider
- Add watchAllActiveRecurringTasks() and watchAllActiveRecurringTasksInRoom() to CalendarDao - Add watchCompletionsInRange() for period-completion filtering - Extend CalendarDayState with prePopulatedTasks field (default empty, backward compat) - Update isEmpty getter to include prePopulatedTasks.isEmpty - Add _isInCurrentIntervalWindow() and _calculatePreviousDueDate() helpers - Rewrite calendarDayProvider and roomCalendarDayProvider with pre-population logic - Fix _subtractMonths() year-boundary bug using total-month arithmetic - Add 9 new DAO tests for watchAllActiveRecurringTasks, watchAllActiveRecurringTasksInRoom, watchCompletionsInRange
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:household_keeper/core/database/database.dart';
|
||||
@@ -484,6 +485,225 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('CalendarDao.watchAllActiveRecurringTasks', () {
|
||||
test('returns all active tasks', () async {
|
||||
// Insert 2 active tasks and 1 inactive task
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Aktive Aufgabe 1',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 20),
|
||||
));
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room2Id,
|
||||
name: 'Aktive Aufgabe 2',
|
||||
intervalType: IntervalType.monthly,
|
||||
effortLevel: EffortLevel.medium,
|
||||
nextDueDate: DateTime(2026, 3, 25),
|
||||
));
|
||||
// Insert inactive task (isActive defaults to true; manually set false via update)
|
||||
final inactiveId = await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Inaktive Aufgabe',
|
||||
intervalType: IntervalType.daily,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 16),
|
||||
));
|
||||
await (db.update(db.tasks)..where((t) => t.id.equals(inactiveId)))
|
||||
.write(const TasksCompanion(isActive: Value(false)));
|
||||
|
||||
final result =
|
||||
await db.calendarDao.watchAllActiveRecurringTasks().first;
|
||||
expect(result.length, 2);
|
||||
final names = result.map((t) => t.task.name).toList();
|
||||
expect(names, contains('Aktive Aufgabe 1'));
|
||||
expect(names, contains('Aktive Aufgabe 2'));
|
||||
expect(names, isNot(contains('Inaktive Aufgabe')));
|
||||
});
|
||||
|
||||
test('returns tasks sorted alphabetically by name', () async {
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Zuletzt',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 20),
|
||||
));
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room2Id,
|
||||
name: 'Als erstes',
|
||||
intervalType: IntervalType.monthly,
|
||||
effortLevel: EffortLevel.medium,
|
||||
nextDueDate: DateTime(2026, 3, 25),
|
||||
));
|
||||
|
||||
final result =
|
||||
await db.calendarDao.watchAllActiveRecurringTasks().first;
|
||||
expect(result.length, 2);
|
||||
expect(result[0].task.name, 'Als erstes');
|
||||
expect(result[1].task.name, 'Zuletzt');
|
||||
});
|
||||
|
||||
test('returns empty list when no active tasks exist', () async {
|
||||
final result =
|
||||
await db.calendarDao.watchAllActiveRecurringTasks().first;
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('CalendarDao.watchAllActiveRecurringTasksInRoom', () {
|
||||
test('filters tasks by room', () async {
|
||||
// Tasks in room1
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Kueche Aufgabe',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 20),
|
||||
));
|
||||
// Task in room2 (should NOT appear for room1)
|
||||
await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room2Id,
|
||||
name: 'Bad Aufgabe',
|
||||
intervalType: IntervalType.monthly,
|
||||
effortLevel: EffortLevel.medium,
|
||||
nextDueDate: DateTime(2026, 3, 25),
|
||||
));
|
||||
|
||||
final result =
|
||||
await db.calendarDao.watchAllActiveRecurringTasksInRoom(room1Id).first;
|
||||
expect(result.length, 1);
|
||||
expect(result.first.task.name, 'Kueche Aufgabe');
|
||||
expect(result.first.roomId, room1Id);
|
||||
});
|
||||
|
||||
test('returns empty list when room has no active tasks', () async {
|
||||
final result =
|
||||
await db.calendarDao.watchAllActiveRecurringTasksInRoom(room1Id).first;
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('CalendarDao.watchCompletionsInRange', () {
|
||||
test('returns completions within date range', () async {
|
||||
final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Woechentliche Aufgabe',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 23),
|
||||
));
|
||||
|
||||
// Completion within range (March 18)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 18, 10),
|
||||
));
|
||||
// Completion within range (March 20)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 20, 14),
|
||||
));
|
||||
// Completion OUTSIDE range (before start — March 15)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 15),
|
||||
));
|
||||
// Completion OUTSIDE range (after end — March 25)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 25),
|
||||
));
|
||||
|
||||
// Range: March 16 (inclusive) to March 24 (exclusive)
|
||||
final result = await db.calendarDao
|
||||
.watchCompletionsInRange(
|
||||
taskId,
|
||||
DateTime(2026, 3, 16),
|
||||
DateTime(2026, 3, 24),
|
||||
)
|
||||
.first;
|
||||
expect(result.length, 2);
|
||||
});
|
||||
|
||||
test('returns empty list when no completions in range', () async {
|
||||
final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Keine Completions',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 23),
|
||||
));
|
||||
|
||||
final result = await db.calendarDao
|
||||
.watchCompletionsInRange(
|
||||
taskId,
|
||||
DateTime(2026, 3, 16),
|
||||
DateTime(2026, 3, 24),
|
||||
)
|
||||
.first;
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('returns empty list for wrong taskId', () async {
|
||||
final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Aufgabe mit Completion',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 23),
|
||||
));
|
||||
// Insert completion for taskId
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 18),
|
||||
));
|
||||
|
||||
// Query for a different task ID
|
||||
final result = await db.calendarDao
|
||||
.watchCompletionsInRange(
|
||||
taskId + 999,
|
||||
DateTime(2026, 3, 16),
|
||||
DateTime(2026, 3, 24),
|
||||
)
|
||||
.first;
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('start is inclusive, end is exclusive', () async {
|
||||
final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: room1Id,
|
||||
name: 'Grenzen Test',
|
||||
intervalType: IntervalType.weekly,
|
||||
effortLevel: EffortLevel.low,
|
||||
nextDueDate: DateTime(2026, 3, 23),
|
||||
));
|
||||
|
||||
// Completion exactly at start boundary (inclusive)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 16),
|
||||
));
|
||||
// Completion exactly at end boundary (exclusive — should NOT be included)
|
||||
await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
|
||||
taskId: taskId,
|
||||
completedAt: DateTime(2026, 3, 24),
|
||||
));
|
||||
|
||||
final result = await db.calendarDao
|
||||
.watchCompletionsInRange(
|
||||
taskId,
|
||||
DateTime(2026, 3, 16),
|
||||
DateTime(2026, 3, 24),
|
||||
)
|
||||
.first;
|
||||
// Only the start-boundary completion should be included
|
||||
expect(result.length, 1);
|
||||
expect(result.first.completedAt, DateTime(2026, 3, 16));
|
||||
});
|
||||
});
|
||||
|
||||
group('CalendarDao.getTaskCountInRoom', () {
|
||||
test('returns 0 when room has no tasks', () async {
|
||||
final count = await db.calendarDao.getTaskCountInRoom(room1Id);
|
||||
|
||||
Reference in New Issue
Block a user