import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tasq/models/programmer_day_sheet.model.dart'; import 'package:tasq/screens/programmer_tasks/day_sheets/day_sheet_dialogs.dart'; import 'package:tasq/utils/programmer_day_rows.dart'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- Widget _wrap(Widget child) { return MaterialApp(home: Scaffold(body: child)); } // Minimal tappable button that opens the dialog under test. class _DisapproveOpener extends StatelessWidget { const _DisapproveOpener({required this.rows, this.result}); final List rows; final ValueNotifier? result; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () async { final r = await showDisapproveDialog(context, flaggedRows: rows); result?.value = r; }, child: const Text('Open'), ); } } class _JustifyOpener extends StatelessWidget { const _JustifyOpener({this.result}); final ValueNotifier? result; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () async { final r = await showJustifyDialog(context); result?.value = r; }, child: const Text('Open'), ); } } // --------------------------------------------------------------------------- // DaySheetRow fixture // --------------------------------------------------------------------------- DaySheetRow _row(String taskId) => DaySheetRow( taskId: taskId, title: 'Task $taskId', category: 'Software Development', kind: 'assignee', seconds: 3600, notes: const [], ); // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- void main() { group('showDisapproveDialog', () { testWidgets('stays open and shows error when remarks are empty', (tester) async { final result = ValueNotifier(null); await tester.pumpWidget(_wrap(_DisapproveOpener( rows: [_row('t1')], result: result, ))); await tester.tap(find.text('Open')); await tester.pumpAndSettle(); // Dialog should be visible. expect(find.text('Disapprove'), findsWidgets); // Tap the Disapprove action button with empty remarks. final disapproveButtons = find.text('Disapprove'); await tester.tap(disapproveButtons.last); await tester.pumpAndSettle(); // Error message should appear and dialog should still be open. expect(find.text('Remarks are required'), findsOneWidget); expect(find.text('Disapprove'), findsWidgets); expect(result.value, isNull); }); testWidgets('returns the entered remarks when non-empty', (tester) async { final result = ValueNotifier(null); await tester.pumpWidget(_wrap(_DisapproveOpener( rows: [_row('t1')], result: result, ))); await tester.tap(find.text('Open')); await tester.pumpAndSettle(); await tester.enterText( find.byType(TextField).first, 'Missing notes'); await tester.pumpAndSettle(); final disapproveButtons = find.text('Disapprove'); await tester.tap(disapproveButtons.last); await tester.pumpAndSettle(); expect(result.value, 'Missing notes'); }); }); group('showJustifyDialog', () { testWidgets('stays open and shows error when justification is empty', (tester) async { final result = ValueNotifier(null); await tester.pumpWidget(_wrap(_JustifyOpener(result: result))); await tester.tap(find.text('Open')); await tester.pumpAndSettle(); // Tap submit with no text. await tester.tap(find.text('Submit')); await tester.pumpAndSettle(); expect(find.text('Please write a justification'), findsOneWidget); expect(result.value, isNull); }); testWidgets('returns body when non-empty', (tester) async { final result = ValueNotifier(null); await tester.pumpWidget(_wrap(_JustifyOpener(result: result))); await tester.tap(find.text('Open')); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).first, 'My justification'); await tester.pumpAndSettle(); await tester.tap(find.text('Submit')); await tester.pumpAndSettle(); expect(result.value, 'My justification'); }); }); group('rowsForSheet', () { test('returns snapshot rows when approved and snapshot exists', () { final snapshotRow = _row('snap-1'); final snapshot = DaySheetSnapshot( totalSeconds: 3600, rows: [snapshotRow], ); final sheet = ProgrammerDaySheet( id: 'sheet-1', programmerId: 'user-1', status: DaySheetStatus.approved, workDate: DateTime.utc(2026, 9, 27), approvedSnapshot: snapshot, createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), ); final liveRow = _row('live-1'); final result = rowsForSheet(sheet, [liveRow]); expect(result, [snapshotRow]); }); test('returns live rows when not approved', () { final sheet = ProgrammerDaySheet( id: 'sheet-2', programmerId: 'user-1', status: DaySheetStatus.pending, workDate: DateTime.utc(2026, 9, 27), createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), ); final liveRow = _row('live-1'); final result = rowsForSheet(sheet, [liveRow]); expect(result, [liveRow]); }); test('returns live rows when approved but snapshot is null', () { final sheet = ProgrammerDaySheet( id: 'sheet-3', programmerId: 'user-1', status: DaySheetStatus.approved, workDate: DateTime.utc(2026, 9, 27), approvedSnapshot: null, createdAt: DateTime.utc(2026, 9, 27), updatedAt: DateTime.utc(2026, 9, 27), ); final liveRow = _row('live-1'); final result = rowsForSheet(sheet, [liveRow]); expect(result, [liveRow]); }); }); }