diff --git a/lib/core/models/github_json_model.dart b/lib/core/models/github_json_model.dart index c090aeb35..fb4b25e84 100644 --- a/lib/core/models/github_json_model.dart +++ b/lib/core/models/github_json_model.dart @@ -38,40 +38,34 @@ class GithubJsonModel { /// Optional fields (eventDates, venue, description) will be null if not provided factory GithubJsonModel.fromJson(Map json) { List events = (json['events'] != null) - ? (json['events'] as List?) - ?.map((item) => Event.fromJson(item)) - .toList() ?? - [] + ? (json['events'] as List) + .map((item) => Event.fromJson(item)) + .toList() : []; List tracks = (json['tracks'] != null) - ? (json['tracks'] as List?) - ?.map((item) => Track.fromJson(item)) - .toList() ?? - [] + ? (json['tracks'] as List) + .map((item) => Track.fromJson(item)) + .toList() : []; List sessions = (json['sessions'] != null) - ? (json['sessions'] as List?) - ?.map((item) => Session.fromJson(item)) - .toList() ?? - [] + ? (json['sessions'] as List) + .map((item) => Session.fromJson(item)) + .toList() : []; List agendadays = (json['agendadays'] != null) - ? (json['agendadays'] as List?) - ?.map((item) => AgendaDay.fromJson(item)) - .toList() ?? - [] + ? (json['agendadays'] as List) + .map((item) => AgendaDay.fromJson(item)) + .toList() : []; List sponsors = (json['sponsors'] != null) - ? (json['sponsors'] as List?) - ?.map((item) => Sponsor.fromJson(item)) - .toList() ?? - [] + ? (json['sponsors'] as List) + .map((item) => Sponsor.fromJson(item)) + .toList() : []; List speakers = (json['speakers'] != null) - ? (json['speakers'] as List?) - ?.map((item) => Speaker.fromJson(item)) - .toList() ?? - [] + ? (json['speakers'] as List) + .map((item) => Speaker.fromJson(item)) + .toList() : []; return GithubJsonModel( events: events, diff --git a/lib/core/models/session_type.dart b/lib/core/models/session_type.dart index 3ac4b2647..29ae8305d 100644 --- a/lib/core/models/session_type.dart +++ b/lib/core/models/session_type.dart @@ -53,10 +53,4 @@ abstract class SessionTypes { return 'other'; } } - - static List allLabels(BuildContext context) { - return SessionType.values - .map((type) => getSessionTypeLabel(context, type.name)) - .toList(); - } } diff --git a/lib/core/routing/app_router.dart b/lib/core/routing/app_router.dart index a0d2f1d0a..463a9ea4a 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -88,9 +88,6 @@ class AppRouter { path: agendaFormPath, name: agendaFormName, builder: (context, state) { - if (state.extra == null) { - return AgendaFormScreen(); - } final agendaFormData = state.extra as AgendaFormData; return AgendaFormScreen(data: agendaFormData); }, diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index 9c434e0cb..ecd448352 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -198,6 +198,10 @@ "onLive": "On Live", "onlineNow": "En línia ara", "selectSpeaker": "Selecciona un ponent", - "noLiveStreamAvailable": "No hi ha directes disponibles" - + "noLiveStreamAvailable": "No hi ha directes disponibles", + "confirm": "Confirmar", + "deleteOptionMessage": "Vols eliminar aquesta opció?", + "delete": "Eliminar", + "addOption": "Afegir opció", + "optionHint": "Opció" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4f5d718ca..8e442b9c7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -194,5 +194,10 @@ "onLive": "On Live", "selectSpeaker": "Select a speaker", "onlineNow": "Online Now", - "noLiveStreamAvailable": "No live streams available" + "noLiveStreamAvailable": "No live streams available", + "confirm": "Confirm", + "deleteOptionMessage": "Do you want to delete this option?", + "delete": "Delete", + "addOption": "Add option", + "optionHint": "Option" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ae9fb018b..980f25f6e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -178,5 +178,10 @@ "loadingTitle": "Cargando...", "logout": "Cerrar Sesión", "onLive": "En Vivo", - "noLiveStreamAvailable": "No hay transmisión en vivo disponible" + "noLiveStreamAvailable": "No hay transmisión en vivo disponible", + "confirm": "Confirmar", + "deleteOptionMessage": "¿Deseas eliminar esta opción?", + "delete": "Eliminar", + "addOption": "Añadir opción", + "optionHint": "Opción" } \ No newline at end of file diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index dd96fd7a8..8bc9a58f6 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -120,7 +120,7 @@ "loginTitle": "Saioa hasi", "projectNameLabel": "Proiektuaren izena", "projectNameHint": "Mesedez, sartu proiektuaren izena", - "tokenHintLabel": "Sartu zure bezeroaren sekretua jarraitzeko", + "tokenHintLabel": "Sartu votre bezeroaren sekretua jarraitzeko", "tokenHint": "Mesedez, sartu baliozko GitHub token bat", "unknownAuthError": "Autentifikazio-errore ezezaguna.", "projectNotFoundError": "“{projectName}” proiektua ez da existitzen zure GitHub-eko biltegietan.", @@ -194,6 +194,10 @@ "onLive": "On Live", "selectSpeaker": "Hautatu hizlaria", "onlineNow": "Online Now", - "noLiveStreamAvailable": "Ez dago zuzeneko emankizunik eskuragarri" - + "noLiveStreamAvailable": "Ez dago zuzeneko emankizunik eskuragarri", + "confirm": "Berretsi", + "deleteOptionMessage": "Aukera hau ezabatu nahi duzu?", + "delete": "Ezabatu", + "addOption": "Aukera gehitu", + "optionHint": "Aukera" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d2345e29d..4625eb659 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -195,6 +195,10 @@ "onLive": "On Live", "onlineNow": "En ligne maintenant", "selectSpeaker": "Sélectionnez un intervenant", - "noLiveStreamAvailable": "Aucun direct n'est disponible" - + "noLiveStreamAvailable": "Aucun direct n'est disponible", + "confirm": "Confirmer", + "deleteOptionMessage": "Voulez-vous supprimer cette option ?", + "delete": "Supprimer", + "addOption": "Ajouter une option", + "optionHint": "Option" } \ No newline at end of file diff --git a/lib/l10n/app_gl.arb b/lib/l10n/app_gl.arb index ab1759eb8..709a32f5b 100644 --- a/lib/l10n/app_gl.arb +++ b/lib/l10n/app_gl.arb @@ -194,6 +194,10 @@ "noLiveStreamAvailable": "No hay directos disponibles", "onLive": "On Live", "selectSpeaker": "Selecciona un poñente", - "onlineNow": "En liña agora" - + "onlineNow": "En liña agora", + "confirm": "Confirmar", + "deleteOptionMessage": "Desexas eliminar esta opción?", + "delete": "Eliminar", + "addOption": "Engadir opción", + "optionHint": "Opción" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index d3cdcd47c..8565afbc6 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -205,6 +205,10 @@ "onLive": "On Live", "onlineNow": "Online adesso", "selectSpeaker": "Seleziona un relatore", - "noLiveStreamAvailable": "Non ci sono dirette disponibili" - + "noLiveStreamAvailable": "Non ci sono dirette disponibili", + "confirm": "Conferma", + "deleteOptionMessage": "Vuoi eliminare questa opzione?", + "delete": "Elimina", + "addOption": "Aggiungi opzione", + "optionHint": "Opzione" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 9073de686..12747169e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1183,6 +1183,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'No live streams available'** String get noLiveStreamAvailable; + + /// No description provided for @confirm. + /// + /// In en, this message translates to: + /// **'Confirm'** + String get confirm; + + /// No description provided for @deleteOptionMessage. + /// + /// In en, this message translates to: + /// **'Do you want to delete this option?'** + String get deleteOptionMessage; + + /// No description provided for @delete. + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// No description provided for @addOption. + /// + /// In en, this message translates to: + /// **'Add option'** + String get addOption; + + /// No description provided for @optionHint. + /// + /// In en, this message translates to: + /// **'Option'** + String get optionHint; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ca.dart b/lib/l10n/app_localizations_ca.dart index 453e35bbb..8047b0b86 100644 --- a/lib/l10n/app_localizations_ca.dart +++ b/lib/l10n/app_localizations_ca.dart @@ -566,4 +566,19 @@ class AppLocalizationsCa extends AppLocalizations { @override String get noLiveStreamAvailable => 'No hi ha directes disponibles'; + + @override + String get confirm => 'Confirmar'; + + @override + String get deleteOptionMessage => 'Vols eliminar aquesta opció?'; + + @override + String get delete => 'Eliminar'; + + @override + String get addOption => 'Afegir opció'; + + @override + String get optionHint => 'Opció'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8065f1f12..962a730dd 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -557,4 +557,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noLiveStreamAvailable => 'No live streams available'; + + @override + String get confirm => 'Confirm'; + + @override + String get deleteOptionMessage => 'Do you want to delete this option?'; + + @override + String get delete => 'Delete'; + + @override + String get addOption => 'Add option'; + + @override + String get optionHint => 'Option'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d83b15a3c..225775a98 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -556,4 +556,19 @@ class AppLocalizationsEs extends AppLocalizations { @override String get noLiveStreamAvailable => 'No hay transmisión en vivo disponible'; + + @override + String get confirm => 'Confirmar'; + + @override + String get deleteOptionMessage => '¿Deseas eliminar esta opción?'; + + @override + String get delete => 'Eliminar'; + + @override + String get addOption => 'Añadir opción'; + + @override + String get optionHint => 'Opción'; } diff --git a/lib/l10n/app_localizations_eu.dart b/lib/l10n/app_localizations_eu.dart index bf008404c..8f43e1aae 100644 --- a/lib/l10n/app_localizations_eu.dart +++ b/lib/l10n/app_localizations_eu.dart @@ -375,7 +375,7 @@ class AppLocalizationsEu extends AppLocalizations { String get projectNameHint => 'Mesedez, sartu proiektuaren izena'; @override - String get tokenHintLabel => 'Sartu zure bezeroaren sekretua jarraitzeko'; + String get tokenHintLabel => 'Sartu votre bezeroaren sekretua jarraitzeko'; @override String get tokenHint => 'Mesedez, sartu baliozko GitHub token bat'; @@ -561,4 +561,19 @@ class AppLocalizationsEu extends AppLocalizations { @override String get noLiveStreamAvailable => 'Ez dago zuzeneko emankizunik eskuragarri'; + + @override + String get confirm => 'Berretsi'; + + @override + String get deleteOptionMessage => 'Aukera hau ezabatu nahi duzu?'; + + @override + String get delete => 'Ezabatu'; + + @override + String get addOption => 'Aukera gehitu'; + + @override + String get optionHint => 'Aukera'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 3f9c10aac..8befa089c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -566,4 +566,19 @@ class AppLocalizationsFr extends AppLocalizations { @override String get noLiveStreamAvailable => 'Aucun direct n\'est disponible'; + + @override + String get confirm => 'Confirmer'; + + @override + String get deleteOptionMessage => 'Voulez-vous supprimer cette option ?'; + + @override + String get delete => 'Supprimer'; + + @override + String get addOption => 'Ajouter une option'; + + @override + String get optionHint => 'Option'; } diff --git a/lib/l10n/app_localizations_gl.dart b/lib/l10n/app_localizations_gl.dart index 8f87b985b..5fefbf0a3 100644 --- a/lib/l10n/app_localizations_gl.dart +++ b/lib/l10n/app_localizations_gl.dart @@ -562,4 +562,19 @@ class AppLocalizationsGl extends AppLocalizations { @override String get noLiveStreamAvailable => 'No hay directos disponibles'; + + @override + String get confirm => 'Confirmar'; + + @override + String get deleteOptionMessage => 'Desexas eliminar esta opción?'; + + @override + String get delete => 'Eliminar'; + + @override + String get addOption => 'Engadir opción'; + + @override + String get optionHint => 'Opción'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 2b8e94d3b..b651c48cb 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -565,4 +565,19 @@ class AppLocalizationsIt extends AppLocalizations { @override String get noLiveStreamAvailable => 'Non ci sono dirette disponibili'; + + @override + String get confirm => 'Conferma'; + + @override + String get deleteOptionMessage => 'Vuoi eliminare questa opzione?'; + + @override + String get delete => 'Elimina'; + + @override + String get addOption => 'Aggiungi opzione'; + + @override + String get optionHint => 'Opzione'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index e1c60ae68..fa62add53 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -560,4 +560,19 @@ class AppLocalizationsPt extends AppLocalizations { @override String get noLiveStreamAvailable => 'Não há transmissões ao vivo disponíveis'; + + @override + String get confirm => 'Confirmar'; + + @override + String get deleteOptionMessage => 'Deseja eliminar esta opção?'; + + @override + String get delete => 'Apagar'; + + @override + String get addOption => 'Adicionar opção'; + + @override + String get optionHint => 'Opção'; } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 1be054fd5..2e16c4bab 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -211,6 +211,10 @@ "onlineNow": "Online Agora", "onLive": "On Live", "selectSpeaker": "Selecione um palestrante", - "noLiveStreamAvailable": "Não há transmissões ao vivo disponíveis" - + "noLiveStreamAvailable": "Não há transmissões ao vivo disponíveis", + "confirm": "Confirmar", + "deleteOptionMessage": "Deseja eliminar esta opção?", + "delete": "Apagar", + "addOption": "Adicionar opção", + "optionHint": "Opção" } \ No newline at end of file diff --git a/lib/presentation/ui/screens/agenda/form/agenda_form_screen.dart b/lib/presentation/ui/screens/agenda/form/agenda_form_screen.dart index 20abc7840..5f5618c24 100644 --- a/lib/presentation/ui/screens/agenda/form/agenda_form_screen.dart +++ b/lib/presentation/ui/screens/agenda/form/agenda_form_screen.dart @@ -656,10 +656,6 @@ class _AgendaFormScreenState extends State { return startMinutes < endMinutes; } - bool isTimeSelected(TimeOfDay? time) { - return time != null; // Simpler check - } - void _showAddTrackDialog() { final location = AppLocalizations.of(context)!; final TextEditingController trackNameController = TextEditingController(); @@ -670,16 +666,19 @@ class _AgendaFormScreenState extends State { return AlertDialog( title: Text(location.addRoomTitle), content: TextFormField( + key: Key('track_name_field'), controller: trackNameController, autofocus: true, decoration: InputDecoration(hintText: location.roomNameHint), ), actions: [ TextButton( + key: Key('cancel_button_room'), onPressed: () => Navigator.pop(context), child: Text(location.cancelButton), ), FilledButton( + key: Key('save_room_button'), onPressed: () async { if (trackNameController.text.isNotEmpty) { final String newTrackName = trackNameController.text; diff --git a/lib/presentation/ui/screens/event_collection/event_collection_screen.dart b/lib/presentation/ui/screens/event_collection/event_collection_screen.dart index 74c982b7a..3553cad40 100644 --- a/lib/presentation/ui/screens/event_collection/event_collection_screen.dart +++ b/lib/presentation/ui/screens/event_collection/event_collection_screen.dart @@ -118,6 +118,7 @@ class _EventCollectionScreenState extends State { ), centerTitle: false, title: GestureDetector( + key: const Key('title_key_event_collection'), onTap: () async { _titleTapCount++; @@ -152,7 +153,7 @@ class _EventCollectionScreenState extends State { // will do setup again to refresh configName await viewmodel.setup(); - await _loadConfiguration(); // esto leerá getIt() fresco + await _loadConfiguration(); } }), ), diff --git a/lib/presentation/ui/screens/login/admin_login_screen.dart b/lib/presentation/ui/screens/login/admin_login_screen.dart index 1fd73a4aa..972f19c70 100644 --- a/lib/presentation/ui/screens/login/admin_login_screen.dart +++ b/lib/presentation/ui/screens/login/admin_login_screen.dart @@ -24,6 +24,10 @@ class _AdminLoginScreenState extends State { final ValueNotifier _obscureText = ValueNotifier(true); + GitHub getGithubUser() { + return GitHub(auth: Authentication.withToken(_token.value)); + } + Future _submit(BuildContext context) async { final SecureInfo secureInfo = getIt(); final location = AppLocalizations.of(context)!; @@ -31,7 +35,7 @@ class _AdminLoginScreenState extends State { _formKey.currentState!.save(); // Here you can add the authentication logic try { - var github = GitHub(auth: Authentication.withToken(_token.value)); + var github = getGithubUser(); final user = await github.users.getCurrentUser(); // If authentication is successful and there is no exception: @@ -103,6 +107,7 @@ class _AdminLoginScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( + key: const Key('title_key_event_collection'), location.enterGithubTokenTitle, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.left, @@ -114,6 +119,7 @@ class _AdminLoginScreenState extends State { valueListenable: _obscureText, builder: (context, isObscure, child) { return TextFormField( + key: const Key('token_key_github'), obscuringCharacter: '*', obscureText: isObscure, decoration: InputDecoration( @@ -147,6 +153,7 @@ class _AdminLoginScreenState extends State { const SizedBox(height: 20), Center( child: ElevatedButton( + key: const Key('login_button'), onPressed: () => _submit(context), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/presentation/ui/widgets/add_room.dart b/lib/presentation/ui/widgets/add_room.dart index 95f3be044..5e93ec1b3 100644 --- a/lib/presentation/ui/widgets/add_room.dart +++ b/lib/presentation/ui/widgets/add_room.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:sec/core/models/agenda.dart'; +import '../../../l10n/app_localizations.dart'; + class AddRoom extends StatefulWidget { final List rooms; final void Function(List) editedRooms; @@ -67,16 +69,18 @@ class _AddRoomState extends State { } void _confirmRemoveOption(int index) { + final location = AppLocalizations.of(context)!; + showDialog( context: context, builder: (context) { return AlertDialog( - title: const Text("Confirmar"), - content: const Text("¿Deseas eliminar esta opción?"), + title: Text(location.confirm), + content: Text(location.deleteOptionMessage), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text("Cancelar"), + child: Text(location.cancel), ), TextButton( onPressed: () async { @@ -93,10 +97,7 @@ class _AddRoomState extends State { } Navigator.pop(context); }, - child: const Text( - "Eliminar", - style: TextStyle(color: Colors.red), - ), + child: Text(location.delete, style: TextStyle(color: Colors.red)), ), ], ); @@ -106,6 +107,8 @@ class _AddRoomState extends State { @override Widget build(BuildContext context) { + final location = AppLocalizations.of(context)!; + return ListView.builder( itemCount: _tracks.length + 1, // +1 para incluir el botón itemBuilder: (context, index) { @@ -116,8 +119,8 @@ class _AddRoomState extends State { child: TextButton.icon( onPressed: _addOption, icon: const Icon(Icons.add, color: Colors.purple), - label: const Text( - "Add Option", + label: Text( + location.addOption, style: TextStyle(color: Colors.purple), ), ), diff --git a/test/core/config/config_loader_test.dart b/test/core/config/config_loader_test.dart index 87bfa3f10..762742e29 100644 --- a/test/core/config/config_loader_test.dart +++ b/test/core/config/config_loader_test.dart @@ -30,10 +30,11 @@ void main() { getIt.registerSingleton(mockSecureInfo); getIt.registerSingleton(mockCheckOrg); - // Configuración por defecto para GitHub when(mockGitHub.repositories).thenReturn(mockRepositoriesService); }); - tearDown(() { + tearDown(() async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/assets', null); getIt.reset(); }); @@ -50,7 +51,6 @@ void main() { 'eventForcedToViewUID': null, }; - // Esta es la clave para mockear rootBundle.loadString TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMessageHandler('flutter/assets', (message) async { final Uint8List encoded = utf8.encoder.convert( @@ -67,10 +67,10 @@ void main() { }); group('ConfigLoader - loadOrganization', () { - // Aquí puedes testear la lógica compleja test( 'should return remote config and set error to false on success', () async { + rootBundle.clear(); // 1. Mock Local Bundle final localJson = json.encode({ 'configName': 'Random Organization', @@ -111,5 +111,114 @@ void main() { verify(mockCheckOrg.setError(false)).called(1); }, ); + test( + 'should return remote config and set error to false with res.file == null', + () async { + rootBundle.clear(); + // 1. Mock Local Bundle + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/assets', (message) async { + return utf8.encoder.convert(localJson).buffer.asByteData(); + }); + + // 2. Mock Secure Storage & GitHub + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => GithubData(projectName: 'remote_proj')); + + + final mockContents = RepositoryContents()..file = null; + + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + + await ConfigLoader.loadOrganization(); + // Assert + verify(mockCheckOrg.setError(true)).called(1); + }, + ); + test( + 'should return remote config and set error to false on success when githubItem is null', + () async { + rootBundle.clear(); + // 1. Mock Local Bundle + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/assets', (message) async { + return utf8.encoder.convert(localJson).buffer.asByteData(); + }); + + // 2. Mock Secure Storage & GitHub + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => GithubData()); + + final base64Content = base64.encode(utf8.encode(localJson)); + + final mockContent = GitHubFile()..content = base64Content; + final mockContents = RepositoryContents()..file = mockContent; + + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + + // Act + final result = await ConfigLoader.loadOrganization(); + + // Assert + expect(result.githubUser, 'remote_user'); + verify(mockCheckOrg.setError(false)).called(1); + }, + ); + test( + 'should return remote config with local error if remote fails', + () async { + rootBundle.clear(); + // 1. Mock Local Bundle + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': '', + 'project_name': '', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/assets', (message) async { + return utf8.encoder.convert(localJson).buffer.asByteData(); + }); + + final result = await ConfigLoader.loadOrganization(); + + // Assert + expect(result.githubUser, ''); + verify(mockCheckOrg.setError(true)).called(1); + }, + ); }); } diff --git a/test/core/config/secure_info_test.dart b/test/core/config/secure_info_test.dart index ecaf3cba8..ce4367aac 100644 --- a/test/core/config/secure_info_test.dart +++ b/test/core/config/secure_info_test.dart @@ -69,5 +69,41 @@ void main() { ); }, ); + + test( + 'should return an error saving a token', + () async { + final githubData = GithubData(token: 'token_fake'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + if (methodCall.method == 'read' && + methodCall.arguments['key'] == 'github_service') { + throw Exception(""); + } + return null; + }); + expect( + () => secureInfo.saveGithubKey(githubData), + throwsA(isA()), + ); + }, + ); + test( + 'should return an error trying to remove a token', + () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + if (methodCall.method == 'read' && + methodCall.arguments['key'] == 'github_service') { + throw Exception(""); + } + return null; + }); + expect( + () => secureInfo.removeGithubKey(), + throwsA(isA()), + ); + }, + ); }); } diff --git a/test/core/utils/result_test.dart b/test/core/utils/result_test.dart new file mode 100644 index 000000000..7044b0b80 --- /dev/null +++ b/test/core/utils/result_test.dart @@ -0,0 +1,99 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:sec/core/utils/result.dart'; +import 'package:sec/data/exceptions/exceptions.dart'; + +// Creamos una excepción de prueba que herede de CustomException +class TestException extends CustomException { + const TestException(String message) : super(message); +} + +void main() { + group('Result Class Tests', () { + + group('Ok', () { + test('should create an Ok instance with the correct value', () { + const value = 'Success string'; + const result = Result.ok(value); + + expect(result, isA>()); + expect((result as Ok).value, value); + }); + + test('toString() should return the expected format', () { + const value = 42; + const result = Result.ok(value); + + expect(result.toString(), 'Result.ok(42)'); + }); + + test('should work with complex objects', () { + final mapValue = {'id': 1}; + final result = Result>.ok(mapValue); + + expect((result as Ok).value['id'], 1); + }); + }); + + group('Error', () { + test('should create an Error instance with the correct CustomException', () { + final exception = TestException('Something went wrong'); + final result = Result.error(exception); + + expect(result, isA>()); + expect((result as Error).error, exception); + expect((result as Error).error.message, 'Something went wrong'); + }); + + test('toString() should return the expected format', () { + final exception = TestException('Failure'); + final result = Result.error(exception); + + // El formato depende de cómo sea el toString de TestException/CustomException + expect(result.toString(), contains('Result.error')); + expect(result.toString(), contains('Failure')); + }); + }); + + group('Pattern Matching (Switch)', () { + test('should match Ok pattern correctly', () { + const result = Result.ok('test'); + + String? extractedValue; + + switch (result) { + case Ok(:final value): + extractedValue = value; + case Error(): + extractedValue = 'failed'; + } + + expect(extractedValue, 'test'); + }); + + test('should match Error pattern correctly', () { + final result = Result.error(TestException('error_msg')); + + String? errorMessage; + + switch (result) { + case Ok(): + errorMessage = 'no error'; + case Error(:final error): + errorMessage = error.message; + } + + expect(errorMessage, 'error_msg'); + }); + }); + + group('Type Safety', () { + test('should maintain type integrity', () { + const Result result = Result.ok(10); + + // Esto debería compilar y ser verdadero + expect(result, isA>()); + expect(result, isNot(isA>())); + }); + }); + }); +} diff --git a/test/data/remote_data/common/commons_api_services_test.dart b/test/data/remote_data/common/commons_api_services_test.dart index faf89db48..3662f647b 100644 --- a/test/data/remote_data/common/commons_api_services_test.dart +++ b/test/data/remote_data/common/commons_api_services_test.dart @@ -163,6 +163,213 @@ void main() { throwsA(isA()), ); }); + test('should throw an exception when sha is null', () async { + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + final mockContent = repoContentsFile..content = base64Content; + mockContent.sha = null; + + final mockContents = repoContent..file = mockContent; + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + when(mockContents.file?.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you cant create the file in github', + () async { + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError but you can create the file in github', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + MockContentCreation contentCreation = MockContentCreation(); + MockGitHubFile mockGitHubFile = MockGitHubFile(); + when(mockGitHubFile.content).thenReturn(base64Content); + when(contentCreation.content).thenReturn(mockGitHubFile); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + returnsNormally, + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you have a response.content null', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + MockContentCreation contentCreation = MockContentCreation(); + when(contentCreation.content).thenReturn(null); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when repoContentsFile.sha is null', + () async { + when(repoContentsFile.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + test('updateData returns a different statuscode of 200', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 400)); + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); test('updateData works successfully', () async { secureInfo.saveGithubKey( GithubData(token: "fake_token", projectName: "test_project"), @@ -171,8 +378,12 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); expect( () => commonsServices.updateData( @@ -184,6 +395,31 @@ void main() { returnsNormally, ); }); + test('updateData throws an exception when getToken() is null', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData = GithubData()); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + expect( + () => commonsServices.updateData( + originalData, + data, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); }); // [GROUP] updateDataList group('updateDataList', () { @@ -200,6 +436,237 @@ void main() { throwsA(isA()), ); }); + test('should throw an exception when sha is null', () async { + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + final mockContent = repoContentsFile..content = base64Content; + mockContent.sha = null; + + final mockContents = repoContent..file = mockContent; + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + when(mockContents.file?.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you cant create the file in github', + () async { + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError but you can create the file in github', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + MockContentCreation contentCreation = MockContentCreation(); + MockGitHubFile mockGitHubFile = MockGitHubFile(); + when(mockGitHubFile.content).thenReturn(base64Content); + when(contentCreation.content).thenReturn(mockGitHubFile); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + returnsNormally, + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you have a response.content null', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + MockContentCreation contentCreation = MockContentCreation(); + when(contentCreation.content).thenReturn(null); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when repoContentsFile.sha is null', + () async { + when(repoContentsFile.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + test('updateDataList returns a different statuscode of 200', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 400)); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test('updateDataList returns a 409 statuscode', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 409)); + expect( + () => commonsServices.updateDataList( + originalData, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); test('should updateDataList successfully', () async { secureInfo.saveGithubKey( GithubData(token: "fake_token", projectName: "test_project"), @@ -208,8 +675,12 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); expect( () async => await commonsServices.updateDataList( originalData, @@ -228,7 +699,11 @@ void main() { secureInfo.saveGithubKey(GithubData(token: null, projectName: "")); expect( - () => commonsServices.updateSingleData(data, testPath, commitMessage), + () async => await commonsServices.updateSingleData( + data, + testPath, + commitMessage, + ), throwsA(isA()), ); }); @@ -240,8 +715,12 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); expect( () => commonsServices.updateSingleData(data, testPath, commitMessage), @@ -276,8 +755,12 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); expect( () => commonsServices.removeData( @@ -330,6 +813,244 @@ void main() { throwsA(isA()), ); }); + test('should throw an exception when sha is null', () async { + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + final mockContent = repoContentsFile..content = base64Content; + mockContent.sha = null; + + final mockContents = repoContent..file = mockContent; + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + when(mockContents.file?.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you cant create the file in github', + () async { + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError but you can create the file in github', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + MockContentCreation contentCreation = MockContentCreation(); + MockGitHubFile mockGitHubFile = MockGitHubFile(); + when(mockGitHubFile.content).thenReturn(base64Content); + when(contentCreation.content).thenReturn(mockGitHubFile); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + returnsNormally, + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you have a response.content null', + () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + MockContentCreation contentCreation = MockContentCreation(); + when(contentCreation.content).thenReturn(null); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when repoContentsFile.sha is null', + () async { + when(repoContentsFile.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + test('removeDataList returns a different statuscode of 200', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 400)); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test('removeDataList returns a 409 statuscode', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 409)); + expect( + () => commonsServices.removeDataList( + originalData.toList(), + dataToRemove, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); test('should removeDataList successfully', () async { secureInfo.saveGithubKey( GithubData(token: "fake_token", projectName: "test_project"), @@ -338,8 +1059,12 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); expect( () => commonsServices.removeDataList( @@ -369,7 +1094,190 @@ void main() { throwsA(isA()), ); }); - test('should run updateAllData successfully', () async { + test('should throw an exception when sha is null', () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + final mockContent = repoContentsFile..content = base64Content; + mockContent.sha = null; + + final mockContents = repoContent..file = mockContent; + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenAnswer((_) async => mockContents); + when(mockContents.file?.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you cant create the file in github', + () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError but you can create the file in github', + () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + final localJson = json.encode({ + 'configName': 'Random Organization', + 'primaryColorOrganization': '#4285F4', + 'secondaryColorOrganization': '#4285F4', + 'github_user': 'remote_user', + 'project_name': 'remote_proj', + 'branch': 'prod', + 'eventForcedToViewUID': null, + }); + final base64Content = base64.encode(utf8.encode(localJson)); + + MockContentCreation contentCreation = MockContentCreation(); + MockGitHubFile mockGitHubFile = MockGitHubFile(); + when(mockGitHubFile.content).thenReturn(base64Content); + when(contentCreation.content).thenReturn(mockGitHubFile); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + returnsNormally, + ); + }, + ); + + test( + 'should throw an exception when github.repositories.getContents thows a gitHubError & you have a response.content null', + () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + + MockContentCreation contentCreation = MockContentCreation(); + when(contentCreation.content).thenReturn(null); + when( + mockRepositoriesService.getContents(any, any, ref: anyNamed('ref')), + ).thenThrow(github_sdk.GitHubError(mockGitHub, "Not Found")); + when( + mockRepositoriesService.createFile(any, any), + ).thenAnswer((_) async => contentCreation); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + + test( + 'should throw an exception when repoContentsFile.sha is null', + () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + when(repoContentsFile.sha).thenReturn(null); + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }, + ); + test('updateAllData returns a different statuscode of 200', () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; secureInfo.saveGithubKey( GithubData(token: "fake_token", projectName: "test_project"), ); @@ -377,9 +1285,74 @@ void main() { getIt.unregister(); getIt.registerSingleton(mockSecureInfo); - when(mockSecureInfo.getGithubKey()).thenAnswer((_) async => mockGithubData); - when(mockSecureInfo.getGithubItem()).thenAnswer((_) async => mockGitHub); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 400)); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test('updateAllData returns a 409 statuscode', () async { + final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); + const commitMessage = 'Update all data'; + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); + when( + mockHttpClient.put( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => Response("{}", 409)); + expect( + () => commonsServices.updateAllData( + fullDataModel, + testPath, + commitMessage, + ), + throwsA(isA()), + ); + }); + test('should run updateAllData successfully', () async { + secureInfo.saveGithubKey( + GithubData(token: "fake_token", projectName: "test_project"), + ); + final mockSecureInfo = MockSecureInfo(); + getIt.unregister(); + getIt.registerSingleton(mockSecureInfo); + when( + mockSecureInfo.getGithubKey(), + ).thenAnswer((_) async => mockGithubData); + when( + mockSecureInfo.getGithubItem(), + ).thenAnswer((_) async => mockGitHub); final fullDataModel = MockGithubJsonModel({'newData': 'is here'}); const commitMessage = 'Update all data'; diff --git a/test/l10n/app_localizations_ca_test.dart b/test/l10n/app_localizations_ca_test.dart index a4fdd70ab..8fdeb2d2e 100644 --- a/test/l10n/app_localizations_ca_test.dart +++ b/test/l10n/app_localizations_ca_test.dart @@ -193,6 +193,11 @@ void main() { expect(localizations.selectSpeaker, 'Selecciona un ponent'); expect(localizations.onlineNow, 'En línia ara'); expect(localizations.noLiveStreamAvailable, 'No hi ha directes disponibles'); + expect(localizations.confirm, 'Confirmar'); + expect(localizations.deleteOptionMessage, 'Vols eliminar aquesta opció?'); + expect(localizations.delete, 'Eliminar'); + expect(localizations.addOption, 'Afegir opció'); + expect(localizations.optionHint, 'Opció'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_en_test.dart b/test/l10n/app_localizations_en_test.dart index 7118575db..65d38d08d 100644 --- a/test/l10n/app_localizations_en_test.dart +++ b/test/l10n/app_localizations_en_test.dart @@ -190,6 +190,11 @@ void main() { expect(localizations.selectSpeaker, 'Select a speaker'); expect(localizations.onlineNow, 'Online Now'); expect(localizations.noLiveStreamAvailable, 'No live streams available'); + expect(localizations.confirm, 'Confirm'); + expect(localizations.deleteOptionMessage, 'Do you want to delete this option?'); + expect(localizations.delete, 'Delete'); + expect(localizations.addOption, 'Add option'); + expect(localizations.optionHint, 'Option'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_es_test.dart b/test/l10n/app_localizations_es_test.dart index fe8e86261..8b2c78aad 100644 --- a/test/l10n/app_localizations_es_test.dart +++ b/test/l10n/app_localizations_es_test.dart @@ -190,6 +190,11 @@ void main() { expect(localizations.logout, 'Cerrar Sesión'); expect(localizations.onLive, 'En Vivo'); expect(localizations.noLiveStreamAvailable, 'No hay transmisión en vivo disponible'); + expect(localizations.confirm, 'Confirmar'); + expect(localizations.delete, 'Eliminar'); + expect(localizations.addOption, 'Añadir opción'); + expect(localizations.optionHint, 'Opción'); + expect(localizations.deleteOptionMessage, '¿Deseas eliminar esta opción?'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_eu_test.dart b/test/l10n/app_localizations_eu_test.dart index 04853ed3f..c9b6df76d 100644 --- a/test/l10n/app_localizations_eu_test.dart +++ b/test/l10n/app_localizations_eu_test.dart @@ -134,7 +134,7 @@ void main() { expect(localizations.loginTitle, 'Saioa hasi'); expect(localizations.projectNameLabel, 'Proiektuaren izena'); expect(localizations.projectNameHint, 'Mesedez, sartu proiektuaren izena'); - expect(localizations.tokenHintLabel, 'Sartu zure bezeroaren sekretua jarraitzeko'); + expect(localizations.tokenHintLabel, 'Sartu votre bezeroaren sekretua jarraitzeko'); expect(localizations.tokenHint, 'Mesedez, sartu baliozko GitHub token bat'); expect(localizations.unknownAuthError, 'Autentifikazio-errore ezezaguna.'); expect(localizations.authNetworkError, 'Autentifikazio- edo sare-errorea. Egiaztatu zure kredentzialak eta proiektuaren izena.'); @@ -190,6 +190,11 @@ void main() { expect(localizations.selectSpeaker, 'Hautatu hizlaria'); expect(localizations.onlineNow, 'Online Now'); expect(localizations.noLiveStreamAvailable, 'Ez dago zuzeneko emankizunik eskuragarri'); + expect(localizations.delete, 'Ezabatu'); + expect(localizations.addOption, 'Aukera gehitu'); + expect(localizations.optionHint, 'Aukera'); + expect(localizations.deleteOptionMessage, 'Aukera hau ezabatu nahi duzu?'); + expect(localizations.confirm, 'Berretsi'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_fr_test.dart b/test/l10n/app_localizations_fr_test.dart index 92a1f63df..7820aef5a 100644 --- a/test/l10n/app_localizations_fr_test.dart +++ b/test/l10n/app_localizations_fr_test.dart @@ -188,6 +188,12 @@ import 'package:sec/l10n/app_localizations_fr.dart';void main() { expect(localizations.selectSpeaker, 'Sélectionnez un intervenant'); expect(localizations.onlineNow, 'En ligne maintenant'); expect(localizations.noLiveStreamAvailable, 'Aucun direct n\'est disponible'); + expect(localizations.confirm, 'Confirmer'); + expect(localizations.delete, 'Supprimer'); + expect(localizations.addOption, 'Ajouter une option'); + expect(localizations.optionHint, 'Option'); + expect(localizations.deleteOptionMessage, 'Voulez-vous supprimer cette option ?'); + }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_gl_test.dart b/test/l10n/app_localizations_gl_test.dart index 53946ac99..4de478155 100644 --- a/test/l10n/app_localizations_gl_test.dart +++ b/test/l10n/app_localizations_gl_test.dart @@ -190,6 +190,11 @@ void main() { expect(localizations.onLive, 'On Live'); expect(localizations.selectSpeaker, 'Selecciona un poñente'); expect(localizations.onlineNow, 'En liña agora'); + expect(localizations.confirm, 'Confirmar'); + expect(localizations.delete, 'Eliminar'); + expect(localizations.addOption, 'Engadir opción'); + expect(localizations.optionHint, 'Opción'); + expect(localizations.deleteOptionMessage, 'Desexas eliminar esta opción?'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_it_test.dart b/test/l10n/app_localizations_it_test.dart index aef77df8e..4d0c40b83 100644 --- a/test/l10n/app_localizations_it_test.dart +++ b/test/l10n/app_localizations_it_test.dart @@ -189,6 +189,11 @@ void main() { expect(localizations.onlineNow, 'Online adesso'); expect(localizations.selectSpeaker, 'Seleziona un relatore'); expect(localizations.noLiveStreamAvailable, 'Non ci sono dirette disponibili'); + expect(localizations.confirm, 'Conferma'); + expect(localizations.delete, 'Elimina'); + expect(localizations.addOption, 'Aggiungi opzione'); + expect(localizations.optionHint, 'Opzione'); + expect(localizations.deleteOptionMessage, 'Vuoi eliminare questa opzione?'); }); test('should return correct translations for methods with parameters', () { diff --git a/test/l10n/app_localizations_pt_test.dart b/test/l10n/app_localizations_pt_test.dart index cdd75e37d..da82dffb8 100644 --- a/test/l10n/app_localizations_pt_test.dart +++ b/test/l10n/app_localizations_pt_test.dart @@ -189,6 +189,12 @@ void main() { expect(localizations.onLive, 'On Live'); expect(localizations.selectSpeaker, 'Selecione um palestrante'); expect(localizations.noLiveStreamAvailable, 'Não há transmissões ao vivo disponíveis'); + expect(localizations.confirm, 'Confirmar'); + expect(localizations.delete, 'Apagar'); + expect(localizations.addOption, 'Adicionar opção'); + expect(localizations.optionHint, 'Opção'); + expect(localizations.deleteOptionMessage, 'Deseja eliminar esta opção?'); + }); test('should return correct translations for methods with parameters', () { diff --git a/test/mocks.dart b/test/mocks.dart index 794775c1b..9045734fb 100644 --- a/test/mocks.dart +++ b/test/mocks.dart @@ -49,7 +49,9 @@ import 'package:sec/presentation/ui/screens/sponsor/sponsor_view_model.dart'; CommonsServicesImp, Config, ConfigViewModel, + ContentCreation, ConfigUseCase, + CurrentUser, DataLoaderManager, DataUpdateManager, DataUpdate, @@ -77,6 +79,7 @@ import 'package:sec/presentation/ui/screens/sponsor/sponsor_view_model.dart'; SponsorViewModel, Track, TokenRepository, + UsersService, Nominatim, ]) void main() {} diff --git a/test/mocks.mocks.dart b/test/mocks.mocks.dart index f1ac7fa49..7b4c040e5 100644 --- a/test/mocks.mocks.dart +++ b/test/mocks.mocks.dart @@ -426,8 +426,18 @@ class _FakeSpeaker_61 extends _i1.SmartFake implements _i7.Speaker { : super(parent, parentInvocation); } -class _FakePlace_62 extends _i1.SmartFake implements _i22.Place { - _FakePlace_62(Object parent, Invocation parentInvocation) +class _FakeUser_62 extends _i1.SmartFake implements _i14.User { + _FakeUser_62(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeCurrentUser_63 extends _i1.SmartFake implements _i14.CurrentUser { + _FakeCurrentUser_63(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlace_64 extends _i1.SmartFake implements _i22.Place { + _FakePlace_64(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } @@ -2041,6 +2051,23 @@ class MockConfigViewModel extends _i1.Mock implements _i35.ConfigViewModel { ); } +/// A class which mocks [ContentCreation]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockContentCreation extends _i1.Mock implements _i14.ContentCreation { + MockContentCreation() { + _i1.throwOnMissingStub(this); + } + + @override + Map toJson() => + (super.noSuchMethod( + Invocation.method(#toJson, []), + returnValue: {}, + ) + as Map); +} + /// A class which mocks [ConfigUseCase]. /// /// See the documentation for Mockito's code generation for more information. @@ -2063,6 +2090,251 @@ class MockConfigUseCase extends _i1.Mock implements _i36.ConfigUseCase { as _i15.Future<_i28.Result>); } +/// A class which mocks [CurrentUser]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCurrentUser extends _i1.Mock implements _i14.CurrentUser { + MockCurrentUser() { + _i1.throwOnMissingStub(this); + } + + @override + set privateReposCount(int? value) => super.noSuchMethod( + Invocation.setter(#privateReposCount, value), + returnValueForMissingStub: null, + ); + + @override + set ownedPrivateReposCount(int? value) => super.noSuchMethod( + Invocation.setter(#ownedPrivateReposCount, value), + returnValueForMissingStub: null, + ); + + @override + set diskUsage(int? value) => super.noSuchMethod( + Invocation.setter(#diskUsage, value), + returnValueForMissingStub: null, + ); + + @override + set plan(_i14.UserPlan? value) => super.noSuchMethod( + Invocation.setter(#plan, value), + returnValueForMissingStub: null, + ); + + @override + set json(Map? value) => super.noSuchMethod( + Invocation.setter(#json, value), + returnValueForMissingStub: null, + ); + + @override + set login(String? value) => super.noSuchMethod( + Invocation.setter(#login, value), + returnValueForMissingStub: null, + ); + + @override + set id(int? value) => super.noSuchMethod( + Invocation.setter(#id, value), + returnValueForMissingStub: null, + ); + + @override + set avatarUrl(String? value) => super.noSuchMethod( + Invocation.setter(#avatarUrl, value), + returnValueForMissingStub: null, + ); + + @override + set htmlUrl(String? value) => super.noSuchMethod( + Invocation.setter(#htmlUrl, value), + returnValueForMissingStub: null, + ); + + @override + set siteAdmin(bool? value) => super.noSuchMethod( + Invocation.setter(#siteAdmin, value), + returnValueForMissingStub: null, + ); + + @override + set name(String? value) => super.noSuchMethod( + Invocation.setter(#name, value), + returnValueForMissingStub: null, + ); + + @override + set company(String? value) => super.noSuchMethod( + Invocation.setter(#company, value), + returnValueForMissingStub: null, + ); + + @override + set blog(String? value) => super.noSuchMethod( + Invocation.setter(#blog, value), + returnValueForMissingStub: null, + ); + + @override + set location(String? value) => super.noSuchMethod( + Invocation.setter(#location, value), + returnValueForMissingStub: null, + ); + + @override + set email(String? value) => super.noSuchMethod( + Invocation.setter(#email, value), + returnValueForMissingStub: null, + ); + + @override + set hirable(bool? value) => super.noSuchMethod( + Invocation.setter(#hirable, value), + returnValueForMissingStub: null, + ); + + @override + set bio(String? value) => super.noSuchMethod( + Invocation.setter(#bio, value), + returnValueForMissingStub: null, + ); + + @override + set publicReposCount(int? value) => super.noSuchMethod( + Invocation.setter(#publicReposCount, value), + returnValueForMissingStub: null, + ); + + @override + set publicGistsCount(int? value) => super.noSuchMethod( + Invocation.setter(#publicGistsCount, value), + returnValueForMissingStub: null, + ); + + @override + set followersCount(int? value) => super.noSuchMethod( + Invocation.setter(#followersCount, value), + returnValueForMissingStub: null, + ); + + @override + set followingCount(int? value) => super.noSuchMethod( + Invocation.setter(#followingCount, value), + returnValueForMissingStub: null, + ); + + @override + set createdAt(DateTime? value) => super.noSuchMethod( + Invocation.setter(#createdAt, value), + returnValueForMissingStub: null, + ); + + @override + set updatedAt(DateTime? value) => super.noSuchMethod( + Invocation.setter(#updatedAt, value), + returnValueForMissingStub: null, + ); + + @override + set twitterUsername(String? value) => super.noSuchMethod( + Invocation.setter(#twitterUsername, value), + returnValueForMissingStub: null, + ); + + @override + set eventsUrl(String? value) => super.noSuchMethod( + Invocation.setter(#eventsUrl, value), + returnValueForMissingStub: null, + ); + + @override + set followersUrl(String? value) => super.noSuchMethod( + Invocation.setter(#followersUrl, value), + returnValueForMissingStub: null, + ); + + @override + set followingUrl(String? value) => super.noSuchMethod( + Invocation.setter(#followingUrl, value), + returnValueForMissingStub: null, + ); + + @override + set gistsUrl(String? value) => super.noSuchMethod( + Invocation.setter(#gistsUrl, value), + returnValueForMissingStub: null, + ); + + @override + set gravatarId(String? value) => super.noSuchMethod( + Invocation.setter(#gravatarId, value), + returnValueForMissingStub: null, + ); + + @override + set nodeId(String? value) => super.noSuchMethod( + Invocation.setter(#nodeId, value), + returnValueForMissingStub: null, + ); + + @override + set organizationsUrl(String? value) => super.noSuchMethod( + Invocation.setter(#organizationsUrl, value), + returnValueForMissingStub: null, + ); + + @override + set receivedEventsUrl(String? value) => super.noSuchMethod( + Invocation.setter(#receivedEventsUrl, value), + returnValueForMissingStub: null, + ); + + @override + set reposUrl(String? value) => super.noSuchMethod( + Invocation.setter(#reposUrl, value), + returnValueForMissingStub: null, + ); + + @override + set starredAt(DateTime? value) => super.noSuchMethod( + Invocation.setter(#starredAt, value), + returnValueForMissingStub: null, + ); + + @override + set starredUrl(String? value) => super.noSuchMethod( + Invocation.setter(#starredUrl, value), + returnValueForMissingStub: null, + ); + + @override + set subscriptionsUrl(String? value) => super.noSuchMethod( + Invocation.setter(#subscriptionsUrl, value), + returnValueForMissingStub: null, + ); + + @override + set type(String? value) => super.noSuchMethod( + Invocation.setter(#type, value), + returnValueForMissingStub: null, + ); + + @override + set url(String? value) => super.noSuchMethod( + Invocation.setter(#url, value), + returnValueForMissingStub: null, + ); + + @override + Map toJson() => + (super.noSuchMethod( + Invocation.method(#toJson, []), + returnValue: {}, + ) + as Map); +} + /// A class which mocks [DataLoaderManager]. /// /// See the documentation for Mockito's code generation for more information. @@ -6860,6 +7132,205 @@ class MockTokenRepository extends _i1.Mock implements _i50.TokenRepository { as _i15.Future); } +/// A class which mocks [UsersService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUsersService extends _i1.Mock implements _i14.UsersService { + MockUsersService() { + _i1.throwOnMissingStub(this); + } + + @override + _i14.GitHub get github => + (super.noSuchMethod( + Invocation.getter(#github), + returnValue: _FakeGitHub_39(this, Invocation.getter(#github)), + ) + as _i14.GitHub); + + @override + _i15.Future<_i14.User> getUser(String? name) => + (super.noSuchMethod( + Invocation.method(#getUser, [name]), + returnValue: _i15.Future<_i14.User>.value( + _FakeUser_62(this, Invocation.method(#getUser, [name])), + ), + ) + as _i15.Future<_i14.User>); + + @override + _i15.Future<_i14.CurrentUser> editCurrentUser({ + String? name, + String? email, + String? blog, + String? company, + String? location, + bool? hireable, + String? bio, + }) => + (super.noSuchMethod( + Invocation.method(#editCurrentUser, [], { + #name: name, + #email: email, + #blog: blog, + #company: company, + #location: location, + #hireable: hireable, + #bio: bio, + }), + returnValue: _i15.Future<_i14.CurrentUser>.value( + _FakeCurrentUser_63( + this, + Invocation.method(#editCurrentUser, [], { + #name: name, + #email: email, + #blog: blog, + #company: company, + #location: location, + #hireable: hireable, + #bio: bio, + }), + ), + ), + ) + as _i15.Future<_i14.CurrentUser>); + + @override + _i15.Stream<_i14.User> getUsers(List? names, {int? pages}) => + (super.noSuchMethod( + Invocation.method(#getUsers, [names], {#pages: pages}), + returnValue: _i15.Stream<_i14.User>.empty(), + ) + as _i15.Stream<_i14.User>); + + @override + _i15.Future<_i14.CurrentUser> getCurrentUser() => + (super.noSuchMethod( + Invocation.method(#getCurrentUser, []), + returnValue: _i15.Future<_i14.CurrentUser>.value( + _FakeCurrentUser_63(this, Invocation.method(#getCurrentUser, [])), + ), + ) + as _i15.Future<_i14.CurrentUser>); + + @override + _i15.Future isUser(String? name) => + (super.noSuchMethod( + Invocation.method(#isUser, [name]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Stream<_i14.User> listUsers({int? pages, int? since}) => + (super.noSuchMethod( + Invocation.method(#listUsers, [], {#pages: pages, #since: since}), + returnValue: _i15.Stream<_i14.User>.empty(), + ) + as _i15.Stream<_i14.User>); + + @override + _i15.Stream<_i14.UserEmail> listEmails() => + (super.noSuchMethod( + Invocation.method(#listEmails, []), + returnValue: _i15.Stream<_i14.UserEmail>.empty(), + ) + as _i15.Stream<_i14.UserEmail>); + + @override + _i15.Stream<_i14.UserEmail> addEmails(List? emails) => + (super.noSuchMethod( + Invocation.method(#addEmails, [emails]), + returnValue: _i15.Stream<_i14.UserEmail>.empty(), + ) + as _i15.Stream<_i14.UserEmail>); + + @override + _i15.Future deleteEmails(List? emails) => + (super.noSuchMethod( + Invocation.method(#deleteEmails, [emails]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Stream<_i14.User> listUserFollowers(String? user) => + (super.noSuchMethod( + Invocation.method(#listUserFollowers, [user]), + returnValue: _i15.Stream<_i14.User>.empty(), + ) + as _i15.Stream<_i14.User>); + + @override + _i15.Future isFollowingUser(String? user) => + (super.noSuchMethod( + Invocation.method(#isFollowingUser, [user]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Future isUserFollowing(String? user, String? target) => + (super.noSuchMethod( + Invocation.method(#isUserFollowing, [user, target]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Future followUser(String? user) => + (super.noSuchMethod( + Invocation.method(#followUser, [user]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Future unfollowUser(String? user) => + (super.noSuchMethod( + Invocation.method(#unfollowUser, [user]), + returnValue: _i15.Future.value(false), + ) + as _i15.Future); + + @override + _i15.Stream<_i14.User> listCurrentUserFollowers() => + (super.noSuchMethod( + Invocation.method(#listCurrentUserFollowers, []), + returnValue: _i15.Stream<_i14.User>.empty(), + ) + as _i15.Stream<_i14.User>); + + @override + _i15.Stream<_i14.User> listCurrentUserFollowing() => + (super.noSuchMethod( + Invocation.method(#listCurrentUserFollowing, []), + returnValue: _i15.Stream<_i14.User>.empty(), + ) + as _i15.Stream<_i14.User>); + + @override + _i15.Stream<_i14.PublicKey> listPublicKeys([String? userLogin]) => + (super.noSuchMethod( + Invocation.method(#listPublicKeys, [userLogin]), + returnValue: _i15.Stream<_i14.PublicKey>.empty(), + ) + as _i15.Stream<_i14.PublicKey>); + + @override + _i15.Future<_i14.PublicKey> createPublicKey(_i14.CreatePublicKey? key) => + (super.noSuchMethod( + Invocation.method(#createPublicKey, [key]), + returnValue: _i15.Future<_i14.PublicKey>.value( + _FakePublicKey_51( + this, + Invocation.method(#createPublicKey, [key]), + ), + ), + ) + as _i15.Future<_i14.PublicKey>); +} + /// A class which mocks [Nominatim]. /// /// See the documentation for Mockito's code generation for more information. @@ -6937,7 +7408,7 @@ class MockNominatim extends _i1.Mock implements _i22.Nominatim { #host: host, }), returnValue: _i15.Future<_i22.Place>.value( - _FakePlace_62( + _FakePlace_64( this, Invocation.method(#reverseSearch, [], { #lat: lat, diff --git a/test/presentation/agenda/form/agenda_form_screen_test.dart b/test/presentation/agenda/form/agenda_form_screen_test.dart index 9ca345b6c..338187a82 100644 --- a/test/presentation/agenda/form/agenda_form_screen_test.dart +++ b/test/presentation/agenda/form/agenda_form_screen_test.dart @@ -244,6 +244,92 @@ void main() { ); }); + group('room dialog', () { + testWidgets('should add a new dialog when add room button is pressed', ( + WidgetTester tester, + ) async { + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.loadFinished)); + + when(mockViewModel.addSpeaker(any, any)).thenAnswer((_) async {}); + + await tester.pumpWidget( + createWidgetUnderTest(data: AgendaFormData(eventId: 'event1')), + ); + await tester.pumpAndSettle(); + + // Find the add speaker button and tap it. + final addButton = find.byKey(Key("add_room_button")); + expect(addButton, findsOneWidget); + await tester.tap(addButton); + await tester.pumpAndSettle(); + + // Verify that we have navigated to the SpeakerFormScreen + expect(find.byType(AlertDialog), findsOneWidget); + }); + testWidgets('tap cancel button into room dialog, it closes', ( + WidgetTester tester, + ) async { + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.loadFinished)); + + when(mockViewModel.addSpeaker(any, any)).thenAnswer((_) async {}); + + await tester.pumpWidget( + createWidgetUnderTest(data: AgendaFormData(eventId: 'event1')), + ); + await tester.pumpAndSettle(); + + // Find the add speaker button and tap it. + final addButton = find.byKey(Key("add_room_button")); + expect(addButton, findsOneWidget); + await tester.tap(addButton); + await tester.pumpAndSettle(); + + final cancelButton = find.byKey(Key("cancel_button_room")); + expect(cancelButton, findsOneWidget); + await tester.tap(cancelButton); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + }); + testWidgets('tap save button into room dialog, it closes and saves new room', ( + WidgetTester tester, + ) async { + when(mockViewModel.addTrack(any,any)).thenAnswer((_) async { + return true; + }); + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.loadFinished)); + + when(mockViewModel.addSpeaker(any, any)).thenAnswer((_) async {}); + + await tester.pumpWidget( + createWidgetUnderTest(data: AgendaFormData(eventId: 'event1')), + ); + await tester.pumpAndSettle(); + + // Find the add speaker button and tap it. + final addButton = find.byKey(Key("add_room_button")); + expect(addButton, findsOneWidget); + await tester.tap(addButton); + await tester.pumpAndSettle(); + + final fieldTextRoom = find.byKey(Key("track_name_field")); + await tester.enterText(fieldTextRoom, 'New room'); + + + final cancelButton = find.byKey(Key("save_room_button")); + expect(cancelButton, findsOneWidget); + await tester.tap(cancelButton); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + }); + }); group('TextInputField validator for the talks', () { testWidgets('Shows an error when the TextFormField is empty', ( tester, @@ -561,13 +647,13 @@ void main() { await tester.pumpAndSettle(); } - // Open the start time picker and set it to 10:00 + // Open the start time picker and set it to 10:00 final startPicker = find.byKey(const Key('start_time_picker')); await tester.ensureVisible(startPicker); await tester.tap(startPicker); await tester.pumpAndSettle(); - // Check that there are two TextFields + // Check that there are two TextFields final startTextFields = find.byType(TextField); expect(startTextFields, findsNWidgets(2)); diff --git a/test/presentation/event_collection/event_collection_screen_test.dart b/test/presentation/event_collection/event_collection_screen_test.dart index 26e8f3258..eb63540ae 100644 --- a/test/presentation/event_collection/event_collection_screen_test.dart +++ b/test/presentation/event_collection/event_collection_screen_test.dart @@ -78,21 +78,20 @@ void main() { group('EventCollectionScreen', () { testWidgets('shows loading indicator initially and then content', ( - WidgetTester tester, - ) async { - when(mockViewModel.viewState).thenReturn(ValueNotifier(ViewState.isLoading)); + WidgetTester tester, + ) async { + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.isLoading)); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); expect(find.byType(CircularProgressIndicator), findsOneWidget); - when(mockViewModel.viewState) - .thenReturn(ValueNotifier(ViewState.loadFinished)); + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.loadFinished)); // Rebuild the widget to reflect the new state. - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); expect(find.byType(CircularProgressIndicator), findsNothing); @@ -100,55 +99,57 @@ void main() { }); testWidgets('displays error message on load failure', ( - WidgetTester tester, - ) async { + WidgetTester tester, + ) async { // Use a local ValueNotifier to control state changes during the test. final viewStateNotifier = ValueNotifier(ViewState.error); // Start with an error state when(mockViewModel.viewState).thenReturn(viewStateNotifier); - when(mockViewModel.errorMessage) - .thenReturn('Error loading configuration: '); + when( + mockViewModel.errorMessage, + ).thenReturn('Error loading configuration: '); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); // Pump and settle to allow dialog to show. // The error message and retry button are inside the CustomErrorDialog. expect(find.byType(CustomErrorDialog), findsOneWidget); - expect(find.descendant(of: find.byType(CustomErrorDialog), matching: find.text('Error loading configuration: ')), findsOneWidget); + expect( + find.descendant( + of: find.byType(CustomErrorDialog), + matching: find.text('Error loading configuration: '), + ), + findsOneWidget, + ); }); testWidgets('shows CustomErrorDialog when viewmodel has a specific error', ( - WidgetTester tester, - ) async { + WidgetTester tester, + ) async { when(mockViewModel.viewState).thenReturn(ValueNotifier(ViewState.error)); when(mockViewModel.errorMessage).thenReturn('Network Error'); - await tester.pumpWidget( - buildTestableWidget(), - ); - await tester.pumpAndSettle(); // Let post frame callback run for the dialog + await tester.pumpWidget(buildTestableWidget()); + await tester + .pumpAndSettle(); // Let post frame callback run for the dialog expect(find.byType(CustomErrorDialog), findsOneWidget); expect(find.text('Network Error'), findsOneWidget); }); testWidgets('displays MaintenanceScreen when there are no events', ( - WidgetTester tester, - ) async { + WidgetTester tester, + ) async { when(mockViewModel.eventsToShow).thenReturn(ValueNotifier([])); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); expect(find.byType(MaintenanceScreen), findsOneWidget); }); testWidgets('displays event grid and highlights the upcoming event', ( - WidgetTester tester, - ) async { + WidgetTester tester, + ) async { final now = DateTime.now(); final upcomingEvent = Event( uid: '2', @@ -189,9 +190,7 @@ void main() { mockViewModel.eventsToShow, ).thenReturn(ValueNotifier([pastEvent, upcomingEvent])); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); expect(find.byType(GridView), findsOneWidget); @@ -200,11 +199,9 @@ void main() { }); testWidgets('filter dropdown calls viewmodel with correct filter', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - buildTestableWidget(), - ); + WidgetTester tester, + ) async { + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); await tester.tap(find.text('Filter Event')); @@ -217,19 +214,17 @@ void main() { }); testWidgets('tapping title 5 times opens admin login if org has error', ( - WidgetTester tester, - ) async { + WidgetTester tester, + ) async { when(mockCheckOrg.hasError).thenReturn(true); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); final titleGestureDetector = find .descendant( - of: find.byType(AppBar), - matching: find.byType(GestureDetector), - ) + of: find.byType(AppBar), + matching: find.byType(GestureDetector), + ) .first; for (int i = 0; i < 5; i++) { @@ -239,10 +234,45 @@ void main() { expect(find.byType(AdminLoginScreen), findsOneWidget); }); + testWidgets( + 'Should open AdminLoginScreen dialog after 5 taps on title when hasOrgError is true', + (WidgetTester tester) async { + + // Configure to simulate an organization error + when(mockCheckOrg.hasError).thenReturn(true); + when(mockViewModel.setup()).thenAnswer((_) async => {}); + when( + mockViewModel.viewState, + ).thenReturn(ValueNotifier(ViewState.loadFinished)); + + // 2. Render the screen + await tester.pumpWidget( + buildTestableWidget(), + ); + await tester.pumpAndSettle(); + + // 3. Find the title GestureDetector by its Key + final titleFinder = find.byKey(const Key('title_key_event_collection')); + expect(titleFinder, findsOneWidget); + + // 4. Perform 5 clicks + for (int i = 0; i < 5; i++) { + await tester.tap(titleFinder); + await tester.pump(const Duration(milliseconds: 100)); + } + + // Wait for the dialog to animate + await tester.pumpAndSettle(); + + // 5. Verify that the Dialog and AdminLoginScreen are present + expect(find.byType(Dialog), findsOneWidget); + expect(find.byType(AdminLoginScreen), findsOneWidget); + }, + ); testWidgets( 'Admin sees Add Event button, taps it, and new event is added', - (WidgetTester tester) async { + (WidgetTester tester) async { final newEvent = Event( uid: 'new', eventName: 'New Event', @@ -269,9 +299,7 @@ void main() { ).thenAnswer((_) async => newEvent); when(mockViewModel.addEvent(newEvent)).thenAnswer((_) async {}); - await tester.pumpWidget( - buildTestableWidget(), - ); + await tester.pumpWidget(buildTestableWidget()); await tester.pumpAndSettle(); expect( @@ -287,161 +315,5 @@ void main() { verify(mockViewModel.addEvent(newEvent)).called(1); }, ); - - testWidgets('Admin can toggle event visibility', ( - WidgetTester tester, - ) async { - final event = Event( - uid: '1', - eventName: 'Event 1', - eventDates: EventDates( - uid: "eventDates_UID", - startDate: DateTime.now().toIso8601String(), - endDate: '', - timezone: "Europe/Madrid", - ), - location: '', - description: '', - isVisible: true, - tracks: [], - year: '', - primaryColor: '', - secondaryColor: '', - ); - - // --- INICIO DE LA SOLUCIÓN --- - // Clonamos el evento y cambiamos su visibilidad para simular la lógica del ViewModel. - final editedEvent = event.copyWith(isVisible: false); - - when(mockViewModel.checkToken()).thenAnswer((_) async => true); - // Define el comportamiento esperado para la llamada a editEvent. - when(mockViewModel.editEvent(any)).thenAnswer((_) async { - mockViewModel.eventsToShow.value = [editedEvent]; - return Result.ok(null); - }); - // --- FIN DE LA SOLUCIÓN --- - - mockViewModel.eventsToShow.value = [event]; // Estado inicial - - await tester.pumpWidget( - buildTestableWidget(), - ); - await tester.pumpAndSettle(); - - await tester.tap(find.byIcon(Icons.visibility)); - await tester.pumpAndSettle(); // Muestra el diálogo - - await tester.tap(find.widgetWithText(TextButton, 'Change Visibility')); - await tester.pumpAndSettle(); // Cierra el diálogo y ejecuta la lógica - - // Ahora, en lugar de llamar directamente al método, verificamos que el test lo llamó. - verify(mockViewModel.editEvent(any)).called(1); - - // Y comprobamos que el estado se actualizó como esperábamos. - expect(mockViewModel.eventsToShow.value[0].isVisible, isFalse); - }); - - testWidgets('Admin cancels toggle visibility dialog', ( - WidgetTester tester, - ) async { - // Setup similar to the successful toggle test, but we will tap "Cancel" - final event = Event(uid: '1', eventName: 'Event 1', isVisible: true, eventDates: EventDates(uid: 'uid', startDate: DateTime.now().toIso8601String(), endDate: '', timezone: ''), location: '', description: '', tracks: [], year: '', primaryColor: '', secondaryColor: ''); - when(mockViewModel.checkToken()).thenAnswer((_) async => true); - mockViewModel.eventsToShow.value = [event]; - - await tester.pumpWidget(buildTestableWidget()); - await tester.pumpAndSettle(); - - await tester.tap(find.byIcon(Icons.visibility)); - await tester.pumpAndSettle(); // Dialog is shown - - await tester.tap(find.widgetWithText(TextButton, 'Cancel')); - await tester.pumpAndSettle(); // Dialog is dismissed - - verifyNever(mockViewModel.editEvent(any)); - }); - - testWidgets('Admin can edit and delete event from card', ( - WidgetTester tester, - ) async { - final event = Event( - uid: '1', - eventName: 'Event 1', - eventDates: EventDates( - uid: "eventDates_UID", - startDate: DateTime.now().toIso8601String(), - endDate: '', - timezone: "Europe/Madrid", - ), - location: '', - description: '', - isVisible: true, - tracks: [], - year: '', - primaryColor: '', - secondaryColor: '', - ); - when(mockViewModel.eventsToShow).thenReturn(ValueNotifier([event])); - when(mockViewModel.checkToken()).thenAnswer((_) async => true); - when( - mockRouter.push(AppRouter.eventFormPath, extra: '1'), - ).thenAnswer((_) async => null); - when(mockViewModel.deleteEvent(event)).thenAnswer((_) async {}); - - await tester.pumpWidget( - buildTestableWidget(), - ); - await tester.pumpAndSettle(); - - await tester.tap(find.byIcon(Icons.edit)); - await tester.pumpAndSettle(); - verify(mockRouter.push(AppRouter.eventFormPath, extra: '1')).called(1); - - await tester.tap(find.byIcon(Icons.delete)); - await tester.pumpAndSettle(); - await tester.tap(find.widgetWithText(TextButton, 'Delete Event')); - await tester.pumpAndSettle(); - - verify(mockViewModel.deleteEvent(event)).called(1); - }); - - testWidgets( - 'Admin sees and can tap organization FAB, updates config on return', - (WidgetTester tester) async { - when(mockViewModel.checkToken()).thenAnswer((_) async => true); - final updatedConfig = Config( - configName: 'Updated Conf', - primaryColorOrganization: 'test', - secondaryColorOrganization: 'test', - githubUser: 'test', - projectName: 'test', - branch: 'test', - ); - when( - mockRouter.push(AppRouter.configFormPath), - ).thenAnswer((_) async => updatedConfig); - - when(mockConfig.configName).thenReturn('Test Conf'); - - await tester.pumpWidget( - buildTestableWidget(), - ); - await tester.pumpAndSettle(); - - when(mockConfig.configName).thenReturn('Updated Conf'); - - final fab = find.byIcon(Icons.business); - expect(fab, findsOneWidget); - - await tester.tap(fab); - await tester.pumpAndSettle(); - - verify(mockRouter.push(AppRouter.configFormPath)).called(1); - - await tester.pumpAndSettle(); - - expect(find.text('Updated Conf'), findsOneWidget); - }, - ); }); }