29bcbcaa4d
Replace the Mine tab flat list with ProgrammerDashboard — a NowWorkingCard hero (running task with Pause/Complete, or idle with nextUp Start), a TODO stub for Task 8 stat tiles/badges, and collapsible TaskSections (In progress, Paused, Not started always-visible; Completed/Cancelled start collapsed with Show-all at 10+). groupTasksForDashboard excludes other assignees and sorts completed/cancelled by terminal date desc. Co-Authored-By: claude-flow <ruv@ruv.net>
195 lines
6.3 KiB
Dart
195 lines
6.3 KiB
Dart
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 = <String, ProgrammerRunInfo>{
|
|
'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 = <String, ProgrammerRunInfo>{
|
|
'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 = <String, ProgrammerRunInfo>{
|
|
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 = <String, ProgrammerRunInfo>{
|
|
'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);
|
|
});
|
|
}
|