158 lines
6.3 KiB
Dart
158 lines
6.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:fuel_tax_tracker/models/fuel_entry.dart';
|
|
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: 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
|
|
/// entries in range". There's deliberately no checkbox — selection state is
|
|
/// conveyed by color alone.
|
|
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));
|
|
|
|
// 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(
|
|
id: id,
|
|
vehicleId: vehicleId,
|
|
date: now,
|
|
gallons: gallons,
|
|
pricePerGallon: cost / gallons,
|
|
totalCost: cost,
|
|
updatedAt: now.toUtc(),
|
|
receiptDriveFileId: 'remote-$id',
|
|
);
|
|
|
|
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(
|
|
value: appState,
|
|
child: const MaterialApp(home: ReportScreen()),
|
|
),
|
|
);
|
|
return appState;
|
|
}
|
|
|
|
Card cardFor(WidgetTester tester, String vehicleLabel) => tester.widget<Card>(
|
|
find.ancestor(of: find.text(vehicleLabel), matching: find.byType(Card)),
|
|
);
|
|
|
|
testWidgets('both vehicles start colored as selected once a range with entries for both is picked',
|
|
(tester) async {
|
|
await pumpReport(tester);
|
|
|
|
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
|
|
|
|
expect(cardFor(tester, 'Truck').color, colorScheme.primary);
|
|
expect(cardFor(tester, 'Van').color, colorScheme.primary);
|
|
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);
|
|
|
|
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
|
|
|
|
await tester.tap(find.text('Truck'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('20.000 gal · \$70.00'), findsOneWidget, reason: "only Van's total remains");
|
|
expect(cardFor(tester, 'Truck').color, isNot(colorScheme.primary), reason: 'deselected');
|
|
expect(cardFor(tester, 'Van').color, colorScheme.primary, reason: 'still selected');
|
|
});
|
|
|
|
testWidgets('tapping a deselected vehicle again reselects it (restores its coloring)',
|
|
(tester) async {
|
|
await pumpReport(tester);
|
|
|
|
final colorScheme = Theme.of(tester.element(find.text('Truck'))).colorScheme;
|
|
|
|
await tester.tap(find.text('Truck'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('Truck'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(cardFor(tester, 'Truck').color, colorScheme.primary);
|
|
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 tester.tap(find.text('Truck'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('Van'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
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.
|
|
expect(find.text('Truck'), findsOneWidget);
|
|
expect(find.text('Van'), findsOneWidget);
|
|
});
|
|
}
|