## 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>
686 lines
22 KiB
Dart
686 lines
22 KiB
Dart
/// Tests unitaires pour EvenementsBloc
|
|
library evenements_bloc_test;
|
|
|
|
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/events/bloc/evenements_bloc.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/bloc/evenements_event.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/bloc/evenements_state.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/data/repositories/evenement_repository_impl.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_events.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_event_by_id.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/create_event.dart' as uc;
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/update_event.dart' as uc;
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/delete_event.dart' as uc;
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/register_for_event.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/cancel_registration.dart';
|
|
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_event_participants.dart';
|
|
|
|
@GenerateMocks([
|
|
GetEvents,
|
|
GetEventById,
|
|
uc.CreateEvent,
|
|
uc.UpdateEvent,
|
|
uc.DeleteEvent,
|
|
RegisterForEvent,
|
|
CancelRegistration,
|
|
GetEventParticipants,
|
|
IEvenementRepository,
|
|
])
|
|
import 'evenements_bloc_test.mocks.dart';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
final _now = DateTime(2025, 6, 15);
|
|
final _later = DateTime(2025, 6, 16);
|
|
|
|
EvenementModel _makeEvenement({int id = 1, String titre = 'Réunion mensuelle'}) =>
|
|
EvenementModel(
|
|
id: id,
|
|
titre: titre,
|
|
dateDebut: _now,
|
|
dateFin: _later,
|
|
);
|
|
|
|
EvenementSearchResult _makeSearchResult(List<EvenementModel> items) =>
|
|
EvenementSearchResult(
|
|
evenements: items,
|
|
total: items.length,
|
|
page: 0,
|
|
size: 20,
|
|
totalPages: items.isEmpty ? 0 : 1,
|
|
);
|
|
|
|
DioException _makeDioException(int statusCode) => DioException(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
response: Response(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
statusCode: statusCode,
|
|
),
|
|
type: DioExceptionType.badResponse,
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void main() {
|
|
late MockGetEvents mockGetEvents;
|
|
late MockGetEventById mockGetEventById;
|
|
late MockCreateEvent mockCreateEvent;
|
|
late MockUpdateEvent mockUpdateEvent;
|
|
late MockDeleteEvent mockDeleteEvent;
|
|
late MockRegisterForEvent mockRegisterForEvent;
|
|
late MockCancelRegistration mockCancelRegistration;
|
|
late MockGetEventParticipants mockGetEventParticipants;
|
|
late MockIEvenementRepository mockRepository;
|
|
|
|
EvenementsBloc buildBloc() => EvenementsBloc(
|
|
mockGetEvents,
|
|
mockGetEventById,
|
|
mockCreateEvent,
|
|
mockUpdateEvent,
|
|
mockDeleteEvent,
|
|
mockRegisterForEvent,
|
|
mockCancelRegistration,
|
|
mockGetEventParticipants,
|
|
mockRepository,
|
|
);
|
|
|
|
setUp(() {
|
|
mockGetEvents = MockGetEvents();
|
|
mockGetEventById = MockGetEventById();
|
|
mockCreateEvent = MockCreateEvent();
|
|
mockUpdateEvent = MockUpdateEvent();
|
|
mockDeleteEvent = MockDeleteEvent();
|
|
mockRegisterForEvent = MockRegisterForEvent();
|
|
mockCancelRegistration = MockCancelRegistration();
|
|
mockGetEventParticipants = MockGetEventParticipants();
|
|
mockRepository = MockIEvenementRepository();
|
|
});
|
|
|
|
// ---- initial state -------------------------------------------------------
|
|
|
|
test('initial state is EvenementsInitial', () {
|
|
final bloc = buildBloc();
|
|
expect(bloc.state, isA<EvenementsInitial>());
|
|
bloc.close();
|
|
});
|
|
|
|
// ---- LoadEvenements ------------------------------------------------------
|
|
|
|
group('LoadEvenements', () {
|
|
final evenement = _makeEvenement();
|
|
final searchResult = _makeSearchResult([evenement]);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Loaded] on success',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: anyNamed('recherche')))
|
|
.thenAnswer((_) async => searchResult);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenements()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsLoaded>()
|
|
.having((s) => s.evenements.length, 'count', 1)
|
|
.having((s) => s.total, 'total', 1),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Refreshing, Loaded] when refresh=true and state is EvenementsLoaded',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: anyNamed('recherche')))
|
|
.thenAnswer((_) async => _makeSearchResult([]));
|
|
return buildBloc();
|
|
},
|
|
seed: () => EvenementsLoaded(
|
|
evenements: [evenement],
|
|
total: 1,
|
|
totalPages: 1,
|
|
),
|
|
act: (b) => b.add(const LoadEvenements(refresh: true)),
|
|
expect: () => [
|
|
isA<EvenementsRefreshing>()
|
|
.having((s) => s.currentEvenements.length, 'current', 1),
|
|
isA<EvenementsLoaded>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on generic exception',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: anyNamed('recherche')))
|
|
.thenThrow(Exception('network'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenements()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, NetworkError] on DioException (non-auth)',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: anyNamed('recherche')))
|
|
.thenThrow(_makeDioException(500));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenements()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsNetworkError>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementsError] on 401 DioException',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: anyNamed('recherche')))
|
|
.thenThrow(_makeDioException(401));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenements()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'uses recherche parameter',
|
|
build: () {
|
|
when(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: 'gala'))
|
|
.thenAnswer((_) async => _makeSearchResult([evenement]));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenements(recherche: 'gala')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsLoaded>(),
|
|
],
|
|
verify: (_) => verify(mockGetEvents(
|
|
page: anyNamed('page'),
|
|
size: anyNamed('size'),
|
|
recherche: 'gala')),
|
|
);
|
|
});
|
|
|
|
// ---- LoadEvenementById ---------------------------------------------------
|
|
|
|
group('LoadEvenementById', () {
|
|
final evenement = _makeEvenement();
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementDetailLoaded] when found',
|
|
build: () {
|
|
when(mockGetEventById.call(any)).thenAnswer((_) async => evenement);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementById('1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementDetailLoaded>()
|
|
.having((s) => s.evenement.id, 'id', 1),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementsError(404)] when not found (null)',
|
|
build: () {
|
|
when(mockGetEventById.call(any)).thenAnswer((_) async => null);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementById('missing')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>()
|
|
.having((s) => s.code, 'code', '404'),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on exception',
|
|
build: () {
|
|
when(mockGetEventById.call(any)).thenThrow(Exception('server error'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementById('1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- CreateEvenement -----------------------------------------------------
|
|
|
|
group('CreateEvenement', () {
|
|
final evenement = _makeEvenement(id: 99);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementCreated] on success',
|
|
build: () {
|
|
when(mockCreateEvent.call(any)).thenAnswer((_) async => evenement);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(CreateEvenement(evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementCreated>()
|
|
.having((s) => s.evenement.id, 'id', 99),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementsValidationError] on 400 DioException',
|
|
build: () {
|
|
final dioEx = DioException(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
response: Response(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
statusCode: 400,
|
|
data: {
|
|
'errors': {'titre': 'obligatoire'}
|
|
},
|
|
),
|
|
type: DioExceptionType.badResponse,
|
|
);
|
|
when(mockCreateEvent.call(any)).thenThrow(dioEx);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(CreateEvenement(evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsValidationError>()
|
|
.having((s) => s.code, 'code', '400'),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on generic exception',
|
|
build: () {
|
|
when(mockCreateEvent.call(any)).thenThrow(Exception('creation failed'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(CreateEvenement(evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- UpdateEvenement -----------------------------------------------------
|
|
|
|
group('UpdateEvenement', () {
|
|
final evenement = _makeEvenement();
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementUpdated] on success',
|
|
build: () {
|
|
when(mockUpdateEvent.call(any, any)).thenAnswer((_) async => evenement);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(UpdateEvenement('1', evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementUpdated>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, ValidationError] on 400',
|
|
build: () {
|
|
final dioEx = DioException(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
response: Response(
|
|
requestOptions: RequestOptions(path: '/test'),
|
|
statusCode: 400,
|
|
data: {'errors': {}},
|
|
),
|
|
type: DioExceptionType.badResponse,
|
|
);
|
|
when(mockUpdateEvent.call(any, any)).thenThrow(dioEx);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(UpdateEvenement('1', evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsValidationError>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on generic exception',
|
|
build: () {
|
|
when(mockUpdateEvent.call(any, any))
|
|
.thenThrow(Exception('update failed'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(UpdateEvenement('1', evenement)),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- DeleteEvenement -----------------------------------------------------
|
|
|
|
group('DeleteEvenement', () {
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementDeleted] on success',
|
|
build: () {
|
|
when(mockDeleteEvent.call(any)).thenAnswer((_) async => null);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const DeleteEvenement('1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementDeleted>()
|
|
.having((s) => s.id, 'id', '1'),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on failure',
|
|
build: () {
|
|
when(mockDeleteEvent.call(any)).thenThrow(Exception('delete failed'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const DeleteEvenement('1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- LoadEvenementsAVenir ------------------------------------------------
|
|
|
|
group('LoadEvenementsAVenir', () {
|
|
final result = _makeSearchResult([_makeEvenement()]);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Loaded] on success',
|
|
build: () {
|
|
when(mockRepository.getEvenementsAVenir(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenAnswer((_) async => result);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsAVenir()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsLoaded>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, NetworkError] on DioException',
|
|
build: () {
|
|
when(mockRepository.getEvenementsAVenir(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenThrow(_makeDioException(503));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsAVenir()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsNetworkError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- LoadEvenementsEnCours -----------------------------------------------
|
|
|
|
group('LoadEvenementsEnCours', () {
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Loaded] on success',
|
|
build: () {
|
|
when(mockRepository.getEvenementsEnCours(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenAnswer((_) async => _makeSearchResult([_makeEvenement()]));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsEnCours()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsLoaded>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on generic exception',
|
|
build: () {
|
|
when(mockRepository.getEvenementsEnCours(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenThrow(Exception('server error'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsEnCours()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- LoadEvenementsPasses ------------------------------------------------
|
|
|
|
group('LoadEvenementsPasses', () {
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Loaded] on success',
|
|
build: () {
|
|
when(mockRepository.getEvenementsPasses(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenAnswer((_) async => _makeSearchResult([_makeEvenement()]));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsPasses()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsLoaded>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on generic exception',
|
|
build: () {
|
|
when(mockRepository.getEvenementsPasses(
|
|
page: anyNamed('page'), size: anyNamed('size')))
|
|
.thenThrow(Exception('server error'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsPasses()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- InscrireEvenement ---------------------------------------------------
|
|
|
|
group('InscrireEvenement', () {
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementInscrit] on success',
|
|
build: () {
|
|
when(mockRegisterForEvent.call(any)).thenAnswer((_) async => null);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const InscrireEvenement('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementInscrit>()
|
|
.having((s) => s.evenementId, 'evenementId', 'evt-1'),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on failure',
|
|
build: () {
|
|
when(mockRegisterForEvent.call(any))
|
|
.thenThrow(Exception('registration failed'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const InscrireEvenement('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, NetworkError] on non-auth DioException',
|
|
build: () {
|
|
when(mockRegisterForEvent.call(any))
|
|
.thenThrow(_makeDioException(409));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const InscrireEvenement('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsNetworkError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- DesinscrireEvenement ------------------------------------------------
|
|
|
|
group('DesinscrireEvenement', () {
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementDesinscrit] on success',
|
|
build: () {
|
|
when(mockCancelRegistration.call(any)).thenAnswer((_) async => null);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const DesinscrireEvenement('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementDesinscrit>()
|
|
.having((s) => s.evenementId, 'evenementId', 'evt-1'),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on failure',
|
|
build: () {
|
|
when(mockCancelRegistration.call(any))
|
|
.thenThrow(Exception('cancel failed'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const DesinscrireEvenement('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- LoadParticipants ----------------------------------------------------
|
|
|
|
group('LoadParticipants', () {
|
|
final participants = [
|
|
{'id': 'm1', 'nom': 'Dupont', 'statut': 'CONFIRME'},
|
|
{'id': 'm2', 'nom': 'Martin', 'statut': 'EN_ATTENTE'},
|
|
];
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, ParticipantsLoaded] on success',
|
|
build: () {
|
|
when(mockGetEventParticipants.call(any))
|
|
.thenAnswer((_) async => participants);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadParticipants('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<ParticipantsLoaded>()
|
|
.having((s) => s.evenementId, 'evenementId', 'evt-1')
|
|
.having((s) => s.participants.length, 'count', 2),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on failure',
|
|
build: () {
|
|
when(mockGetEventParticipants.call(any))
|
|
.thenThrow(Exception('participants error'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadParticipants('evt-1')),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
|
|
// ---- LoadEvenementsStats -------------------------------------------------
|
|
|
|
group('LoadEvenementsStats', () {
|
|
final stats = {'total': 20, 'aVenir': 5, 'enCours': 3};
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, EvenementsStatsLoaded] on success',
|
|
build: () {
|
|
when(mockRepository.getEvenementsStats())
|
|
.thenAnswer((_) async => stats);
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsStats()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsStatsLoaded>()
|
|
.having((s) => s.stats['total'], 'total', 20),
|
|
],
|
|
);
|
|
|
|
blocTest<EvenementsBloc, EvenementsState>(
|
|
'emits [Loading, Error] on exception',
|
|
build: () {
|
|
when(mockRepository.getEvenementsStats())
|
|
.thenThrow(Exception('stats error'));
|
|
return buildBloc();
|
|
},
|
|
act: (b) => b.add(const LoadEvenementsStats()),
|
|
expect: () => [
|
|
isA<EvenementsLoading>(),
|
|
isA<EvenementsError>(),
|
|
],
|
|
);
|
|
});
|
|
}
|