Files
doormile_milderapp/test/demo_bookings_test.dart
2026-08-11 13:16:33 +05:30

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