129 lines
5.3 KiB
Dart
129 lines
5.3 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:miler/Models/stop_status.dart';
|
|
import 'fixtures/demo_stops.dart';
|
|
|
|
/// Injecting demo stops into Bookings is only half the fix. The tab drops
|
|
/// anything still pending — it shows work the rider has *taken on* — so demo
|
|
/// stops that arrive as `status: 'new'` are filtered out silently and the tab
|
|
/// stays empty anyway. These tests pin the states, because a regression there
|
|
/// looks exactly like "the mock data doesn't work" with nothing in the logs.
|
|
void main() {
|
|
/// The predicate Bookings uses to decide a stop belongs to it at all
|
|
/// (pickups.dart — anything still pending lives on Home instead).
|
|
bool survivesBookingsFilter(Map<String, dynamic> o) {
|
|
final status = (o['orderstatus']?.toString().toLowerCase() ?? '').trim();
|
|
const pending = {'', 'assigned', 'miler_assigned', 'pending', 'new'};
|
|
return !pending.contains(status);
|
|
}
|
|
|
|
test('every demo booking survives the accepted-only filter', () {
|
|
final bookings = getDemoBookings(riderId: 4021);
|
|
expect(bookings, isNotEmpty);
|
|
expect(
|
|
bookings.where(survivesBookingsFilter).length,
|
|
bookings.length,
|
|
reason: 'a demo stop left as "new" is dropped and never reaches the list',
|
|
);
|
|
});
|
|
|
|
test('covers every state the tab can render', () {
|
|
final statuses = getDemoBookings(riderId: 4021)
|
|
.map((b) => stopStatusFromRaw(b['orderstatus']))
|
|
.toSet();
|
|
|
|
// Two finished ones give the progress bar something green; the active one
|
|
// drives the banner and blocks the rest; the skipped one is the amber
|
|
// return-visit case.
|
|
expect(statuses, contains(StopStatus.picked));
|
|
expect(statuses, contains(StopStatus.active));
|
|
expect(statuses, contains(StopStatus.skipped));
|
|
expect(statuses, contains(StopStatus.accepted));
|
|
});
|
|
|
|
test('leaves enough open stops to fill the list', () {
|
|
// Bookings excludes active and finished stops from the card list — those
|
|
// go to the banner and the progress count. What is left has to be more
|
|
// than a couple of cards or the tab still looks broken.
|
|
final open = getDemoBookings(riderId: 4021).where((b) {
|
|
final st = stopStatusFromRaw(b['orderstatus']);
|
|
return st != StopStatus.active &&
|
|
st != StopStatus.picked &&
|
|
st != StopStatus.cancelled;
|
|
}).length;
|
|
expect(open, greaterThanOrEqualTo(8));
|
|
});
|
|
|
|
test('is the same route Home gave the miler', () {
|
|
final home = getDemoTripStops(riderId: 4021).take(12);
|
|
final bookings = getDemoBookings(riderId: 4021);
|
|
expect(
|
|
bookings.map((b) => b['orderid']),
|
|
home.map((b) => b['orderid']),
|
|
reason: 'Bookings must not invent a second, unrelated route',
|
|
);
|
|
});
|
|
|
|
pipelineTests();
|
|
|
|
test('two milers get different bookings', () {
|
|
final a = getDemoBookings(riderId: 4021).map((b) => b['orderid']).toSet();
|
|
final b = getDemoBookings(riderId: 4022).map((b) => b['orderid']).toSet();
|
|
expect(a.intersection(b), isEmpty);
|
|
});
|
|
}
|
|
|
|
/// The first fix for the empty Bookings tab was gated on the RAW api response
|
|
/// (`items.isEmpty`), but the tab renders a list that survives two more
|
|
/// filters. A signed-in miler's backend answer is rarely empty — it is full of
|
|
/// ASSIGNED bookings, which belong on Home and are filtered out here — so the
|
|
/// fallback never fired and the tab stayed blank. These tests pin the end of
|
|
/// the pipeline, using the real production helpers, not a paraphrase of them.
|
|
void pipelineTests() {
|
|
/// Bookings' accepted-only predicate.
|
|
List<Map<String, dynamic>> keepWorkable(
|
|
List<Map<String, dynamic>> items,
|
|
Set<String> acceptedIds,
|
|
) =>
|
|
items.where((o) {
|
|
final id = (o['orderid'] ?? '').toString();
|
|
if (id.isNotEmpty && acceptedIds.contains(id)) return true;
|
|
final status =
|
|
(o['orderstatus']?.toString().toLowerCase() ?? '').trim();
|
|
const pending = {'', 'assigned', 'miler_assigned', 'pending', 'new'};
|
|
return !pending.contains(status);
|
|
}).toList();
|
|
|
|
test('a backend full of ASSIGNED bookings still leaves the tab empty', () {
|
|
// What the live backend actually returns for a seeded miler.
|
|
final fromApi = [
|
|
for (var i = 0; i < 6; i++)
|
|
<String, dynamic>{'orderid': 'API-$i', 'orderstatus': 'miler_assigned'},
|
|
];
|
|
expect(fromApi, isNotEmpty, reason: 'the raw response is NOT empty');
|
|
expect(keepWorkable(fromApi, {}), isEmpty,
|
|
reason: 'yet nothing survives — which is why the old gate never fired');
|
|
});
|
|
|
|
test('the fallback keyed on the filtered list produces visible cards', () {
|
|
final fromApi = [
|
|
for (var i = 0; i < 6; i++)
|
|
<String, dynamic>{'orderid': 'API-$i', 'orderstatus': 'miler_assigned'},
|
|
];
|
|
|
|
var workable = keepWorkable(fromApi, {});
|
|
if (workable.isEmpty) workable = getDemoBookings(riderId: 4021);
|
|
|
|
// Bookings' list excludes active and finished stops — those go to the
|
|
// banner and the progress count.
|
|
final listForTop = workable.where((o) {
|
|
final st = stopStatusFromRaw(o['orderstatus']);
|
|
return st != StopStatus.active &&
|
|
st != StopStatus.picked &&
|
|
st != StopStatus.cancelled;
|
|
}).toList();
|
|
|
|
expect(listForTop, isNotEmpty, reason: 'the rider must see cards');
|
|
expect(listForTop.length, greaterThanOrEqualTo(8));
|
|
});
|
|
}
|