import 'package:flutter_test/flutter_test.dart'; import 'package:tasq/models/leave_of_absence.model.dart'; import 'package:tasq/models/pass_slip.model.dart'; import 'package:tasq/models/profile.model.dart'; import 'package:tasq/models/programmer_task.model.dart'; import 'package:tasq/models/programmer_task_activity_log.model.dart'; import 'package:tasq/models/programmer_task_work_log.model.dart'; import 'package:tasq/providers/programmer_day_data_provider.dart'; import 'package:tasq/screens/programmer_tasks/monitor/monitor_model.dart'; import 'package:tasq/utils/app_time.dart'; import 'package:tasq/utils/staff_presence.dart'; void main() { setUpAll(AppTime.initialize); group('HelpEdge', () { test('creates with all fields', () { final edge = HelpEdge( helperId: 'h1', assigneeId: 'a1', taskId: 't1', taskTitle: 'Fix bug', minutes: 30, at: DateTime.utc(2026, 9, 27), ); expect(edge.helperId, 'h1'); expect(edge.assigneeId, 'a1'); expect(edge.taskId, 't1'); expect(edge.taskTitle, 'Fix bug'); expect(edge.minutes, 30); expect(edge.at, DateTime.utc(2026, 9, 27)); }); }); group('buildHelpEdges', () { test('no edges when no work logs', () { final edges = buildHelpEdges( workLogs: [], taskById: {}, day: DateTime.utc(2026, 9, 27), ); expect(edges, isEmpty); }); test('skips when author == assignee (not helping)', () { final task = ProgrammerTask( id: 't1', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'a1', // Same as assignee description: 'work', minutes: 30, createdAt: DateTime.utc(2026, 9, 27, 10, 0), workDate: DateTime.utc(2026, 9, 27), ); final edges = buildHelpEdges( workLogs: [log], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, isEmpty); }); test('skips when minutes is null', () { final task = ProgrammerTask( id: 't1', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'h1', description: 'work', minutes: null, createdAt: DateTime.utc(2026, 9, 27, 10, 0), workDate: DateTime.utc(2026, 9, 27), ); final edges = buildHelpEdges( workLogs: [log], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, isEmpty); }); test('skips when minutes is 0', () { final task = ProgrammerTask( id: 't1', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'h1', description: 'work', minutes: 0, createdAt: DateTime.utc(2026, 9, 27, 10, 0), workDate: DateTime.utc(2026, 9, 27), ); final edges = buildHelpEdges( workLogs: [log], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, isEmpty); }); test('skips when workDate != day', () { final task = ProgrammerTask( id: 't1', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'h1', description: 'work', minutes: 30, createdAt: DateTime.utc(2026, 9, 26, 10, 0), workDate: DateTime.utc(2026, 9, 26), ); final edges = buildHelpEdges( workLogs: [log], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, isEmpty); }); test('creates edge when all conditions met', () { final task = ProgrammerTask( id: 't1', title: 'Fix bug', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'h1', description: 'work', minutes: 30, createdAt: DateTime.utc(2026, 9, 27, 10, 0), workDate: DateTime.utc(2026, 9, 27), ); final edges = buildHelpEdges( workLogs: [log], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, hasLength(1)); expect(edges[0].helperId, 'h1'); expect(edges[0].assigneeId, 'a1'); expect(edges[0].taskId, 't1'); expect(edges[0].taskTitle, 'Fix bug'); expect(edges[0].minutes, 30); }); test('sorts by createdAt newest first', () { final task = ProgrammerTask( id: 't1', title: 'Fix bug', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'a1', ); final log1 = ProgrammerTaskWorkLog( id: 'l1', taskId: 't1', authorId: 'h1', description: 'work', minutes: 30, createdAt: DateTime.utc(2026, 9, 27, 10, 0), workDate: DateTime.utc(2026, 9, 27), ); final log2 = ProgrammerTaskWorkLog( id: 'l2', taskId: 't1', authorId: 'h2', description: 'work', minutes: 20, createdAt: DateTime.utc(2026, 9, 27, 11, 0), workDate: DateTime.utc(2026, 9, 27), ); final edges = buildHelpEdges( workLogs: [log1, log2], taskById: {'t1': task}, day: DateTime.utc(2026, 9, 27), ); expect(edges, hasLength(2)); expect(edges[0].helperId, 'h2'); // Newest first expect(edges[1].helperId, 'h1'); }); }); group('buildMonitorEntries', () { final now = DateTime.utc(2026, 9, 27, 12, 0); final today = DateTime.utc(2026, 9, 27); test('programmer on leave -> onLeave state', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final leave = LeaveOfAbsence( id: 'l1', userId: 'p1', leaveType: 'Sick Leave', justification: 'ill', startTime: DateTime.utc(2026, 9, 27, 8, 0), endTime: DateTime.utc(2026, 9, 27, 17, 0), status: 'approved', filedBy: 'p1', createdAt: DateTime.utc(2026, 9, 27), ); final data = DayActivityData( day: today, tasks: [], logsByTask: {}, workLogs: [], ); final activeLeaves = activeLeaveByUser([leave], now); final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: {}, leaves: activeLeaves, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.onLeave); }); test('programmer with active pass slip -> passSlip state', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final slip = PassSlip( id: 's1', userId: 'p1', dutyScheduleId: 'd1', reason: 'out', status: 'approved', requestedAt: DateTime.utc(2026, 9, 27), approvedBy: 'admin', approvedAt: DateTime.utc(2026, 9, 27), slipStart: DateTime.utc(2026, 9, 27, 13, 0), slipEnd: null, ); final data = DayActivityData( day: today, tasks: [], logsByTask: {}, workLogs: [], ); final activePassSlips = activePassSlipByUser([slip]); final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: {}, leaves: {}, passSlips: activePassSlips, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.passSlip); }); test('programmer with running task -> working state with currentTask', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final task = ProgrammerTask( id: 't1', title: 'Fix bug', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final data = DayActivityData( day: today, tasks: [task], logsByTask: {}, workLogs: [], ); final runStates = { 't1': (state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0)), }; final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: runStates, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.working); expect(entries[0].currentTask, task); }); test('programmer with only paused task -> paused state', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final task = ProgrammerTask( id: 't1', title: 'Fix bug', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final data = DayActivityData( day: today, tasks: [task], logsByTask: {}, workLogs: [], ); final runStates = { 't1': (state: ProgrammerRunState.paused, since: DateTime.utc(2026, 9, 27, 10, 0)), }; final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: runStates, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.paused); }); test('programmer with no running tasks -> idle state', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final data = DayActivityData( day: today, tasks: [], logsByTask: {}, workLogs: [], ); final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: {}, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.idle); expect(entries[0].currentTask, isNull); }); test('leave overrides running task (onLeave state)', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final task = ProgrammerTask( id: 't1', title: 'Fix bug', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final leave = LeaveOfAbsence( id: 'l1', userId: 'p1', leaveType: 'Sick Leave', justification: 'ill', startTime: DateTime.utc(2026, 9, 27, 8, 0), endTime: DateTime.utc(2026, 9, 27, 17, 0), status: 'approved', filedBy: 'p1', createdAt: DateTime.utc(2026, 9, 27), ); final data = DayActivityData( day: today, tasks: [task], logsByTask: {}, workLogs: [], ); final runStates = { 't1': (state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0)), }; final activeLeaves = activeLeaveByUser([leave], now); final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: runStates, leaves: activeLeaves, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(1)); expect(entries[0].state, MonitorState.onLeave); }); test('sort order: working, paused, idle, passSlip, onLeave, then name', () { final p1 = Profile( id: 'p1', role: 'programmer', fullName: 'Zoe', // Should be last after sorting by state ); final p2 = Profile( id: 'p2', role: 'programmer', fullName: 'Alice', ); final p3 = Profile( id: 'p3', role: 'programmer', fullName: 'Bob', ); final p4 = Profile( id: 'p4', role: 'programmer', fullName: 'Charlie', ); final p5 = Profile( id: 'p5', role: 'programmer', fullName: 'Diana', ); // p1: working // p2: paused // p3: idle // p4: passSlip // p5: onLeave final task1 = ProgrammerTask( id: 't1', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final task2 = ProgrammerTask( id: 't2', title: 'Task', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p2', ); final slip = PassSlip( id: 's1', userId: 'p4', dutyScheduleId: 'd1', reason: 'out', status: 'approved', requestedAt: DateTime.utc(2026, 9, 27), approvedBy: 'admin', approvedAt: DateTime.utc(2026, 9, 27), slipStart: DateTime.utc(2026, 9, 27, 13, 0), slipEnd: null, ); final leave = LeaveOfAbsence( id: 'l1', userId: 'p5', leaveType: 'Sick Leave', justification: 'ill', startTime: DateTime.utc(2026, 9, 27, 8, 0), endTime: DateTime.utc(2026, 9, 27, 17, 0), status: 'approved', filedBy: 'p5', createdAt: DateTime.utc(2026, 9, 27), ); final data = DayActivityData( day: today, tasks: [task1, task2], logsByTask: {}, workLogs: [], ); final runStates = { 't1': (state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0)), 't2': (state: ProgrammerRunState.paused, since: DateTime.utc(2026, 9, 27, 11, 0)), }; final activeLeaves = activeLeaveByUser([leave], now); final activePassSlips = activePassSlipByUser([slip]); final entries = buildMonitorEntries( programmers: [p1, p2, p3, p4, p5], today: data, runStates: runStates, leaves: activeLeaves, passSlips: activePassSlips, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(5)); expect(entries[0].programmer.id, 'p1'); // working expect(entries[1].programmer.id, 'p2'); // paused expect(entries[2].programmer.id, 'p3'); // idle expect(entries[3].programmer.id, 'p4'); // passSlip expect(entries[4].programmer.id, 'p5'); // onLeave }); test('within same state, sort alphabetically by fullName', () { final p1 = Profile( id: 'p1', role: 'programmer', fullName: 'Zoe', ); final p2 = Profile( id: 'p2', role: 'programmer', fullName: 'Alice', ); final p3 = Profile( id: 'p3', role: 'programmer', fullName: 'Bob', ); // All idle final data = DayActivityData( day: today, tasks: [], logsByTask: {}, workLogs: [], ); final entries = buildMonitorEntries( programmers: [p1, p2, p3], today: data, runStates: {}, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries, hasLength(3)); expect(entries[0].programmer.fullName, 'Alice'); expect(entries[1].programmer.fullName, 'Bob'); expect(entries[2].programmer.fullName, 'Zoe'); }); test('counts inProgress and paused and notStarted tasks', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); // 2 running, 1 paused, 1 queued final t1 = ProgrammerTask( id: 't1', title: 'Task 1', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final t2 = ProgrammerTask( id: 't2', title: 'Task 2', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final t3 = ProgrammerTask( id: 't3', title: 'Task 3', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final t4 = ProgrammerTask( id: 't4', title: 'Task 4', category: 'Bug Fix', status: 'queued', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final data = DayActivityData( day: today, tasks: [t1, t2, t3, t4], logsByTask: {}, workLogs: [], ); final runStates = { 't1': (state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0)), 't2': (state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0)), 't3': (state: ProgrammerRunState.paused, since: DateTime.utc(2026, 9, 27, 11, 0)), 't4': (state: ProgrammerRunState.notStarted, since: null), }; final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: runStates, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries[0].inProgress, 2); expect(entries[0].paused, 1); expect(entries[0].notStarted, 1); }); test('currentTask is oldest running task', () { final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final t1 = ProgrammerTask( id: 't1', title: 'Task 1', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final t2 = ProgrammerTask( id: 't2', title: 'Task 2', category: 'Bug Fix', status: 'in_progress', createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), assigneeId: 'p1', ); final data = DayActivityData( day: today, tasks: [t1, t2], logsByTask: {}, workLogs: [], ); final runStates = { 't1': ( state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 10, 0), ), // Earlier 't2': ( state: ProgrammerRunState.running, since: DateTime.utc(2026, 9, 27, 11, 0), ), // Later }; final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: runStates, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries[0].currentTask?.id, 't1'); // Oldest running }); test('todayTotal equals totalSeconds for all programmer tasks', () { // This test verifies that todayTotal matches the expected value // For now, we just verify the structure is correct final programmer = Profile( id: 'p1', role: 'programmer', fullName: 'Alice', ); final data = DayActivityData( day: today, tasks: [], logsByTask: {}, workLogs: [], ); final entries = buildMonitorEntries( programmers: [programmer], today: data, runStates: {}, leaves: {}, passSlips: {}, sheetStatusByUser: {}, projectNames: {}, profileNames: {}, now: now, ); expect(entries[0].todayTotal, isA()); expect(entries[0].currentTaskToday, isA()); }); }); }