feat(programmer): day sheet detail screen
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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<DaySheetRow> rows;
|
||||
final ValueNotifier<String?>? 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<String?>? 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<String?>(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<String?>(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<String?>(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<String?>(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]);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user