@@ -21,6 +21,8 @@ class _InstrumentedRepository extends InMemorySurplusRepository {
2121 bool reservationStreamError = false ;
2222 bool throwOnReserve = false ;
2323 bool throwOnAbuseSignal = false ;
24+ bool watchListingCalledAfterRetry = false ;
25+ bool watchVenuesCalledAfterRetry = false ;
2426 String ? lastAbuseReason;
2527 int reconcileCalls = 0 ;
2628
@@ -30,6 +32,7 @@ class _InstrumentedRepository extends InMemorySurplusRepository {
3032
3133 @override
3234 Stream <Listing ?> watchListing (String listingId) {
35+ watchListingCalledAfterRetry = true ;
3336 if (listingStreamErrorIds.contains (listingId)) {
3437 return Stream .error (StateError ('listing stream failed' ));
3538 }
@@ -42,6 +45,7 @@ class _InstrumentedRepository extends InMemorySurplusRepository {
4245
4346 @override
4447 Stream <List <Venue >> watchVenues () {
48+ watchVenuesCalledAfterRetry = true ;
4549 if (venuesStreamError) {
4650 return Stream .error (StateError ('venue stream failed' ));
4751 }
@@ -295,6 +299,43 @@ void main() {
295299 },
296300 );
297301
302+ testWidgets ('listings page supports near-hubs and available-now filters' , (
303+ tester,
304+ ) async {
305+ final repo = _InstrumentedRepository ();
306+ final now = DateTime .now ();
307+ await repo.createListing (
308+ _input (
309+ now,
310+ venueId: 'taipei-nangang-exhibition-center-hall-1' ,
311+ itemType: 'Near Hub Item' ,
312+ expiresIn: const Duration (hours: 2 ),
313+ ),
314+ );
315+ await repo.createListing (
316+ _input (
317+ now.subtract (const Duration (hours: 3 )),
318+ venueId: 'taipei-nangang-exhibition-center-hall-2' ,
319+ itemType: 'Expired Window Item' ,
320+ expiresIn: const Duration (hours: 4 ),
321+ ),
322+ );
323+
324+ await _pumpHome (tester, repo);
325+
326+ await tester.tap (find.text ('Near hubs' ));
327+ await tester.pumpAndSettle ();
328+ expect (find.text ('Clear filter' ), findsOneWidget);
329+
330+ await tester.tap (find.text ('Available now' ));
331+ await tester.pumpAndSettle ();
332+ expect (find.text ('Clear filter' ), findsOneWidget);
333+
334+ await tester.tap (find.text ('Clear filter' ));
335+ await tester.pumpAndSettle ();
336+ expect (find.text ('Clear filter' ), findsNothing);
337+ });
338+
298339 testWidgets ('listings page shows load error when venue stream fails' , (
299340 tester,
300341 ) async {
@@ -378,6 +419,9 @@ void main() {
378419 ..listingStreamErrorIds.add ('bad-listing' );
379420 await _pumpDetail (tester, repo, 'bad-listing' );
380421 expect (find.text ('Unable to load' ), findsOneWidget);
422+ await tester.tap (find.widgetWithText (FilledButton , 'Retry' ));
423+ await tester.pumpAndSettle ();
424+ expect (repo.watchListingCalledAfterRetry, isTrue);
381425 });
382426
383427 testWidgets ('listing detail shows load error when venues stream fails' , (
@@ -393,8 +437,56 @@ void main() {
393437 );
394438 await _pumpDetail (tester, repo, created.listingId);
395439 expect (find.text ('Unable to load' ), findsOneWidget);
440+ await tester.tap (find.widgetWithText (FilledButton , 'Retry' ));
441+ await tester.pumpAndSettle ();
442+ expect (repo.watchVenuesCalledAfterRetry, isTrue);
396443 });
397444
445+ testWidgets (
446+ 'listing detail reserve success falls back to navigator when go_router is absent' ,
447+ (tester) async {
448+ final repo = _InstrumentedRepository ();
449+ final created = await repo.createListing (
450+ _input (
451+ DateTime .now (),
452+ venueId: 'taipei-nangang-exhibition-center-hall-1' ,
453+ itemType: 'Fallback flow' ,
454+ ),
455+ );
456+ await _pumpDetail (tester, repo, created.listingId);
457+
458+ await tester.tap (find.text ('Reserve 1 item' ));
459+ await tester.pumpAndSettle ();
460+ await tester.tap (find.byType (CheckboxListTile ));
461+ await tester.pumpAndSettle ();
462+ await tester.tap (find.widgetWithText (FilledButton , 'Reserve' ));
463+ await tester.pumpAndSettle ();
464+
465+ expect (find.text ('Reservation confirmed' ), findsOneWidget);
466+ },
467+ );
468+
469+ testWidgets (
470+ 'listing detail renders badge chips and filters unknown badge id' ,
471+ (tester) async {
472+ final now = DateTime .now ();
473+ final repo = _InstrumentedRepository ()
474+ ..forcedListings['badge-id' ] = _forcedListing (
475+ now,
476+ id: 'badge-id' ,
477+ status: ListingStatus .active,
478+ quantityRemaining: 1 ,
479+ expiresAt: now.add (const Duration (hours: 1 )),
480+ displayNameOptional: 'Badge Enterprise' ,
481+ enterpriseBadges: const < String > ['verified' , 'unknown_badge' ],
482+ );
483+
484+ await _pumpDetail (tester, repo, 'badge-id' );
485+ expect (find.text ('Verified enterprise' ), findsOneWidget);
486+ expect (find.text ('unknown_badge' ), findsNothing);
487+ },
488+ );
489+
398490 testWidgets (
399491 'listing detail renders reserved, expired and completed statuses' ,
400492 (tester) async {
0 commit comments