import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/core/theme/theme_extensions.dart'; import 'package:motorcycle_dash/features/social/clubs/create_club_screen.dart'; import 'package:motorcycle_dash/services/auth/auth_providers.dart'; import 'package:motorcycle_dash/services/auth/auth_user.dart'; import 'package:motorcycle_dash/services/clubs/club_models.dart'; import 'package:motorcycle_dash/services/clubs/club_providers.dart'; import 'package:motorcycle_dash/services/clubs/club_service.dart'; import 'package:motorcycle_dash/services/clubs/club_slug_validator.dart'; class FakeCreateClubService extends ClubService { final Set takenSlugs; Club? lastCreatedClub; bool shouldFailSlugTaken; FakeCreateClubService({ this.takenSlugs = const {}, this.shouldFailSlugTaken = false, }); @override Future isSlugAvailable(String slug) async { final validation = ClubSlugValidator.validate(slug); if (!validation.isValid) return false; return !takenSlugs.contains(validation.normalizedSlug!); } @override Future createClub({ required String name, required String slug, String description = '', ClubVisibility visibility = ClubVisibility.public, ClubAdmissionPolicy admissionPolicy = ClubAdmissionPolicy.request, String? logoUrl, String? bannerUrl, String? homeRegion, String? motorcycleFocus, String? callerUid, }) async { if (shouldFailSlugTaken || takenSlugs.contains(slug)) { throw const ClubOperationException( 'This club slug is already taken.', code: 'slug-taken', ); } final club = Club( id: 'created_club_123', name: name, slug: slug, description: description, visibility: visibility, admissionPolicy: admissionPolicy, ownerUid: callerUid ?? 'user_1', memberCount: 1, createdAt: DateTime.now(), updatedAt: DateTime.now(), homeRegion: homeRegion, motorcycleFocus: motorcycleFocus, ); lastCreatedClub = club; return club; } } void main() { group('CreateClubScreen Widget Tests', () { testWidgets('renders creation form and handles creation flow', (tester) async { tester.view.physicalSize = const Size(1000, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); const user = AuthUser(uid: 'alice', isAnonymous: false); final fakeService = FakeCreateClubService(); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => user), clubServiceProvider.overrideWithValue(fakeService), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const CreateClubScreen(), ), ), ); expect(find.text('Create Club'), findsWidgets); expect(find.text('Found a Motorcycle Club'), findsOneWidget); expect(find.text('Club Name *'), findsOneWidget); expect(find.text('Club Handle / Slug *'), findsOneWidget); // Fill in Name await tester.enterText( find.widgetWithText(TextFormField, 'Club Name *'), 'Smoky Mountain MC', ); // Fill in Slug await tester.enterText( find.widgetWithText(TextFormField, 'Club Handle / Slug *'), 'smoky-mountain-mc', ); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); // debounce expect(find.text('Handle is available'), findsOneWidget); final submitButton = find.byKey(const ValueKey('create_club_submit_button')); expect(submitButton, findsOneWidget); await tester.tap(submitButton); await tester.pump(); expect(fakeService.lastCreatedClub, isNotNull); expect(fakeService.lastCreatedClub!.name, equals('Smoky Mountain MC')); expect(fakeService.lastCreatedClub!.slug, equals('smoky-mountain-mc')); }); testWidgets('handles slug-taken conflict gracefully', (tester) async { tester.view.physicalSize = const Size(1000, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); const user = AuthUser(uid: 'alice', isAnonymous: false); final fakeService = FakeCreateClubService(shouldFailSlugTaken: true); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => user), clubServiceProvider.overrideWithValue(fakeService), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const CreateClubScreen(), ), ), ); await tester.enterText( find.widgetWithText(TextFormField, 'Club Name *'), 'Contested Name', ); await tester.enterText( find.widgetWithText(TextFormField, 'Club Handle / Slug *'), 'contested-slug', ); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); final submitButton = find.byKey(const ValueKey('create_club_submit_button')); expect(submitButton, findsOneWidget); await tester.tap(submitButton); await tester.pumpAndSettle(); expect( find.text('This handle was just taken by another club. Please pick a different handle.'), findsOneWidget, ); }); testWidgets('renders across responsive viewports and keyboard constraints without overflow', (tester) async { const user = AuthUser(uid: 'alice', isAnonymous: false); final fakeService = FakeCreateClubService(); const viewports = [ Size(412, 915), // Standard portrait Size(915, 412), // Landscape Size(412, 400), // Keyboard-constrained height ]; for (final vp in viewports) { tester.view.physicalSize = vp; tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => user), clubServiceProvider.overrideWithValue(fakeService), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const CreateClubScreen(), ), ), ); await tester.pumpAndSettle(); expect(tester.takeException(), isNull, reason: 'No layout overflow on $vp'); expect(find.text('Found a Motorcycle Club'), findsOneWidget); expect(find.byType(ListView), findsOneWidget); } }); testWidgets('renders account required screen when unauthenticated', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => null), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const CreateClubScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('Account Required'), findsOneWidget); expect( find.text('Sign in or create a MotorSignal account to join and manage riding clubs.'), findsOneWidget, ); expect(find.text('Sign In / Register'), findsOneWidget); }); }); }