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 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> keepWorkable( List> items, Set 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++) {'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++) {'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)); }); }