Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions test/presentation/browse/browse_pages_additional_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class _InstrumentedRepository extends InMemorySurplusRepository {
bool reservationStreamError = false;
bool throwOnReserve = false;
bool throwOnAbuseSignal = false;
bool watchListingCalledAfterRetry = false;
bool watchVenuesCalledAfterRetry = false;
String? lastAbuseReason;
int reconcileCalls = 0;

Expand All @@ -30,6 +32,7 @@ class _InstrumentedRepository extends InMemorySurplusRepository {

@override
Stream<Listing?> watchListing(String listingId) {
watchListingCalledAfterRetry = true;
if (listingStreamErrorIds.contains(listingId)) {
return Stream.error(StateError('listing stream failed'));
}
Expand All @@ -42,6 +45,7 @@ class _InstrumentedRepository extends InMemorySurplusRepository {

@override
Stream<List<Venue>> watchVenues() {
watchVenuesCalledAfterRetry = true;
if (venuesStreamError) {
return Stream.error(StateError('venue stream failed'));
}
Expand Down Expand Up @@ -295,6 +299,43 @@ void main() {
},
);

testWidgets('listings page supports near-hubs and available-now filters', (
tester,
) async {
final repo = _InstrumentedRepository();
final now = DateTime.now();
await repo.createListing(
_input(
now,
venueId: 'taipei-nangang-exhibition-center-hall-1',
itemType: 'Near Hub Item',
expiresIn: const Duration(hours: 2),
),
);
await repo.createListing(
_input(
now.subtract(const Duration(hours: 3)),
venueId: 'taipei-nangang-exhibition-center-hall-2',
itemType: 'Expired Window Item',
expiresIn: const Duration(hours: 4),
),
);

await _pumpHome(tester, repo);

await tester.tap(find.text('Near hubs'));
await tester.pumpAndSettle();
expect(find.text('Clear filter'), findsOneWidget);

await tester.tap(find.text('Available now'));
await tester.pumpAndSettle();
expect(find.text('Clear filter'), findsOneWidget);

await tester.tap(find.text('Clear filter'));
await tester.pumpAndSettle();
expect(find.text('Clear filter'), findsNothing);
});

testWidgets('listings page shows load error when venue stream fails', (
tester,
) async {
Expand Down Expand Up @@ -378,6 +419,9 @@ void main() {
..listingStreamErrorIds.add('bad-listing');
await _pumpDetail(tester, repo, 'bad-listing');
expect(find.text('Unable to load'), findsOneWidget);
await tester.tap(find.widgetWithText(FilledButton, 'Retry'));
await tester.pumpAndSettle();
expect(repo.watchListingCalledAfterRetry, isTrue);
});

testWidgets('listing detail shows load error when venues stream fails', (
Expand All @@ -393,8 +437,56 @@ void main() {
);
await _pumpDetail(tester, repo, created.listingId);
expect(find.text('Unable to load'), findsOneWidget);
await tester.tap(find.widgetWithText(FilledButton, 'Retry'));
await tester.pumpAndSettle();
expect(repo.watchVenuesCalledAfterRetry, isTrue);
});

testWidgets(
'listing detail reserve success falls back to navigator when go_router is absent',
(tester) async {
final repo = _InstrumentedRepository();
final created = await repo.createListing(
_input(
DateTime.now(),
venueId: 'taipei-nangang-exhibition-center-hall-1',
itemType: 'Fallback flow',
),
);
await _pumpDetail(tester, repo, created.listingId);

await tester.tap(find.text('Reserve 1 item'));
await tester.pumpAndSettle();
await tester.tap(find.byType(CheckboxListTile));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Reserve'));
await tester.pumpAndSettle();

expect(find.text('Reservation confirmed'), findsOneWidget);
},
);

testWidgets(
'listing detail renders badge chips and filters unknown badge id',
(tester) async {
final now = DateTime.now();
final repo = _InstrumentedRepository()
..forcedListings['badge-id'] = _forcedListing(
now,
id: 'badge-id',
status: ListingStatus.active,
quantityRemaining: 1,
expiresAt: now.add(const Duration(hours: 1)),
displayNameOptional: 'Badge Enterprise',
enterpriseBadges: const <String>['verified', 'unknown_badge'],
);

await _pumpDetail(tester, repo, 'badge-id');
expect(find.text('Verified enterprise'), findsOneWidget);
expect(find.text('unknown_badge'), findsNothing);
},
);

testWidgets(
'listing detail renders reserved, expired and completed statuses',
(tester) async {
Expand Down
104 changes: 103 additions & 1 deletion test/presentation/browse/my_reservations_page_test.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'dart:async';

import 'package:boxmatch/app/app_scope.dart';
import 'package:boxmatch/features/surplus/data/in_memory_surplus_repository.dart';
import 'package:boxmatch/features/surplus/domain/listing_input.dart';
import 'package:boxmatch/features/surplus/domain/listing_visibility.dart';
import 'package:boxmatch/features/surplus/domain/reservation.dart';
import 'package:boxmatch/features/surplus/domain/surplus_exceptions.dart';
import 'package:boxmatch/features/surplus/presentation/browse/my_reservations_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
Expand Down Expand Up @@ -44,6 +48,36 @@ Future<void> _pumpPage(
await tester.pumpAndSettle();
}

class _ThrowingListRepository extends InMemorySurplusRepository {
@override
Future<List<Reservation>> listRecipientReservations({
required String claimerUid,
}) async {
throw const ValidationException('List failed for test.');
}
}

class _PendingListRepository extends InMemorySurplusRepository {
final Completer<List<Reservation>> completer = Completer<List<Reservation>>();

@override
Future<List<Reservation>> listRecipientReservations({
required String claimerUid,
}) async {
return completer.future;
}
}

class _ThrowingCancelRepository extends InMemorySurplusRepository {
@override
Future<void> cancelReservation({
required String reservationId,
required String claimerUid,
}) async {
throw const ValidationException('Cancel failed for test.');
}
}

void main() {
testWidgets('shows privacy/faq without client-side inferred badge', (
tester,
Expand Down Expand Up @@ -104,6 +138,74 @@ void main() {
await _pumpPage(tester, repo: repo);

expect(find.text('No reservations yet.'), findsOneWidget);
expect(find.widgetWithText(FilledButton, 'Browse listings'), findsOneWidget);
expect(
find.widgetWithText(FilledButton, 'Browse listings'),
findsOneWidget,
);
});

testWidgets('shows loading skeleton while waiting for reservations', (
tester,
) async {
final repo = _PendingListRepository();
final deps = await buildTestDependencies(repository: repo);
tester.view.physicalSize = const Size(1280, 2000);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);

await tester.pumpWidget(
AppScope(
dependencies: deps,
child: const MaterialApp(home: MyReservationsPage()),
),
);
await tester.pump(const Duration(milliseconds: 200));

expect(find.byType(ConstrainedBox), findsWidgets);
expect(find.byType(Card), findsWidgets);
repo.completer.complete(const <Reservation>[]);
});

testWidgets('shows error view and warmup hint after retry fails', (
tester,
) async {
final repo = _ThrowingListRepository();
final deps = await buildTestDependencies(repository: repo);

await tester.pumpWidget(
AppScope(
dependencies: deps,
child: const MaterialApp(home: MyReservationsPage()),
),
);
await tester.pump(const Duration(milliseconds: 900));
await tester.pumpAndSettle();

expect(find.text('Unable to load'), findsOneWidget);
expect(
find.textContaining('Service may still be warming up'),
findsOneWidget,
);
});

testWidgets('cancel reservation error shows snackbar', (tester) async {
final repo = _ThrowingCancelRepository();
final now = DateTime.now();
final listing = await repo.createListing(
_input(now, itemType: 'Lunchbox', displayName: 'Acme Charity'),
);
await repo.reserveListing(
listingId: listing.listingId,
claimerUid: 'test-user',
qty: 1,
disclaimerAccepted: true,
);

await _pumpPage(tester, repo: repo);
await tester.tap(find.widgetWithText(OutlinedButton, 'Cancel reservation'));
await tester.pumpAndSettle();

expect(find.text('Cancel failed for test.'), findsOneWidget);
});
}
Loading