From 39cea85f86e03b890e8b510754bdb124ccd63aee Mon Sep 17 00:00:00 2001 From: DatDang Date: Mon, 24 Feb 2025 10:37:45 +0700 Subject: [PATCH] TF-3487 Handle get email content failure --- .../domain/state/get_email_content_state.dart | 3 +- .../get_email_content_interactor.dart | 24 +++++- .../controller/single_email_controller.dart | 23 +++++- .../single_email_controller_test.dart | 78 ++++++++++++++++++- 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/lib/features/email/domain/state/get_email_content_state.dart b/lib/features/email/domain/state/get_email_content_state.dart index af729942d..fb7d5e8a5 100644 --- a/lib/features/email/domain/state/get_email_content_state.dart +++ b/lib/features/email/domain/state/get_email_content_state.dart @@ -51,5 +51,6 @@ class GetEmailContentFromCacheSuccess extends UIState { class GetEmailContentFailure extends FeatureFailure { - GetEmailContentFailure(dynamic exception) : super(exception: exception); + GetEmailContentFailure(dynamic exception, {super.onRetry}) + : super(exception: exception); } \ No newline at end of file diff --git a/lib/features/email/domain/usecases/get_email_content_interactor.dart b/lib/features/email/domain/usecases/get_email_content_interactor.dart index 6e7baeb36..48fc891d3 100644 --- a/lib/features/email/domain/usecases/get_email_content_interactor.dart +++ b/lib/features/email/domain/usecases/get_email_content_interactor.dart @@ -49,7 +49,17 @@ class GetEmailContentInteractor { } } catch (e) { log('GetEmailContentInteractor::execute(): exception = $e'); - yield Left(GetEmailContentFailure(e)); + yield Left(GetEmailContentFailure( + e, + onRetry: execute( + session, + accountId, + emailId, + baseDownloadUrl, + transformConfiguration, + additionalProperties: additionalProperties + ), + )); } } @@ -97,7 +107,17 @@ class GetEmailContentInteractor { } } catch (e) { logError('GetEmailContentInteractor::_getContentEmailFromServer():EXCEPTION: $e'); - yield Left(GetEmailContentFailure(e)); + yield Left(GetEmailContentFailure( + e, + onRetry: execute( + session, + accountId, + emailId, + baseDownloadUrl, + transformConfiguration, + additionalProperties: additionalProperties + ), + )); } } diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index fd0593e9c..e244c7f20 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -35,6 +35,7 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mdn/disposition.dart'; import 'package:jmap_dart_client/jmap/mdn/mdn.dart'; import 'package:model/email/eml_attachment.dart'; +import 'package:model/error_type_handler/unknown_uri_exception.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -332,7 +333,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } else if (failure is ParseCalendarEventFailure) { _handleParseCalendarEventFailure(failure); } else if (failure is GetEmailContentFailure) { - emailLoadedViewState.value = Left(failure); + _handleGetEmailContentFailure(failure); } else if (failure is PrintEmailFailure) { _showMessageWhenEmailPrintingFailed(failure); } else if (failure is CalendarEventReplyFailure) { @@ -348,6 +349,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } + void _handleGetEmailContentFailure(GetEmailContentFailure failure) { + emailLoadedViewState.value = Left(failure); + showRetryToast(failure); + } + void _registerObxStreamListener() { ever(mailboxDashBoardController.accountId, (accountId) { if (accountId is AccountId) { @@ -609,7 +615,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin { )); } catch (e) { logError('SingleEmailController::_getEmailContentAction(): $e'); - consumeState(Stream.value(Left(GetEmailContentFailure(e)))); + consumeState(Stream.value(Left(GetEmailContentFailure( + e, + onRetry: e is UnknownUriException + ? null + : _getEmailContentInteractor.execute( + session!, + accountId!, + emailId, + session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), + PlatformInfo.isWeb + ? TransformConfiguration.forPreviewEmailOnWeb() + : TransformConfiguration.forPreviewEmail(), + ), + )))); } } } diff --git a/test/features/email/presentation/controller/single_email_controller_test.dart b/test/features/email/presentation/controller/single_email_controller_test.dart index 22d5c9efb..7d0e9622d 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -1,9 +1,10 @@ import 'dart:convert'; -import 'dart:ui'; import 'package:core/core.dart'; import 'package:dartz/dartz.dart' hide State; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' hide State; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -24,6 +25,7 @@ import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasourc import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/repository/calendar_event_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; +import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_accept_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart'; @@ -57,6 +59,9 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oi import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations_delegate.dart'; +import 'package:tmail_ui_user/main/localizations/localization_service.dart'; import 'package:tmail_ui_user/main/utils/toast_manager.dart'; import 'package:tmail_ui_user/main/utils/twake_app_manager.dart'; import 'package:uuid/uuid.dart'; @@ -449,4 +454,75 @@ void main() { ); }); }); + + Widget makeTestableWidget({required Widget child}) { + return GetMaterialApp( + localizationsDelegates: const [ + AppLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: LocalizationService.supportedLocales, + home: Scaffold(body: child), + ); + } + + testWidgets( + 'should show retry toast ' + 'when handleFailureViewState is called with GetEmailContentFailure', + (tester) async { + // arrange + when(mailboxDashboardController.selectedEmail).thenReturn(Rxn(PresentationEmail())); + when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(EmailUIAction())); + when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle))); + when(appToast.showToastMessageWithMultipleActions( + any, + any, + actions: anyNamed('actions'), + textColor: anyNamed('textColor'), + backgroundColor: anyNamed('backgroundColor'), + infinityToast: anyNamed('infinityToast'), + )).thenAnswer((realInvocation) { + AppToast().showToastMessageWithMultipleActions( + realInvocation.positionalArguments[0], + realInvocation.positionalArguments[1], + actions: realInvocation.namedArguments[const Symbol('actions')], + textColor: realInvocation.namedArguments[const Symbol('textColor')], + backgroundColor: realInvocation.namedArguments[const Symbol('backgroundColor')], + infinityToast: realInvocation.namedArguments[const Symbol('infinityToast')], + ); + }); + when(imagePaths.icUndo).thenReturn(ImagePaths().icUndo); + when(imagePaths.icClose).thenReturn(ImagePaths().icClose); + Get.put(singleEmailController); + final widget = makeTestableWidget(child: const _TestView()); + await tester.pumpWidget(widget); + await tester.pump(); + + // act + singleEmailController.handleFailureViewState(GetEmailContentFailure( + null, + onRetry: const Stream.empty(), + )); + await tester.pump(); + + // assert + expect(find.text(AppLocalizations().unknownError), findsOneWidget); + expect(find.text(AppLocalizations().retry), findsOneWidget); + expect(find.text(AppLocalizations().close), findsOneWidget); + + // cleanup + Get.delete(); + }, + ); } + +class _TestView extends GetWidget { + const _TestView(); + + @override + Widget build(BuildContext context) { + return const SizedBox(); + } +} \ No newline at end of file