- 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>
157 lines
4.8 KiB
Dart
157 lines
4.8 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:household_keeper/features/tasks/domain/frequency.dart';
|
|
import 'package:household_keeper/features/tasks/domain/relative_date.dart';
|
|
import 'package:household_keeper/features/tasks/domain/scheduling.dart';
|
|
|
|
void main() {
|
|
group('calculateNextDueDate', () {
|
|
test('daily adds 1 day', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.daily,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 3, 16));
|
|
});
|
|
|
|
test('everyNDays(3) adds 3 days', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.everyNDays,
|
|
intervalDays: 3,
|
|
);
|
|
expect(result, DateTime(2026, 3, 18));
|
|
});
|
|
|
|
test('weekly adds 7 days', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.weekly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 3, 22));
|
|
});
|
|
|
|
test('biweekly adds 14 days', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.biweekly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 3, 29));
|
|
});
|
|
|
|
test('monthly from Jan 15 gives Feb 15', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 1, 15),
|
|
intervalType: IntervalType.monthly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 2, 15));
|
|
});
|
|
|
|
test('monthly from Jan 31 gives Feb 28 (clamping)', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 1, 31),
|
|
intervalType: IntervalType.monthly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 2, 28));
|
|
});
|
|
|
|
test('monthly from Feb 28 (anchor 31) gives Mar 31 (anchor memory)', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 2, 28),
|
|
intervalType: IntervalType.monthly,
|
|
intervalDays: 1,
|
|
anchorDay: 31,
|
|
);
|
|
expect(result, DateTime(2026, 3, 31));
|
|
});
|
|
|
|
test('quarterly from Jan 15 gives Apr 15', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 1, 15),
|
|
intervalType: IntervalType.quarterly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2026, 4, 15));
|
|
});
|
|
|
|
test('yearly from 2026-03-15 gives 2027-03-15', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.yearly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, DateTime(2027, 3, 15));
|
|
});
|
|
|
|
test('everyNMonths(2) adds 2 months', () {
|
|
final result = calculateNextDueDate(
|
|
currentDueDate: DateTime(2026, 1, 15),
|
|
intervalType: IntervalType.everyNMonths,
|
|
intervalDays: 2,
|
|
);
|
|
expect(result, DateTime(2026, 3, 15));
|
|
});
|
|
});
|
|
|
|
group('catchUpToPresent', () {
|
|
test('skips past occurrences to reach today or future', () {
|
|
// Task was due Jan 1, weekly interval. Today is Mar 15.
|
|
// Should advance past all missed weeks to the next week on/after Mar 15.
|
|
final result = catchUpToPresent(
|
|
nextDue: DateTime(2026, 1, 1),
|
|
today: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.weekly,
|
|
intervalDays: 1,
|
|
);
|
|
// Jan 1 + 11 weeks = Mar 19 (first Thursday on/after Mar 15)
|
|
expect(result.isAfter(DateTime(2026, 3, 14)), isTrue);
|
|
expect(result.isBefore(DateTime(2026, 3, 22)), isTrue);
|
|
});
|
|
|
|
test('returns date unchanged if already in future', () {
|
|
final futureDate = DateTime(2026, 4, 1);
|
|
final result = catchUpToPresent(
|
|
nextDue: futureDate,
|
|
today: DateTime(2026, 3, 15),
|
|
intervalType: IntervalType.weekly,
|
|
intervalDays: 1,
|
|
);
|
|
expect(result, futureDate);
|
|
});
|
|
});
|
|
|
|
group('formatRelativeDate', () {
|
|
final today = DateTime(2026, 3, 15);
|
|
|
|
test('today returns "Heute"', () {
|
|
expect(formatRelativeDate(DateTime(2026, 3, 15), today), 'Heute');
|
|
});
|
|
|
|
test('tomorrow returns "Morgen"', () {
|
|
expect(formatRelativeDate(DateTime(2026, 3, 16), today), 'Morgen');
|
|
});
|
|
|
|
test('3 days from now returns "in 3 Tagen"', () {
|
|
expect(formatRelativeDate(DateTime(2026, 3, 18), today), 'in 3 Tagen');
|
|
});
|
|
|
|
test('1 day overdue returns "Uberfaellig seit 1 Tag"', () {
|
|
expect(
|
|
formatRelativeDate(DateTime(2026, 3, 14), today),
|
|
'Uberfaellig seit 1 Tag',
|
|
);
|
|
});
|
|
|
|
test('5 days overdue returns "Uberfaellig seit 5 Tagen"', () {
|
|
expect(
|
|
formatRelativeDate(DateTime(2026, 3, 10), today),
|
|
'Uberfaellig seit 5 Tagen',
|
|
);
|
|
});
|
|
});
|
|
}
|