A whole lot of stuff

This commit is contained in:
Courtney Arnold 2026-08-18 11:04:58 -05:00
parent faf319322a
commit ad850d2be6
50 changed files with 3013 additions and 213 deletions

View file

@ -7,9 +7,11 @@ import 'package:fuel_tax_tracker/models/vehicle.dart';
import 'package:fuel_tax_tracker/screens/report_screen.dart';
import 'package:fuel_tax_tracker/services/app_state.dart';
/// Covers the per-vehicle selection cards on the Reports tab: once a date
/// range is picked, every vehicle with a receipted entry in range starts
/// selected (colored like the "Share" button), tapping one to deselect it
/// Covers the per-vehicle selection cards on the Reports tab: with a date
/// range in place (now defaulted automatically — see
/// defaultReportDateRange/ReportScreen.initState), every vehicle with a
/// receipted entry in range starts selected (colored like the "Share"
/// button), tapping one to deselect it
/// drops it from the total (and, by extension, the generated report) and
/// reverts its coloring to a plain unselected card, and deselecting every
/// vehicle shows a distinct "nothing selected" message rather than "no
@ -19,10 +21,10 @@ void main() {
final vehicleA = Vehicle(id: 'a', vin: 'VINAAAAAAAAAAAAAA', nickname: 'Truck', updatedAt: DateTime.utc(2026, 1, 1));
final vehicleB = Vehicle(id: 'b', vin: 'VINBBBBBBBBBBBBBB', nickname: 'Van', updatedAt: DateTime.utc(2026, 1, 1));
// showDatePicker's initial date (when none is set yet) defaults to
// "now" — dating these entries to "now" too, rather than a fixed date,
// means just accepting that default on both pickers (below) already
// covers them, without needing to drive the calendar UI to a specific day.
// ReportScreen's date range now defaults to the current fuel-tax-refund
// period (see defaultReportDateRange) as soon as it opens — dating these
// entries to "now" means they always fall inside that default range,
// without these tests needing to drive the date pickers themselves.
final now = DateTime.now();
FuelEntry entryFor(String vehicleId, String id, double gallons, double cost) => FuelEntry(
@ -36,26 +38,15 @@ void main() {
receiptDriveFileId: 'remote-$id',
);
Future<void> pickRange(WidgetTester tester) async {
await tester.tap(find.text('Start date'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
await tester.tap(find.text('End date'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
}
Future<AppState> pumpReport(WidgetTester tester) async {
Future<AppState> pumpReport(WidgetTester tester, {bool showEstimatedFuelRefund = false}) async {
final appState = AppState()
..isLoading = false
..vehicles = [vehicleA, vehicleB]
..fuelEntries = [
entryFor('a', 'e1', 10, 35),
entryFor('b', 'e2', 20, 70),
];
]
..showEstimatedFuelRefund = showEstimatedFuelRefund;
await tester.pumpWidget(
ChangeNotifierProvider.value(
@ -73,7 +64,6 @@ void main() {
testWidgets('both vehicles start colored as selected once a range with entries for both is picked',
(tester) async {
await pumpReport(tester);
await pickRange(tester);
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
@ -82,10 +72,28 @@ void main() {
expect(find.text('30.000 gal · \$105.00'), findsOneWidget, reason: 'combined total');
});
testWidgets(
'shows an estimated fuel refund below each vehicle\'s total cost and next to the '
'combined total, when the setting is on', (tester) async {
await pumpReport(tester, showEstimatedFuelRefund: true);
// Truck: 10 gal * $0.125/gal = $1.25. Van: 20 gal * $0.125/gal = $2.50.
expect(find.text('Est. \$1.25'), findsOneWidget);
expect(find.text('Est. \$2.50'), findsOneWidget);
// Combined: 30 gal * $0.125/gal = $3.75, folded into the Total row.
expect(find.text('30.000 gal · \$105.00 · Est. \$3.75'), findsOneWidget);
});
testWidgets('shows no estimated fuel refund anywhere when the setting is off (default)',
(tester) async {
await pumpReport(tester);
expect(find.textContaining('Est. \$'), findsNothing);
});
testWidgets('tapping a selected vehicle deselects it (reverts its coloring) and drops it from the total',
(tester) async {
await pumpReport(tester);
await pickRange(tester);
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
@ -100,7 +108,6 @@ void main() {
testWidgets('tapping a deselected vehicle again reselects it (restores its coloring)',
(tester) async {
await pumpReport(tester);
await pickRange(tester);
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
@ -113,10 +120,24 @@ void main() {
expect(find.text('30.000 gal · \$105.00'), findsOneWidget, reason: 'both counted again');
});
testWidgets('tapping Preview opens the report preview screen', (tester) async {
await pumpReport(tester);
await tester.scrollUntilVisible(find.text('Preview'), 300, scrollable: find.byType(Scrollable));
await tester.tap(find.text('Preview'));
// Not pumpAndSettle: PdfPreview's own internal rendering isn't under
// test here (no platform channel mocked for it) — just that Preview
// actually navigates. A couple of bounded pumps are enough for that
// navigation (a synchronous PDF build, no plugin calls) to land.
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('Report Preview'), findsOneWidget);
});
testWidgets('deselecting every vehicle shows a distinct "nothing selected" message',
(tester) async {
await pumpReport(tester);
await pickRange(tester);
await tester.tap(find.text('Truck'));
await tester.pumpAndSettle();
@ -127,6 +148,7 @@ void main() {
find.text('No vehicles selected — tap at least one above to build a report.'),
findsOneWidget,
);
expect(find.text('Preview'), findsNothing);
expect(find.text('Share'), findsNothing);
expect(find.text('Print'), findsNothing);
// The cards themselves stay visible so the user can tap one again.