Files
tasq/test/day_sheet_dialogs_test.dart
2026-09-28 14:21:22 +08:00

208 lines
6.2 KiB
Dart

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]);
});
});
}