234 lines
7.7 KiB
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);
|
|
});
|
|
});
|
|
}
|