import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tasq/models/profile.model.dart'; import 'package:tasq/models/programmer_task.model.dart'; import 'package:tasq/providers/programmer_day_data_provider.dart'; import 'package:tasq/screens/programmer_tasks/dashboard/task_sections.dart'; import 'package:tasq/utils/app_time.dart'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- ProgrammerTask _makeTask({ required String id, required String status, String assigneeId = 'user1', DateTime? completedAt, DateTime? cancelledAt, }) { final now = DateTime(2026, 1, 1); return ProgrammerTask( id: id, title: 'Task $id', category: ProgrammerTaskCategory.bugFix, status: status, assigneeId: assigneeId, createdAt: now, updatedAt: now, completedAt: completedAt, cancelledAt: cancelledAt, ); } Widget _buildSections(TaskSectionGroups groups, {bool readOnly = false}) { return ProviderScope( overrides: [ programmerRunStatesProvider.overrideWithValue({}), ], child: MaterialApp( home: Scaffold( body: SingleChildScrollView( child: TaskSections( groups: groups, profileById: const {}, readOnly: readOnly, ), ), ), ), ); } // --------------------------------------------------------------------------- // Unit tests // --------------------------------------------------------------------------- void main() { setUpAll(AppTime.initialize); test('groupTasksForDashboard splits by run state and excludes other assignees', () { final tasks = [ _makeTask(id: 't1', status: ProgrammerTaskStatus.inProgress, assigneeId: 'user1'), _makeTask(id: 't2', status: ProgrammerTaskStatus.inProgress, assigneeId: 'user1'), _makeTask(id: 't3', status: ProgrammerTaskStatus.queued, assigneeId: 'user1'), _makeTask(id: 't4', status: ProgrammerTaskStatus.queued, assigneeId: 'other'), ]; final runStates = { 't1': (state: ProgrammerRunState.running, since: null), 't2': (state: ProgrammerRunState.paused, since: null), 't3': (state: ProgrammerRunState.notStarted, since: null), 't4': (state: ProgrammerRunState.notStarted, since: null), }; final groups = groupTasksForDashboard(tasks, runStates, 'user1'); expect(groups.running.map((t) => t.id), ['t1']); expect(groups.paused.map((t) => t.id), ['t2']); expect(groups.notStarted.map((t) => t.id), ['t3']); // t4 belongs to 'other' — must not appear expect(groups.running.any((t) => t.id == 't4'), isFalse); expect(groups.paused.any((t) => t.id == 't4'), isFalse); expect(groups.notStarted.any((t) => t.id == 't4'), isFalse); }); test('completed sorted by completedAt desc, cancelled by cancelledAt desc', () { final base = DateTime(2026, 1, 1); final tasks = [ _makeTask( id: 'c1', status: ProgrammerTaskStatus.completed, completedAt: base.add(const Duration(hours: 1)), ), _makeTask( id: 'c2', status: ProgrammerTaskStatus.completed, completedAt: base.add(const Duration(hours: 3)), ), _makeTask( id: 'x1', status: ProgrammerTaskStatus.cancelled, cancelledAt: base.add(const Duration(hours: 2)), ), _makeTask( id: 'x2', status: ProgrammerTaskStatus.cancelled, cancelledAt: base.add(const Duration(hours: 5)), ), ]; final runStates = { 'c1': (state: ProgrammerRunState.completed, since: null), 'c2': (state: ProgrammerRunState.completed, since: null), 'x1': (state: ProgrammerRunState.cancelled, since: null), 'x2': (state: ProgrammerRunState.cancelled, since: null), }; final groups = groupTasksForDashboard(tasks, runStates, 'user1'); expect(groups.completed.map((t) => t.id).toList(), ['c2', 'c1']); expect(groups.cancelled.map((t) => t.id).toList(), ['x2', 'x1']); }); // --------------------------------------------------------------------------- // Widget tests // --------------------------------------------------------------------------- testWidgets('Completed and Cancelled start collapsed', (tester) async { final base = DateTime(2026, 1, 1); final completed = List.generate( 12, (i) => _makeTask( id: 'c$i', status: ProgrammerTaskStatus.completed, completedAt: base.add(Duration(hours: i)), ), ); final runStates = { for (final t in completed) t.id: (state: ProgrammerRunState.completed, since: null), }; final groups = groupTasksForDashboard(completed, runStates, 'user1'); await tester.pumpWidget(_buildSections(groups)); await tester.pump(); // Header is visible expect(find.textContaining('COMPLETED'), findsOneWidget); // First task title is NOT visible (collapsed) expect(find.text('Task c0'), findsNothing); // Tap the header to expand await tester.tap(find.textContaining('COMPLETED')); await tester.pump(); // 10 titles visible (first 10 of 12) int visibleCount = 0; for (int i = 0; i < 12; i++) { if (tester.any(find.text('Task c${11 - i}'))) visibleCount++; } expect(visibleCount, 10); // "Show all (12)" link is visible expect(find.text('Show all (12)'), findsOneWidget); }); testWidgets('empty sections are hidden', (tester) async { final tasks = [ _makeTask(id: 'r1', status: ProgrammerTaskStatus.inProgress), ]; final runStates = { 'r1': (state: ProgrammerRunState.running, since: null), }; final groups = groupTasksForDashboard(tasks, runStates, 'user1'); await tester.pumpWidget(_buildSections(groups)); await tester.pump(); // 'In progress' header present (at least one widget contains the text) expect(find.textContaining('IN PROGRESS'), findsAtLeastNWidgets(1)); // 'Cancelled' header not rendered (no cancelled tasks) expect(find.textContaining('CANCELLED'), findsNothing); // 'Paused' header not rendered expect(find.textContaining('PAUSED'), findsNothing); }); }