A whole bunch of changes
This commit is contained in:
parent
901ec678a3
commit
956231048e
122 changed files with 5442 additions and 914 deletions
136
test/report_vehicle_selection_test.dart
Normal file
136
test/report_vehicle_selection_test.dart
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
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: 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
|
||||
/// 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));
|
||||
|
||||
// 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.
|
||||
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<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 {
|
||||
final appState = AppState()
|
||||
..isLoading = false
|
||||
..vehicles = [vehicleA, vehicleB]
|
||||
..fuelEntries = [
|
||||
entryFor('a', 'e1', 10, 35),
|
||||
entryFor('b', 'e2', 20, 70),
|
||||
];
|
||||
|
||||
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);
|
||||
await pickRange(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('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;
|
||||
|
||||
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);
|
||||
await pickRange(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('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();
|
||||
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('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);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue