import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:app_boilerplate/ui/navigation/main_navigation_scaffold.dart'; import 'package:app_boilerplate/ui/navigation/app_router.dart'; import 'package:app_boilerplate/data/local/local_storage_service.dart'; import 'package:app_boilerplate/data/nostr/nostr_service.dart'; import 'package:app_boilerplate/data/nostr/models/nostr_keypair.dart'; import 'package:app_boilerplate/data/sync/sync_engine.dart'; import 'package:app_boilerplate/data/session/session_service.dart'; import 'package:app_boilerplate/data/session/models/user.dart'; import 'package:app_boilerplate/data/firebase/firebase_service.dart'; import 'package:app_boilerplate/data/recipes/recipe_service.dart'; import 'package:app_boilerplate/core/service_locator.dart'; import 'main_navigation_scaffold_test.mocks.dart'; @GenerateMocks([ LocalStorageService, NostrService, SyncEngine, SessionService, FirebaseService, ]) void main() { late MockLocalStorageService mockLocalStorageService; late MockNostrService mockNostrService; late MockSyncEngine mockSyncEngine; late MockSessionService mockSessionService; late MockFirebaseService mockFirebaseService; setUp(() async { mockLocalStorageService = MockLocalStorageService(); mockNostrService = MockNostrService(); mockSyncEngine = MockSyncEngine(); mockSessionService = MockSessionService(); mockFirebaseService = MockFirebaseService(); // Set default return values for mocks when(mockSessionService.isLoggedIn).thenReturn(false); when(mockSessionService.currentUser).thenReturn(null); when(mockFirebaseService.isEnabled).thenReturn(false); when(mockNostrService.getRelays()).thenReturn([]); // Stub LocalStorageService.getItem for app_settings (used by RelayManagementScreen) when(mockLocalStorageService.getItem('app_settings')).thenAnswer((_) async => null); when(mockLocalStorageService.getItem('app_preferences')).thenAnswer((_) async => null); // Stub NostrService methods that might be called by UI final mockKeyPair = NostrKeyPair.generate(); when(mockNostrService.generateKeyPair()).thenReturn(mockKeyPair); // Create a RecipeService for tests (needed by RecipesScreen) // Use a simple setup - RecipesScreen will handle initialization errors gracefully RecipeService? recipeService; try { recipeService = RecipeService( localStorage: mockLocalStorageService, nostrService: mockNostrService, ); // Don't initialize here - let screens handle it or fail gracefully } catch (e) { // RecipeService creation failed, that's ok for tests recipeService = null; } // Register services with ServiceLocator ServiceLocator.instance.registerServices( localStorageService: mockLocalStorageService, nostrService: mockNostrService, syncEngine: mockSyncEngine, sessionService: mockSessionService, firebaseService: mockFirebaseService, recipeService: recipeService, ); }); tearDown(() { // Reset ServiceLocator after each test ServiceLocator.instance.reset(); }); Widget createTestWidget() { final appRouter = AppRouter( sessionService: mockSessionService, localStorageService: mockLocalStorageService, nostrService: mockNostrService, syncEngine: mockSyncEngine, firebaseService: mockFirebaseService, ); return MaterialApp( onGenerateRoute: appRouter.generateRoute, home: const MainNavigationScaffold(), ); } group('MainNavigationScaffold - Navigation', () { testWidgets('displays bottom navigation bar with correct tabs', (WidgetTester tester) async { // When not logged in, only Home tab is visible in bottom nav await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle(); // Check for navigation icons (custom bottom nav, not standard BottomNavigationBar) expect(find.byIcon(Icons.home), findsWidgets); // Check for labels expect(find.text('Home'), findsWidgets); // User icon is now in AppBar, not bottom nav // Recipes, Favourites, and Add button in bottom nav are hidden when not logged in expect(find.text('Recipes'), findsNothing); expect(find.text('Favourites'), findsNothing); // Note: Home screen may have its own add icon, so we check for the bottom nav add button specifically // The bottom nav add button is in a Material widget with CircleBorder expect(find.text('Add Recipe'), findsNothing); }); testWidgets('displays Add Recipe button in center of bottom nav', (WidgetTester tester) async { // Set up as logged in to see Add button when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Find the add icon in the bottom navigation (should be in center) expect(find.byIcon(Icons.add), findsWidgets); // The add button is now part of the custom bottom nav, not a FloatingActionButton }); testWidgets('renders Home screen by default', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle(); // Home text should appear in AppBar expect(find.text('Home'), findsWidgets); expect(find.byIcon(Icons.home), findsWidgets); }); testWidgets('can navigate to Recipes screen', (WidgetTester tester) async { // Set up as logged in to access Recipes tab when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Tap Recipes tab final recipesTab = find.text('Recipes'); expect(recipesTab, findsWidgets); await tester.tap(recipesTab); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation // Verify Recipes screen is shown (check for AppBar title) expect(find.text('Recipes'), findsWidgets); }); testWidgets('can navigate to Favourites screen', (WidgetTester tester) async { // Set up as logged in to access Favourites tab when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Tap Favourites tab final favouritesTab = find.text('Favourites'); expect(favouritesTab, findsWidgets); await tester.tap(favouritesTab); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation // Verify Favourites screen is shown expect(find.text('Favourites'), findsWidgets); }); testWidgets('can navigate to User/Session screen', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle(); // Tap User icon in AppBar (person icon) final userIcon = find.byIcon(Icons.person); expect(userIcon, findsWidgets); // Tap the first person icon (should be in AppBar) await tester.tap(userIcon.first); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation // Verify User screen is shown expect(find.text('User'), findsWidgets); }); testWidgets('Add Recipe button navigates to Add Recipe screen', (WidgetTester tester) async { // Set up as logged in to see Add button when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Find the add icon in the bottom navigation (there may be multiple - Home screen has one too) // We want the one in the bottom nav, which should be the last one or in a Material widget final addButtons = find.byIcon(Icons.add); expect(addButtons, findsWidgets); // Tap the last add button (should be the bottom nav one) await tester.tap(addButtons.last); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 200)); // Allow navigation // Verify Add Recipe screen is shown (check for AppBar title) // If navigation worked, we should see "Add Recipe" in the AppBar expect(find.text('Add Recipe'), findsWidgets); }); testWidgets('settings icon appears in AppBar', (WidgetTester tester) async { // Set up as logged in to access User screen when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Navigate to User screen via AppBar icon (only screen with settings icon) final userIcon = find.byIcon(Icons.person); expect(userIcon, findsWidgets); await tester.tap(userIcon.first); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation // Settings icon should be in AppBar actions expect(find.byIcon(Icons.settings), findsWidgets); }); testWidgets('settings icon is tappable and triggers navigation', (WidgetTester tester) async { // Set up as logged in to access User screen when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Navigate to User screen via AppBar icon (only screen with settings icon) final userIcon = find.byIcon(Icons.person); expect(userIcon, findsWidgets); await tester.tap(userIcon.first); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation // Find settings icon in AppBar final settingsIcons = find.byIcon(Icons.settings); expect(settingsIcons, findsWidgets); // Tap the settings icon (should be in AppBar) // This should trigger navigation to Relay Management await tester.tap(settingsIcons.first); await tester.pump(); // Just pump once to trigger navigation // Verify navigation was attempted (no errors thrown) // The actual screen content depends on service availability in test environment }); }); group('MainNavigationScaffold - Screen Rendering', () { testWidgets('renders all main screens correctly', (WidgetTester tester) async { await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle(); // Verify scaffold structure exists // Custom bottom nav (not standard BottomNavigationBar) expect(find.byType(Scaffold), findsWidgets); expect(find.byType(IndexedStack), findsOneWidget); // Verify navigation icons are present expect(find.byIcon(Icons.home), findsWidgets); }); testWidgets('User screen has settings icon in AppBar', (WidgetTester tester) async { // Set up as logged in to access all tabs when(mockSessionService.isLoggedIn).thenReturn(true); final testUser = User(id: 'test_user', username: 'Test User'); when(mockSessionService.currentUser).thenReturn(testUser); await tester.pumpWidget(createTestWidget()); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow widget to build // Navigate to User screen via AppBar icon (only screen with settings icon) final userIcon = find.byIcon(Icons.person); expect(userIcon, findsWidgets); await tester.tap(userIcon.first); await tester.pump(); // Initial pump await tester.pump(const Duration(milliseconds: 100)); // Allow navigation expect(find.byIcon(Icons.settings), findsWidgets); }); }); }