import 'package:consumer_app/core/config/app_config.dart';
import 'package:consumer_app/core/location/location_controller.dart';
import 'package:consumer_app/core/navigation/canonical_module_routes.dart';
import 'package:consumer_app/core/networking/api_client.dart';
import 'package:consumer_app/l10n/app_localizations.dart';
import 'package:consumer_app/l10n/locale_controller.dart';
import 'package:consumer_app/models/account_models.dart';
import 'package:consumer_app/models/claim_models.dart';
import 'package:consumer_app/models/content_models.dart';
import 'package:consumer_app/models/image_suggestion_models.dart';
import 'package:consumer_app/models/notification_models.dart';
import 'package:consumer_app/repositories/account_repository.dart';
import 'package:consumer_app/repositories/claim_repository.dart';
import 'package:consumer_app/repositories/discovery_repository.dart';
import 'package:consumer_app/repositories/geo_repository.dart';
import 'package:consumer_app/repositories/image_suggestion_repository.dart';
import 'package:consumer_app/repositories/notifications_repository.dart';
import 'package:consumer_app/shared/app_shell/app_shell.dart';
import 'package:consumer_app/features/discovery/resource_browser_page.dart';
import 'package:consumer_app/features/more/more_page.dart';
import 'package:consumer_app/shared/navigation/app_navigation.dart';
import 'package:consumer_app/features/claim/claim_page.dart';
import 'package:consumer_app/features/saved/saved_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('startup location explanation does not block the shell', (
tester,
) async {
final locationController = LocationController(
storage: _PromptLocationStorage(),
);
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
locationController: locationController,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Standort für lokale Entdeckungen'), findsOneWidget);
expect(find.byType(AlertDialog), findsNothing);
expect(find.text('Orte'), findsAtLeastNWidgets(1));
});
testWidgets('offline bootstrap exposes a localized retry action', (
tester,
) async {
var retries = 0;
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
onRetryBootstrap: () => retries++,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Du bist offline'), findsOneWidget);
expect(find.text('Erneut versuchen'), findsOneWidget);
final retryButton = tester.widget<TextButton>(
find.widgetWithText(TextButton, 'Erneut versuchen'),
);
retryButton.onPressed!();
expect(retries, 1);
});
testWidgets('global shell navigation exposes logo menu and module grid', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
Scaffold(
body: const SizedBox.expand(),
bottomNavigationBar: AppNavigationBar(
items: config.navigation,
selectedIndex: 0,
onSelected: (_) {},
onOpenMenu: () {},
brand: config.brand,
labelBuilder: (item) => switch (item.labelKey) {
'navStart' => 'Start',
'navMap' => 'Karte',
'navEvents' => 'Events',
'navSaved' => 'Gespeichert',
_ => item.labelKey,
},
),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(AppNavigationBar), findsOneWidget);
expect(find.text('Start'), findsOneWidget);
expect(find.text('Gespeichert'), findsOneWidget);
expect(
tester.getSize(find.byType(AppNavigationBar)).height,
lessThanOrEqualTo(100),
);
});
testWidgets('configured navigation collapses duplicate canonical routes', (
tester,
) async {
final defaults = NavigationItem.defaults
.map(
(item) => <String, dynamic>{
'id': item.id,
'route': item.route,
'label_key': item.labelKey,
'icon': item.icon,
'enabled': true,
},
)
.toList(growable: false);
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': defaults,
'app_navigation': {
'bottom': [
{
'id': 'places-alias',
'route': '/places/',
'label_key': 'navPlaces',
'icon': 'place',
'enabled': true,
},
{
'id': 'places',
'route': '/places/',
'label_key': 'navPlaces',
'icon': 'place',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
],
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
expect(navigation.items.map((item) => item.id), ['places-alias', 'events']);
navigation.onSelected(0);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
});
testWidgets(
'fallback shell keeps all canonical primary module destinations',
(tester) async {
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
for (final label in [
'Orte',
'Events',
'Karte',
'Kleinanzeigen',
'Jobs',
'Immobilien',
]) {
expect(find.text(label), findsAtLeastNWidgets(1));
}
},
);
testWidgets(
'places route keeps schema config separate from API taxonomy categories',
(tester) async {
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'content_categories': {
'places': {
'taxonomy': 'mw_place_category',
'profiles': {
'gastronomy': {
'slugs': ['restaurants'],
'labels': {'restaurants': 'Restaurants'},
'fields': [],
'required': [],
},
},
},
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
navigation.onSelected(0);
await tester.pumpAndSettle();
expect(find.text('Kunst & Kultur'), findsOneWidget);
expect(find.text('Restaurants'), findsNothing);
},
);
testWidgets('global map queries every canonical geo module', (tester) async {
final repository = _FakeDiscoveryRepository();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: repository,
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
navigation.onSelected(2); // canonical fallback order: places, events, map
await tester.pumpAndSettle();
expect(
repository.typeCalls.any(
(types) =>
types.length == CanonicalModuleRoutes.ids.length &&
types.every(CanonicalModuleRoutes.ids.contains),
),
isTrue,
);
});
testWidgets('fullscreen menu renders configured secondary modules', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
FullscreenModuleMenuPage(
items: config.navigation,
brand: config.brand,
labelBuilder: (item) => switch (item.labelKey) {
'navCommunity' => 'Community',
'moduleServices' => 'Services und Einrichtungen',
'moduleClassifieds' => 'Kleinanzeigen',
'moduleJobs' => 'Jobs',
'moduleRealEstate' => 'Immobilien',
'moduleNews' => 'News',
'moduleGuides' => 'Guides',
_ => item.labelKey,
},
),
),
);
await tester.pumpAndSettle();
expect(find.text('Services und Einrichtungen'), findsOneWidget);
expect(find.text('Kleinanzeigen'), findsOneWidget);
expect(find.text('Jobs'), findsOneWidget);
expect(find.byIcon(Icons.close), findsOneWidget);
});
testWidgets('opens a canonical module for an incoming deep link', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
initialPath: config.navigation
.firstWhere((item) => item.id == 'real-estate')
.route,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Immobilien'), findsOneWidget);
});
testWidgets('all public module deep links open the same localized shell', (
tester,
) async {
const expectedTitles = <String, String>{
'places': 'Orte',
'services': 'Services und Einrichtungen',
'events': 'Events',
'classifieds': 'Kleinanzeigen',
'jobs': 'Jobs',
'real-estate': 'Immobilien',
};
for (final entry in expectedTitles.entries) {
await tester.pumpWidget(
_app(
AppShell(
key: ValueKey(entry.key),
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
initialPath: CanonicalModuleRoutes.pathFor(entry.key),
),
),
);
await tester.pumpAndSettle();
expect(find.text(entry.value), findsOneWidget);
}
});
testWidgets(
'home tile and fullscreen menu use the same canonical module shell',
(tester) async {
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.drag(find.byType(ListView).first, const Offset(-360, 0));
await tester.pumpAndSettle();
// The home quick-access entry is now a circular, card-free item. The
// first Jobs label is therefore the home entry after the horizontal
// slider has been moved into view.
await tester.tap(find.text('Jobs').first);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
await tester.tap(find.byType(BackButton));
await tester.pumpAndSettle();
await tester.tapAt(tester.getCenter(find.byType(AppNavigationBar)));
await tester.pumpAndSettle();
expect(find.byType(FullscreenModuleMenuPage), findsOneWidget);
final menuJobs = find.text('Jobs').last;
await tester.ensureVisible(menuJobs);
await tester.pumpAndSettle();
await tester.tap(menuJobs);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
},
);
testWidgets('switching canonical modules refreshes the resource type', (
tester,
) async {
final repository = _FakeDiscoveryRepository();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: repository,
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
repository.typeCalls = [];
final navigationFinder = find.byType(AppNavigationBar);
final navigation = tester.widget<AppNavigationBar>(navigationFinder);
final eventsIndex = navigation.items.indexWhere(
(item) => item.id == 'events',
);
expect(eventsIndex, greaterThanOrEqualTo(0));
navigation.onSelected(eventsIndex);
await tester.pumpAndSettle();
expect(
repository.typeCalls.any(
(types) => types.length == 1 && types.first == 'events',
),
isTrue,
);
});
testWidgets(
'configured fullscreen menu keeps every public content route reachable',
(tester) async {
final sparseNavigation = <Map<String, dynamic>>[
{
'id': 'start',
'route': '/',
'label_key': 'navStart',
'icon': 'explore',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
{
'id': 'more',
'route': '/mehr/',
'label_key': 'navMore',
'icon': 'menu',
'enabled': true,
},
];
final allRoutes = NavigationItem.defaults
.map(
(item) => <String, dynamic>{
'id': item.id,
'route': item.route,
'label_key': item.labelKey,
'icon': item.icon,
'enabled': true,
},
)
.toList(growable: false);
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': allRoutes,
'app_navigation': {
'bottom': sparseNavigation,
'fullscreen': sparseNavigation,
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.tapAt(tester.getCenter(find.byType(AppNavigationBar)));
await tester.pumpAndSettle();
expect(find.byType(FullscreenModuleMenuPage), findsOneWidget);
for (final label in [
'Orte',
'Services und Einrichtungen',
'Events',
'Kleinanzeigen',
'Jobs',
'Immobilien',
]) {
expect(find.text(label), findsOneWidget);
}
expect(find.text('Mehr'), findsNothing);
},
);
testWidgets('configured More navigation renders its body inside the shell', (
tester,
) async {
final bottom = <Map<String, dynamic>>[
{
'id': 'start',
'route': '/',
'label_key': 'navStart',
'icon': 'explore',
'enabled': true,
},
{
'id': 'map',
'route': '/karte/',
'label_key': 'navMap',
'icon': 'map',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
{
'id': 'community',
'route': '/community/',
'label_key': 'navCommunity',
'icon': 'people',
'enabled': true,
},
{
'id': 'saved',
'route': '/gespeichert/',
'label_key': 'navSaved',
'icon': 'bookmark',
'enabled': true,
},
{
'id': 'more',
'route': '/mehr/',
'label_key': 'navMore',
'icon': 'menu',
'enabled': true,
},
];
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': bottom,
'app_navigation': {'bottom': bottom, 'fullscreen': bottom},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.menu).last);
await tester.pumpAndSettle();
expect(find.byType(MorePage), findsOneWidget);
expect(find.text('Mehr'), findsAtLeastNWidgets(2));
expect(find.text('Mein Profil'), findsOneWidget);
});
testWidgets('saved surface renders its responsive intro and empty state', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
SavedPage(
accountRepository: _FakeAccountRepository(),
discoveryRepository: _FakeDiscoveryRepository(),
geoRepository: _FakeGeoRepository(),
geoConfig: config.geo,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Gespeichert'), findsOneWidget);
expect(
find.text(
'Deine gespeicherten Orte und Events bleiben hier griffbereit.',
),
findsOneWidget,
);
expect(find.text('Noch nichts gespeichert'), findsOneWidget);
});
testWidgets('claim surface renders localized status and object metadata', (
tester,
) async {
await tester.pumpWidget(
_app(
ClaimPage(
repository: _FakeClaimRepository(
response: ClaimListResponse(
claims: [
ClaimRecord(
id: 7,
objectType: 'classifieds',
objectId: 42,
claimantUserId: 8,
organizationName: 'MeinWeimar Handel',
claimantNote: 'Bitte prüfen',
status: 'approved',
decisionNote: 'Freigabe erteilt',
createdAt: '2026-09-26T10:00:00Z',
updatedAt: '2026-09-26T10:00:00Z',
),
],
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Business-Claims'), findsNWidgets(2));
expect(find.text('1 Anfrage'), findsOneWidget);
await tester.drag(find.byType(CustomScrollView), const Offset(0, -400));
await tester.pumpAndSettle();
expect(find.text('Freigegeben'), findsOneWidget);
expect(find.text('Kleinanzeige #42'), findsOneWidget);
expect(find.text('Deine Notiz'), findsOneWidget);
expect(find.text('Freigabe erteilt'), findsOneWidget);
});
}
Widget _app(Widget child) {
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: const Locale('de'),
home: child,
);
}
class _PromptLocationStorage implements LocationStorage {
LocationPersistedState state = const LocationPersistedState();
@override
Future<LocationPersistedState> read() async => state;
@override
Future<void> write(LocationPersistedState next) async => state = next;
}
class _FakeDiscoveryRepository implements DiscoveryRepository {
static final _empty = DiscoveryResponse.fromJson({
'items': const [],
'meta': {
'categories': [
{
'slug': 'kunst-kultur',
'name': 'Kunst & Kultur',
'parent_slug': '',
'count': 1,
'taxonomy': 'mw_place_category',
},
{
'slug': 'museum-2',
'name': 'Museum',
'parent_slug': 'kunst-kultur',
'count': 1,
'taxonomy': 'mw_place_category',
},
],
},
});
List<List<String>> typeCalls = const [];
@override
Future<DiscoveryResponse> discover({
required List<String> types,
String? search,
double? latitude,
double? longitude,
double? radiusKm = 10,
int page = 1,
int perPage = 20,
String? category,
String? region,
String? status,
Map<String, String> filters = const {},
String sort = 'auto',
double? minLatitude,
double? maxLatitude,
double? minLongitude,
double? maxLongitude,
}) async {
typeCalls = [...typeCalls, List<String>.from(types)];
return _empty;
}
@override
Future<DiscoveryItem> detail({required String resource, required int id}) =>
throw UnimplementedError();
}
class _FakeAccountRepository implements AccountRepository {
@override
Future<ProfileData> me() async => ProfileData.fromJson(const {});
@override
Future<ProfileData> updateMe(Map<String, dynamic> fields) => me();
@override
Future<ProfileData> publicProfile(int userId) => throw UnimplementedError();
@override
Future<SavedResponse> favorites() async => const SavedResponse(items: []);
@override
Future<bool> updateFavorite({
required String objectType,
required int objectId,
required bool saved,
}) async => saved;
}
class _FakeClaimRepository implements ClaimRepository {
_FakeClaimRepository({this.response = const ClaimListResponse(claims: [])});
final ClaimListResponse response;
@override
Future<ClaimListResponse> list() async => response;
}
class _FakeGeoRepository implements GeoRepository {
@override
Future<List<GeoSearchResult>> searchAddress(String query, {int limit = 5}) =>
throw UnimplementedError();
}
class _FakeImageSuggestionRepository implements ImageSuggestionRepository {
@override
Future<ImageSuggestionData> submit({
required String targetType,
required int targetId,
required List<int> bytes,
required String filename,
required bool rightsHolderConfirmed,
required bool lawfulSourceConfirmed,
required bool personsConsentConfirmed,
required bool platformLicenseConfirmed,
required String idempotencyKey,
String source = '',
String license = '',
String photographer = '',
String description = '',
}) => throw UnimplementedError();
@override
Future<ImageSuggestionList> own({String? status}) =>
Future.value(ImageSuggestionList.fromJson(const {}));
@override
Future<ImageSuggestionData> respond({
required int suggestionId,
required String response,
required String idempotencyKey,
}) => throw UnimplementedError();
}
class _FakeNotificationsRepository implements NotificationsRepository {
@override
Future<NotificationList> list({String? status}) =>
Future.value(NotificationList.fromJson(const {}));
@override
Future<void> markRead(int id) => throw UnimplementedError();
}
