feat: BLoC tests complets + sécurité production + freerasp 7.5.1 migration
## Tests BLoC (Task P2.4 Mobile) - 25 nouveaux fichiers *_bloc_test.dart + mocks générés (build_runner) - Features couvertes : authentication, admin_users, adhesions, backup, communication/messaging, contributions, dashboard, finance (approval/budget), events, explore/network, feed, logs_monitoring, notifications, onboarding, organizations (switcher/types/CRUD), profile, reports, settings, solidarity - ~380 tests, > 80% coverage BLoCs ## Sécurité Production (Task P2.2) - lib/core/security/app_integrity_service.dart (freerasp 7.5.1) - Migration API breaking changes freerasp 7.5.1 : - onRootDetected → onPrivilegedAccess - onDebuggerDetected → onDebug - onSignatureDetected → onAppIntegrity - onHookDetected → onHooks - onEmulatorDetected → onSimulator - onUntrustedInstallationSourceDetected → onUnofficialStore - onDeviceBindingDetected → onDeviceBinding - onObfuscationIssuesDetected → onObfuscationIssues - Talsec.start() split → start() + attachListener() - const AndroidConfig/IOSConfig → final (constructors call ConfigVerifier) - supportedAlternativeStores → supportedStores ## Pubspec - bloc_test: ^9.1.7 → ^10.0.0 (compat flutter_bloc ^9.0.0) - freerasp 7.5.1 ## Config - android/app/build.gradle : ajustements release - lib/core/config/environment.dart : URLs API actualisées - lib/main.dart + app_router : intégrations sécurité/BLoC ## Cleanup - Suppression docs intermédiaires (TACHES_*.md, TASK_*_COMPLETION_REPORT.md, TESTS_UNITAIRES_PROGRESS.md) - .g.dart régénérés (json_serializable) - .mocks.dart régénérés (mockito) ## Résultat - 142 fichiers, +27 596 insertions - Toutes les tâches P2 mobile complétées Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
633
test/features/profile/bloc/profile_bloc_test.dart
Normal file
633
test/features/profile/bloc/profile_bloc_test.dart
Normal file
@@ -0,0 +1,633 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'package:unionflow_mobile_apps/features/profile/presentation/bloc/profile_bloc.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/get_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([GetProfile, UpdateProfile, IProfileRepository])
|
||||
import 'profile_bloc_test.mocks.dart';
|
||||
|
||||
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
||||
|
||||
MembreCompletModel _membre({String id = 'membre-1'}) => MembreCompletModel(
|
||||
id: id,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@test.com',
|
||||
);
|
||||
|
||||
DioException _dioError(int statusCode) => DioException(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
response: Response(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
statusCode: statusCode,
|
||||
),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
|
||||
DioException _networkError() => DioException(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
type: DioExceptionType.connectionTimeout,
|
||||
);
|
||||
|
||||
// ─── Tests ───────────────────────────────────────────────────────────────────
|
||||
|
||||
void main() {
|
||||
late ProfileBloc bloc;
|
||||
late MockGetProfile mockGetProfile;
|
||||
late MockUpdateProfile mockUpdateProfile;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockGetProfile = MockGetProfile();
|
||||
mockUpdateProfile = MockUpdateProfile();
|
||||
mockRepository = MockIProfileRepository();
|
||||
|
||||
bloc = ProfileBloc(mockGetProfile, mockUpdateProfile, mockRepository);
|
||||
});
|
||||
|
||||
tearDown(() => bloc.close());
|
||||
|
||||
// ─── Initial state ──────────────────────────────────────────────────────────
|
||||
|
||||
test('initial state is ProfileInitial', () {
|
||||
expect(bloc.state, isA<ProfileInitial>());
|
||||
});
|
||||
|
||||
// ─── LoadMe ─────────────────────────────────────────────────────────────────
|
||||
|
||||
group('LoadMe', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileLoaded] on success',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileLoaded>()
|
||||
.having((s) => s.membre.id, 'id', 'membre-1')
|
||||
.having((s) => s.membre.email, 'email', 'jean.dupont@test.com'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileNotFound] when getProfile returns null',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenAnswer((_) async => null);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileNotFound>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (401)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Non autorisé'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (404)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(404));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('non trouvé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (500)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(500));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur serveur')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on network timeout',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_networkError());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Délai de connexion'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(Exception('Unexpected failure'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Erreur lors du chargement'),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── LoadMyProfile ──────────────────────────────────────────────────────────
|
||||
|
||||
group('LoadMyProfile', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileLoaded] on success',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail('jean@test.com'))
|
||||
.thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileLoaded>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileNotFound] when repository returns null',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenAnswer((_) async => null);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('unknown@test.com')),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileNotFound>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenThrow(_dioError(404));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileError>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenThrow(Exception('DB error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur lors du chargement')),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── UpdateMyProfile ────────────────────────────────────────────────────────
|
||||
|
||||
group('UpdateMyProfile', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileUpdated] when state is ProfileLoaded on success',
|
||||
build: () {
|
||||
final updatedMembre = _membre(id: 'membre-1');
|
||||
when(mockUpdateProfile('membre-1', any))
|
||||
.thenAnswer((_) async => updatedMembre);
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
isA<ProfileUpdated>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdated] from ProfileInitial (no ProfileUpdating)',
|
||||
build: () {
|
||||
when(mockUpdateProfile('membre-1', any))
|
||||
.thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
// No seed → initial state is ProfileInitial (not ProfileLoaded)
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [isA<ProfileUpdated>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileLoaded, ProfileError] on DioException when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any)).thenThrow(_dioError(400));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>(),
|
||||
isA<ProfileLoaded>(), // restored previous state
|
||||
isA<ProfileError>(),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileError] on DioException when state is not ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any)).thenThrow(_dioError(500));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [isA<ProfileError>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileError] on generic exception when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any))
|
||||
.thenThrow(Exception('Serialization error'));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>(),
|
||||
isA<ProfileLoaded>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('mise à jour'),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── ChangePassword ─────────────────────────────────────────────────────────
|
||||
|
||||
group('ChangePassword', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, PasswordChanged] on success from ProfileInitial',
|
||||
build: () {
|
||||
when(mockRepository.changePassword('membre-1', 'old123', 'new456'))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old123',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [isA<PasswordChanging>(), isA<PasswordChanged>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'restores ProfileLoaded after PasswordChanged when previous state was ProfileLoaded',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old123',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<PasswordChanged>(),
|
||||
isA<ProfileLoaded>(), // restored
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'wrong',
|
||||
newPassword: 'new',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError, ProfileLoaded] on DioException when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old',
|
||||
newPassword: 'new',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>(),
|
||||
isA<ProfileLoaded>(), // restored
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(Exception('Wrong old password'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'wrong',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>(),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'strips Exception: prefix from generic error message',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(Exception('Custom error message'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'x',
|
||||
newPassword: 'y',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
isNot(contains('Exception:')),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── DeleteAccount ──────────────────────────────────────────────────────────
|
||||
|
||||
group('DeleteAccount', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, AccountDeleted] on success',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount('membre-1'))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [isA<AccountDeleting>(), isA<AccountDeleted>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on DioException (401)',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Non autorisé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on network error',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_networkError());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Délai de connexion'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any))
|
||||
.thenThrow(Exception('Server unreachable'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [isA<AccountDeleting>(), isA<ProfileError>()],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── _networkErrorMessage coverage ─────────────────────────────────────────
|
||||
|
||||
group('_networkErrorMessage DioExceptionType coverage', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'sendTimeout returns Délai de connexion dépassé',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.sendTimeout,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Délai de connexion')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'receiveTimeout returns Délai de connexion dépassé',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.receiveTimeout,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Délai de connexion')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'unknown DioExceptionType returns Erreur réseau',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.unknown,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur réseau')),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── State equality ─────────────────────────────────────────────────────────
|
||||
|
||||
group('ProfileState equality', () {
|
||||
test('ProfileLoaded with same membre are equal', () {
|
||||
final m = _membre();
|
||||
expect(ProfileLoaded(m), equals(ProfileLoaded(m)));
|
||||
});
|
||||
|
||||
test('ProfileError with same message are equal', () {
|
||||
const s1 = ProfileError('error');
|
||||
const s2 = ProfileError('error');
|
||||
expect(s1, equals(s2));
|
||||
});
|
||||
|
||||
test('ProfileError with different messages are not equal', () {
|
||||
expect(const ProfileError('a'), isNot(equals(const ProfileError('b'))));
|
||||
});
|
||||
|
||||
test('ProfileUpdating contains membre in props', () {
|
||||
final m = _membre();
|
||||
expect(ProfileUpdating(m).props, contains(m));
|
||||
});
|
||||
|
||||
test('ProfileUpdated contains membre in props', () {
|
||||
final m = _membre();
|
||||
expect(ProfileUpdated(m).props, contains(m));
|
||||
});
|
||||
});
|
||||
|
||||
// ─── ProfileEvent equality ──────────────────────────────────────────────────
|
||||
|
||||
group('ProfileEvent equality', () {
|
||||
test('LoadMe events are equal', () {
|
||||
expect(const LoadMe(), equals(const LoadMe()));
|
||||
});
|
||||
|
||||
test('LoadMyProfile with same email are equal', () {
|
||||
expect(
|
||||
const LoadMyProfile('a@b.com'),
|
||||
equals(const LoadMyProfile('a@b.com')),
|
||||
);
|
||||
});
|
||||
|
||||
test('ChangePassword props are correct', () {
|
||||
const event = ChangePassword(
|
||||
membreId: 'm-1',
|
||||
oldPassword: 'old',
|
||||
newPassword: 'new',
|
||||
);
|
||||
expect(event.props, containsAll(['m-1', 'old', 'new']));
|
||||
});
|
||||
|
||||
test('DeleteAccount props are correct', () {
|
||||
const event = DeleteAccount('m-99');
|
||||
expect(event.props, contains('m-99'));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user