import 'package:adhd_scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { group('Child task ownership', () { final now = DateTime(2026, 6, 19, 12); test('child references parent through parentTaskId', () { final parent = task(id: 'parent', title: 'Clean kitchen', createdAt: now); final child = task( id: 'child', title: 'Clear sink', createdAt: now, parentTaskId: parent.id, ); final view = ChildTaskView(tasks: [parent, child]); expect(child.parentTaskId, parent.id); expect(view.isChildOf(child: child, parent: parent), isTrue); expect(view.parentOf(child), same(parent)); }); test('parent can query and aggregate direct children', () { final parent = task(id: 'parent', title: 'Clean kitchen', createdAt: now); final plannedChild = task( id: 'planned-child', title: 'Clear sink', createdAt: now, parentTaskId: parent.id, ); final completedChild = task( id: 'completed-child', title: 'Wipe counter', createdAt: now, status: TaskStatus.completed, parentTaskId: parent.id, ); final unrelatedChild = task( id: 'unrelated-child', title: 'Sort drawer', createdAt: now, parentTaskId: 'other-parent', ); final view = ChildTaskView( tasks: [parent, plannedChild, unrelatedChild, completedChild], ); expect(view.childrenOf(parent).map((child) => child.id), [ 'planned-child', 'completed-child', ]); final summary = view.summaryFor(parent); expect(summary.totalCount, 2); expect(summary.plannedCount, 1); expect(summary.completedCount, 1); expect(summary.allChildrenCompleted, isFalse); }); test('parent can remain incomplete while children are planned or completed', () { final parent = task(id: 'parent', title: 'Clean kitchen', createdAt: now); final completedChild = task( id: 'completed-child', title: 'Clear sink', createdAt: now, status: TaskStatus.completed, parentTaskId: parent.id, ); final view = ChildTaskView(tasks: [parent, completedChild]); expect(parent.status, TaskStatus.planned); expect(view.summaryFor(parent).allChildrenCompleted, isTrue); expect(view.parentShouldAutoComplete(parent), isFalse); }); test('child ownership does not block normal scheduling behavior', () { const engine = SchedulingEngine(); final parent = task( id: 'parent', title: 'Clean kitchen', createdAt: now, status: TaskStatus.backlog, ); final child = task( id: 'child', title: 'Clear sink', createdAt: now, status: TaskStatus.backlog, durationMinutes: 15, parentTaskId: parent.id, ); final result = engine.insertBacklogTaskIntoNextAvailableSlot( input: SchedulingInput( tasks: [parent, child], window: SchedulingWindow( start: DateTime(2026, 6, 19, 9), end: DateTime(2026, 6, 19, 10), ), ), taskId: child.id, updatedAt: now, ); final scheduledChild = taskById(result.tasks, child.id); final unchangedParent = taskById(result.tasks, parent.id); expect(scheduledChild.status, TaskStatus.planned); expect(scheduledChild.parentTaskId, parent.id); expect(scheduledChild.scheduledStart, DateTime(2026, 6, 19, 9)); expect(unchangedParent.status, TaskStatus.backlog); expect(unchangedParent.scheduledStart, isNull); }); }); group('Child task entry defaults', () { final now = DateTime(2026, 6, 19, 12); test('child creation supports explicit row fields', () { final parent = task( id: 'parent', title: 'Clean kitchen', createdAt: now, ).copyWith(projectId: 'home'); final child = ChildTaskEntry( id: 'child', title: ' Clear sink ', createdAt: now, priority: PriorityLevel.high, reward: RewardLevel.high, difficulty: DifficultyLevel.hard, durationMinutes: 20, ).toTask(parent: parent); expect(child.id, 'child'); expect(child.title, 'Clear sink'); expect(child.parentTaskId, parent.id); expect(child.projectId, parent.projectId); expect(child.priority, PriorityLevel.high); expect(child.reward, RewardLevel.high); expect(child.difficulty, DifficultyLevel.hard); expect(child.durationMinutes, 20); expect(child.status, TaskStatus.backlog); }); test('child inherits parent project unless project override is supplied', () { final parent = task( id: 'parent', title: 'Plan trip', createdAt: now, ).copyWith(projectId: 'travel'); final inherited = ChildTaskEntry( id: 'inherited', title: 'Book train', createdAt: now, ).toTask(parent: parent); final overridden = ChildTaskEntry( id: 'overridden', title: 'Ask partner', createdAt: now, projectId: 'relationships', ).toTask(parent: parent); expect(inherited.projectId, 'travel'); expect(overridden.projectId, 'relationships'); }); test('missing reward remains not set and distinct from very low reward', () { final parent = task(id: 'parent', title: 'Clean kitchen', createdAt: now); final noReward = ChildTaskEntry( id: 'no-reward', title: 'Clear sink', createdAt: now, ).toTask(parent: parent); final veryLowReward = ChildTaskEntry( id: 'very-low', title: 'Wipe counter', createdAt: now, reward: RewardLevel.veryLow, ).toTask(parent: parent); expect(noReward.reward, RewardLevel.notSet); expect(veryLowReward.reward, RewardLevel.veryLow); expect(noReward.reward, isNot(veryLowReward.reward)); }); test('children without priority preserve row insertion order', () { final parent = task(id: 'parent', title: 'Clean kitchen', createdAt: now); final first = ChildTaskEntry( id: 'first', title: 'Clear sink', createdAt: now, ).toTask(parent: parent); final second = ChildTaskEntry( id: 'second', title: 'Wipe counter', createdAt: now, ).toTask(parent: parent); final third = ChildTaskEntry( id: 'third', title: 'Sweep floor', createdAt: now, ).toTask(parent: parent); final view = ChildTaskView(tasks: [parent, first, second, third]); expect(first.priority, isNull); expect(second.priority, isNull); expect(third.priority, isNull); expect(view.childrenOf(parent).map((child) => child.id), [ 'first', 'second', 'third', ]); }); }); } Task task({ required String id, required String title, required DateTime createdAt, TaskStatus status = TaskStatus.planned, int? durationMinutes, String? parentTaskId, }) { return Task( id: id, title: title, projectId: 'home', type: TaskType.flexible, status: status, priority: PriorityLevel.medium, durationMinutes: durationMinutes, parentTaskId: parentTaskId, createdAt: createdAt, updatedAt: createdAt, ); } Task taskById(List tasks, String id) { return tasks.singleWhere((task) => task.id == id); }