You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
12 KiB
304 lines
12 KiB
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 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 and User tabs are visible
|
|
await tester.pumpWidget(createTestWidget());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Check for navigation icons (custom bottom nav, not standard BottomNavigationBar)
|
|
expect(find.byIcon(Icons.home), findsWidgets);
|
|
expect(find.byIcon(Icons.person), findsWidgets);
|
|
|
|
// Check for labels
|
|
expect(find.text('Home'), findsWidgets);
|
|
expect(find.text('User'), findsWidgets);
|
|
|
|
// 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 tab
|
|
final userTab = find.text('User');
|
|
expect(userTab, findsWidgets);
|
|
await tester.tap(userTab);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 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 {
|
|
await tester.pumpWidget(createTestWidget());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Settings icon should be in AppBar actions
|
|
expect(find.byIcon(Icons.settings), findsWidgets);
|
|
});
|
|
|
|
testWidgets('settings icon is tappable and triggers navigation', (WidgetTester tester) async {
|
|
await tester.pumpWidget(createTestWidget());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Find settings icon in AppBar
|
|
final settingsIcons = find.byIcon(Icons.settings);
|
|
expect(settingsIcons, findsWidgets);
|
|
|
|
// Verify we're on Home screen initially
|
|
expect(find.text('Home'), findsWidgets);
|
|
|
|
// Tap the first 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('all screens have 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
|
|
|
|
// Check Home screen
|
|
expect(find.byIcon(Icons.settings), findsWidgets);
|
|
|
|
// Navigate to Recipes (only visible when logged in)
|
|
final recipesTab = find.text('Recipes');
|
|
if (recipesTab.evaluate().isNotEmpty) {
|
|
await tester.tap(recipesTab);
|
|
await tester.pump(); // Initial pump
|
|
await tester.pump(const Duration(milliseconds: 100)); // Allow navigation
|
|
expect(find.byIcon(Icons.settings), findsWidgets);
|
|
}
|
|
|
|
// Navigate to Favourites (only visible when logged in)
|
|
final favouritesTab = find.text('Favourites');
|
|
if (favouritesTab.evaluate().isNotEmpty) {
|
|
await tester.tap(favouritesTab);
|
|
await tester.pump(); // Initial pump
|
|
await tester.pump(const Duration(milliseconds: 100)); // Allow navigation
|
|
expect(find.byIcon(Icons.settings), findsWidgets);
|
|
}
|
|
|
|
// Navigate to User
|
|
await tester.tap(find.text('User'));
|
|
await tester.pump(); // Initial pump
|
|
await tester.pump(const Duration(milliseconds: 100)); // Allow navigation
|
|
expect(find.byIcon(Icons.settings), findsWidgets);
|
|
});
|
|
});
|
|
}
|