Files
tasq/test/day_sheet_tabs_test.dart
redz1029 54ce528b02 feat(programmer): day sheets + approvals tabs
- day_sheet_snapshot_builder.dart: buildSnapshotForSheet (approved →
  stored snapshot, else live buildDayRows), groupSheetsByDate (descending
  keys), approvalCounts (pending/awaiting/approved)
- approvals_tab.dart: status filter chips, programmer dropdown,
  ReportDateFilter.controlled, date-grouped cards with Auto/Early/Round
  badges, checkbox multi-select, bulk Approve selected (n) flow
- day_sheets_tab.dart: Today card (live rows, total, status, Submit day),
  Needs your attention (disapproved), history with AppStatusSummaryRow
  All/Pending/Disapproved/Approved filter
- programmerTasksTabsFor: admin → [monitor, approvals, mine, all];
  programmer → [mine, all, daySheets]
- programmer_tasks_list_screen.dart: wire approvals + daySheets cases

Tests: 15 passing (day_sheet_tabs_test + programmer_tabs_test)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-28 14:43:52 +08:00

181 lines
6.0 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/programmer_day_sheet.model.dart';
import 'package:tasq/providers/profile_provider.dart';
import 'package:tasq/providers/programmer_day_sheets_provider.dart';
import 'package:tasq/screens/programmer_tasks/day_sheets/approvals_tab.dart';
import 'package:tasq/screens/programmer_tasks/day_sheets/day_sheet_snapshot_builder.dart';
import 'package:tasq/theme/app_theme.dart';
import 'package:tasq/utils/app_time.dart';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
ProgrammerDaySheet _sheet({
required String id,
required String status,
DateTime? workDate,
bool autoSubmitted = false,
int resubmissions = 0,
}) {
final now = AppTime.now();
final date = workDate ?? DateTime.utc(now.year, now.month, 15);
return ProgrammerDaySheet(
id: id,
programmerId: 'prog-1',
status: status,
workDate: date,
autoSubmitted: autoSubmitted,
resubmissions: resubmissions,
createdAt: now,
updatedAt: now,
);
}
Widget _buildApp(Widget child, {List<Override> overrides = const []}) {
return ProviderScope(
overrides: overrides,
child: MaterialApp(
theme: AppTheme.light(),
home: Scaffold(body: child),
),
);
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
void main() {
setUpAll(AppTime.initialize);
// ── groupSheetsByDate ────────────────────────────────────────────────────
group('groupSheetsByDate', () {
test('groups by workDate, keys descending', () {
final d1 = DateTime.utc(2024, 1, 3);
final d2 = DateTime.utc(2024, 1, 2);
final d3 = DateTime.utc(2024, 1, 1);
final sheets = [
_sheet(id: 'a', status: DaySheetStatus.pending, workDate: d2),
_sheet(id: 'b', status: DaySheetStatus.approved, workDate: d1),
_sheet(id: 'c', status: DaySheetStatus.draft, workDate: d3),
_sheet(id: 'd', status: DaySheetStatus.pending, workDate: d2),
];
final grouped = groupSheetsByDate(sheets);
final keys = grouped.keys.toList();
expect(keys, [d1, d2, d3]);
expect(grouped[d1]!.map((s) => s.id).toList(), ['b']);
expect(grouped[d2]!.map((s) => s.id).toList(), ['a', 'd']);
expect(grouped[d3]!.map((s) => s.id).toList(), ['c']);
});
test('single sheet returns single-entry map', () {
final d = DateTime.utc(2024, 6, 1);
final result = groupSheetsByDate([_sheet(id: 'x', status: DaySheetStatus.pending, workDate: d)]);
expect(result.keys.toList(), [d]);
expect(result[d]!.length, 1);
});
test('empty list returns empty map', () {
expect(groupSheetsByDate([]), isEmpty);
});
});
// ── approvalCounts ───────────────────────────────────────────────────────
group('approvalCounts', () {
test('counts pending, awaiting (disapproved) and approved', () {
final sheets = [
_sheet(id: '1', status: DaySheetStatus.pending),
_sheet(id: '2', status: DaySheetStatus.pending),
_sheet(id: '3', status: DaySheetStatus.disapproved),
_sheet(id: '4', status: DaySheetStatus.approved),
_sheet(id: '5', status: DaySheetStatus.draft),
];
final counts = approvalCounts(sheets);
expect(counts.pending, 2);
expect(counts.awaiting, 1);
expect(counts.approved, 1);
});
test('all zero for empty list', () {
final counts = approvalCounts([]);
expect(counts.pending, 0);
expect(counts.awaiting, 0);
expect(counts.approved, 0);
});
test('draft sheets are not counted', () {
final counts = approvalCounts([
_sheet(id: '1', status: DaySheetStatus.draft),
_sheet(id: '2', status: DaySheetStatus.draft),
]);
expect(counts.pending, 0);
expect(counts.awaiting, 0);
expect(counts.approved, 0);
});
});
// ── ApprovalsTab widget ──────────────────────────────────────────────────
group('ApprovalsTab', () {
List<ProgrammerDaySheet> _pendingSheets(int n) {
final now = AppTime.now();
return List.generate(
n,
(i) => _sheet(
id: 'sheet-$i',
status: DaySheetStatus.pending,
workDate: DateTime.utc(now.year, now.month, 15),
),
);
}
testWidgets(
'selecting 2 of 3 pending cards shows Approve selected (2)',
(tester) async {
final sheets = _pendingSheets(3);
await tester.pumpWidget(
_buildApp(
const ApprovalsTab(),
overrides: [
allDaySheetsProvider.overrideWith(
(ref) => Stream.value(sheets),
),
profilesProvider.overrideWith(
(ref) => Stream.value([]),
),
isStrictAdminProvider.overrideWith((ref) => true),
currentUserIdProvider.overrideWith((ref) => 'admin-1'),
],
),
);
// Let the stream settle.
await tester.pump();
await tester.pump();
// All 3 pending cards should be visible with checkboxes.
final checkboxes = find.byType(Checkbox);
expect(checkboxes, findsAtLeastNWidgets(2));
// Select two cards.
await tester.tap(checkboxes.at(0));
await tester.pump();
await tester.tap(checkboxes.at(1));
await tester.pump();
// The approve button must reflect the selection count.
expect(find.text('Approve selected (2)'), findsOneWidget);
});
});
}