MotorSignal-App/test/create_club_screen_test.dart

234 lines
7.7 KiB
Dart

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<String> takenSlugs;
Club? lastCreatedClub;
bool shouldFailSlugTaken;
FakeCreateClubService({
this.takenSlugs = const {},
this.shouldFailSlugTaken = false,
});
@override
Future<bool> isSlugAvailable(String slug) async {
final validation = ClubSlugValidator.validate(slug);
if (!validation.isValid) return false;
return !takenSlugs.contains(validation.normalizedSlug!);
}
@override
Future<Club> 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);
});
});
}