diff --git a/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart b/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart index 92ab8c2e5..1dcad41e1 100644 --- a/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart +++ b/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart @@ -7,6 +7,7 @@ import 'package:linkify/linkify.dart'; class SanitizeAutolinkFilter { final HtmlEscape htmlEscape; + final bool escapeHtml; final _linkifyOption = const LinkifyOptions( humanize: true, looseUrl: true, @@ -18,7 +19,7 @@ class SanitizeAutolinkFilter { const UrlLinkifier() ]; - SanitizeAutolinkFilter(this.htmlEscape); + SanitizeAutolinkFilter(this.htmlEscape, {this.escapeHtml = true}); String process(String inputText) { try { @@ -36,7 +37,7 @@ class SanitizeAutolinkFilter { for (var element in elements) { if (element is TextElement) { - final escapedHtml = htmlEscape.convert(element.text); + final escapedHtml = escapeHtml ? htmlEscape.convert(element.text) : element.text; htmlTextBuffer.write(escapedHtml); } else if (element is EmailElement) { final emailLinkTag = _buildEmailLinkTag( diff --git a/core/lib/presentation/utils/html_transformer/text/new_line_transformer.dart b/core/lib/presentation/utils/html_transformer/text/new_line_transformer.dart new file mode 100644 index 000000000..4b13b7646 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/text/new_line_transformer.dart @@ -0,0 +1,15 @@ +import 'dart:convert'; + +import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; + +class NewLineTransformer extends TextTransformer { + const NewLineTransformer(); + + @override + String process(String text, HtmlEscape htmlEscape) { + return text + .replaceAll('\n', '
') + .replaceAll('\r', ' ') + .replaceAll('\t', ' '); + } +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart b/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart new file mode 100644 index 000000000..75aba0085 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; + +import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; +import 'package:core/presentation/utils/html_transformer/sanitize_autolink_filter.dart'; + +class SanitizeAutolinkUnescapeHtmlTransformer extends TextTransformer { + const SanitizeAutolinkUnescapeHtmlTransformer(); + + @override + String process(String text, HtmlEscape htmlEscape) { + return SanitizeAutolinkFilter(htmlEscape, escapeHtml: false).process(text); + } +} \ No newline at end of file diff --git a/lib/features/email/data/repository/calendar_event_repository_impl.dart b/lib/features/email/data/repository/calendar_event_repository_impl.dart index 49c59dce7..c0e48e417 100644 --- a/lib/features/email/data/repository/calendar_event_repository_impl.dart +++ b/lib/features/email/data/repository/calendar_event_repository_impl.dart @@ -1,19 +1,24 @@ import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_response.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_maybe_response.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_reject_response.dart'; import 'package:tmail_ui_user/features/email/data/datasource/calendar_event_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/domain/repository/calendar_event_repository.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/blob_calendar_event.dart'; class CalendarEventRepositoryImpl extends CalendarEventRepository { final Map _calendarEventDataSource; + final HtmlDataSource _htmlDataSource; - CalendarEventRepositoryImpl(this._calendarEventDataSource); + CalendarEventRepositoryImpl(this._calendarEventDataSource, this._htmlDataSource); @override Future> parse(AccountId accountId, Set blobIds) { @@ -49,4 +54,33 @@ class CalendarEventRepositoryImpl extends CalendarEventRepository { return _calendarEventDataSource[DataSourceType.network]! .rejectEventInvitation(accountId, blobIds, language); } + + @override + Future> transformCalendarEventDescription( + List blobCalendarEvents, + TransformConfiguration transformConfiguration, + ) async { + return Future.wait(blobCalendarEvents.map((blobCalendarEvent) async { + return BlobCalendarEvent( + blobId: blobCalendarEvent.blobId, + calendarEventList: await Future.wait(blobCalendarEvent.calendarEventList.map((calendarEvent) { + return _transformCalendarEventDescription(calendarEvent, transformConfiguration); + })), + ); + })); + } + + Future _transformCalendarEventDescription( + CalendarEvent calendarEvent, + TransformConfiguration transformConfiguration, + ) async { + return calendarEvent.copyWith( + description: calendarEvent.description?.trim().isNotEmpty == true + ? await _htmlDataSource.transformHtmlEmailContent( + calendarEvent.description!, + transformConfiguration, + ) + : calendarEvent.description, + ); + } } \ No newline at end of file diff --git a/lib/features/email/domain/repository/calendar_event_repository.dart b/lib/features/email/domain/repository/calendar_event_repository.dart index de287e0e4..cf2f19318 100644 --- a/lib/features/email/domain/repository/calendar_event_repository.dart +++ b/lib/features/email/domain/repository/calendar_event_repository.dart @@ -1,4 +1,5 @@ +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_response.dart'; @@ -23,4 +24,8 @@ abstract class CalendarEventRepository { AccountId accountId, Set blobIds, String? language); + + Future> transformCalendarEventDescription( + List blobCalendarEvents, + TransformConfiguration transformConfiguration); } \ No newline at end of file diff --git a/lib/features/email/domain/usecases/parse_calendar_event_interactor.dart b/lib/features/email/domain/usecases/parse_calendar_event_interactor.dart index 55b11e5fb..f7ceff884 100644 --- a/lib/features/email/domain/usecases/parse_calendar_event_interactor.dart +++ b/lib/features/email/domain/usecases/parse_calendar_event_interactor.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; @@ -15,15 +16,19 @@ class ParseCalendarEventInteractor { Stream> execute( AccountId accountId, Set blobIds, - String emailContents + TransformConfiguration transformConfiguration, ) async* { try { yield Right(ParseCalendarEventLoading()); final listBlobCalendarEvent = await _calendarEventRepository.parse(accountId, blobIds); + final listBlobCalendarEventWithTransformedDescription = await _calendarEventRepository.transformCalendarEventDescription( + listBlobCalendarEvent, + transformConfiguration, + ); - if (listBlobCalendarEvent.isNotEmpty) { - yield Right(ParseCalendarEventSuccess(listBlobCalendarEvent)); + if (listBlobCalendarEventWithTransformedDescription.isNotEmpty) { + yield Right(ParseCalendarEventSuccess(listBlobCalendarEventWithTransformedDescription)); } else { yield Left(ParseCalendarEventFailure(NotFoundCalendarEventException())); } diff --git a/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart b/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart index e4718ed82..7d841f8ea 100644 --- a/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart +++ b/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart @@ -3,7 +3,10 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; import 'package:tmail_ui_user/features/email/data/datasource/calendar_event_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/calendar_event_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/calendar_event_api.dart'; import 'package:tmail_ui_user/features/email/data/repository/calendar_event_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/repository/calendar_event_repository.dart'; @@ -11,6 +14,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_acce import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_reject_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; +import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; class CalendarEventInteractorBindings extends InteractorsBindings { @@ -18,6 +22,7 @@ class CalendarEventInteractorBindings extends InteractorsBindings { @override void bindingsDataSource() { Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override @@ -26,6 +31,9 @@ class CalendarEventInteractorBindings extends InteractorsBindings { Get.lazyPut(() => CalendarEventDataSourceImpl( Get.find(), Get.find())); + Get.lazyPut(() => HtmlDataSourceImpl( + Get.find(), + Get.find())); } @override @@ -44,7 +52,8 @@ class CalendarEventInteractorBindings extends InteractorsBindings { @override void bindingsRepositoryImpl() { Get.lazyPut(() => CalendarEventRepositoryImpl( - {DataSourceType.network: Get.find()} + {DataSourceType.network: Get.find()}, + Get.find(), )); } } \ No newline at end of file diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index e40e78fca..45449b497 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -4,6 +4,9 @@ import 'dart:typed_data'; import 'package:better_open_file/better_open_file.dart' as open_file; import 'package:core/core.dart'; +import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart'; +import 'package:core/presentation/utils/html_transformer/text/new_line_transformer.dart'; +import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; @@ -519,7 +522,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _parseCalendarEventAction( accountId: mailboxDashBoardController.accountId.value!, blobIds: success.attachments?.calendarEventBlobIds ?? {}, - emailContents: success.htmlEmailContent ); } else { emailContents.value = success.htmlEmailContent; @@ -564,7 +566,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _parseCalendarEventAction( accountId: mailboxDashBoardController.accountId.value!, blobIds: success.attachments?.calendarEventBlobIds ?? {}, - emailContents: success.htmlEmailContent ); } else { emailContents.value = success.htmlEmailContent; @@ -1466,13 +1467,26 @@ class SingleEmailController extends BaseController with AppLoaderMixin { CapabilityIdentifier.jamesCalendarEvent.isSupported(session, accountId); } + @visibleForTesting + parseCalendarEventAction({ + required AccountId accountId, + required Set blobIds, + }) => _parseCalendarEventAction(accountId: accountId, blobIds: blobIds); + void _parseCalendarEventAction({ required AccountId accountId, required Set blobIds, - required String emailContents }) { log("SingleEmailController::_parseCalendarEventAction:blobIds: $blobIds"); - consumeState(_parseCalendarEventInteractor!.execute(accountId, blobIds, emailContents)); + consumeState(_parseCalendarEventInteractor!.execute( + accountId, + blobIds, + TransformConfiguration.fromTextTransformers(const [ + SanitizeAutolinkUnescapeHtmlTransformer(), + StandardizeHtmlSanitizingTransformers(), + NewLineTransformer(), + ]) + )); } void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) { diff --git a/lib/features/email/presentation/extensions/calendar_event_extension.dart b/lib/features/email/presentation/extensions/calendar_event_extension.dart index 2a7c895e0..f993c87cb 100644 --- a/lib/features/email/presentation/extensions/calendar_event_extension.dart +++ b/lib/features/email/presentation/extensions/calendar_event_extension.dart @@ -5,10 +5,21 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/utils/app_logger.dart'; import 'package:date_format/date_format.dart' as date_format; import 'package:flutter/material.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/attendee/calendar_attendee.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/attendee/calendar_attendee_participation_status.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_duration.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_event_status.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_extension_fields.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_free_busy_status.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_organizer.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_priority.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_privacy.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_sequence.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/recurrence_rule/recurrence_rule.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; @@ -364,4 +375,52 @@ extension CalendarEventExtension on CalendarEvent { method == EventMethod.request || method == EventMethod.add || method == EventMethod.counter; + + CalendarEvent copyWith({ + EventId? eventId, + String? title, + String? description, + DateTime? startDate, + DateTime? endDate, + UTCDate? startUtcDate, + UTCDate? endUtcDate, + CalendarDuration? duration, + String? timeZone, + String? location, + EventMethod? method, + CalendarSequence? sequence, + CalendarPrivacy? privacy, + CalendarPriority? priority, + CalendarFreeBusyStatus? freeBusyStatus, + CalendarEventStatus? status, + CalendarOrganizer? organizer, + List? participants, + CalendarExtensionFields? extensionFields, + List? recurrenceRules, + List? excludedCalendarEvents, + }) { + return CalendarEvent( + eventId: eventId ?? this.eventId, + title: title ?? this.title, + description: description ?? this.description, + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + startUtcDate: startUtcDate ?? this.startUtcDate, + endUtcDate: endUtcDate ?? this.endUtcDate, + duration: duration ?? this.duration, + timeZone: timeZone ?? this.timeZone, + location: location ?? this.location, + method: method ?? this.method, + sequence: sequence ?? this.sequence, + privacy: privacy ?? this.privacy, + priority: priority ?? this.priority, + freeBusyStatus: freeBusyStatus ?? this.freeBusyStatus, + status: status ?? this.status, + organizer: organizer ?? this.organizer, + participants: participants ?? this.participants, + extensionFields: extensionFields ?? this.extensionFields, + recurrenceRules: recurrenceRules ?? this.recurrenceRules, + excludedCalendarEvents: excludedCalendarEvents ?? this.excludedCalendarEvents, + ); + } } \ No newline at end of file diff --git a/test/features/email/data/repository/calendar_event_repository_impl_test.dart b/test/features/email/data/repository/calendar_event_repository_impl_test.dart index 43b51824a..67c4bc3d7 100644 --- a/test/features/email/data/repository/calendar_event_repository_impl_test.dart +++ b/test/features/email/data/repository/calendar_event_repository_impl_test.dart @@ -9,17 +9,25 @@ import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_reject_ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:tmail_ui_user/features/email/data/datasource/calendar_event_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/repository/calendar_event_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/calendar_event_exceptions.dart'; import 'calendar_event_repository_impl_test.mocks.dart'; -@GenerateNiceMocks([MockSpec()]) +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) void main() { final calendarEventNetworkDataSource = MockCalendarEventDataSource(); + final htmlDatasource = MockHtmlDataSource(); final calendarEventDataSource = { DataSourceType.network: calendarEventNetworkDataSource}; - final calendarEventRepository = CalendarEventRepositoryImpl(calendarEventDataSource); + final calendarEventRepository = CalendarEventRepositoryImpl( + calendarEventDataSource, + htmlDatasource, + ); final accountId = AccountId(Id('123')); final blobId = Id('blobId'); const language = 'en'; 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 1768bc934..9ba67d1e3 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:ui'; import 'package:core/core.dart'; @@ -18,7 +19,12 @@ import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/calendar_event_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +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/calendar_event_accept_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; @@ -32,9 +38,11 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_event_attendance_status_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; +import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/model/blob_calendar_event.dart'; @@ -46,9 +54,11 @@ import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_ import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.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/utils/toast_manager.dart'; import 'package:uuid/uuid.dart'; +import '../../../../fixtures/account_fixtures.dart'; import '../../../../fixtures/email_fixtures.dart'; import 'single_email_controller_test.mocks.dart'; @@ -91,6 +101,8 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -363,4 +375,137 @@ void main() { debugDefaultTargetPlatformOverride = null; }); }); + + group('_parseCalendarEventAction method test:', () { + final calendarEventDataSource = MockCalendarEventDataSource(); + final calendarEventRepository = CalendarEventRepositoryImpl( + {DataSourceType.network :calendarEventDataSource}, + HtmlDataSourceImpl( + HtmlAnalyzer(HtmlTransform( + MockDioClient(), + const HtmlEscape(), + )), + CacheExceptionThrower(), + ), + ); + + setUp(() { + Get.put(ParseCalendarEventInteractor(calendarEventRepository)); + }); + + tearDown(() { + Get.delete(); + }); + + test( + 'should transform all calendar event description url to a tag ' + 'and all new line to
tag', + () async { + // arrange + const eventDescription = '\nhttps://example1.com\nhttps://example2.com'; + const expectedEventDescription = '' + '
' + 'example1.com' + '
' + 'example2.com' + ''; + final blobId = Id('abc123'); + final calendarEvent = CalendarEvent( + description: eventDescription, + ); + final blobCalendarEvents = [ + BlobCalendarEvent( + blobId: blobId, + calendarEventList: [calendarEvent], + ), + ]; + when(calendarEventDataSource.parse(any, any)) + .thenAnswer((_) async => blobCalendarEvents); + + when(mailboxDashboardController.selectedEmail).thenReturn(Rxn(PresentationEmail())); + when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(EmailUIAction())); + when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle))); + when(mailboxDashboardController.accountId).thenReturn(Rxn(AccountFixtures.aliceAccountId)); + when(emailSupervisorController.scrollPhysicsPageView).thenReturn(Rxn()); + + singleEmailController.onInit(); + mailboxDashboardController.accountId.refresh(); + + // act + singleEmailController.parseCalendarEventAction( + accountId: AccountFixtures.aliceAccountId, + blobIds: {blobId}, + ); + await untilCalled(calendarEventDataSource.parse(any, any)); + await Future.delayed(Duration.zero); + + // assert + expect( + singleEmailController.blobCalendarEvent.value, + BlobCalendarEvent( + blobId: blobId, + calendarEventList: [CalendarEvent(description: expectedEventDescription)], + ), + ); + }); + + test( + 'should transform all calendar event description url to a tag ' + 'and all new line to
tag ' + 'and remove all xss attempt', + () async { + // arrange + const eventDescription = '\nhttps://example1.com' + '\nhttps://example2.com' + '\n' + '\nhref xss'; + const expectedEventDescription = '' + '
' + 'example1.com' + '
' + 'example2.com' + '
' + '
' + 'href xss' + ''; + final blobId = Id('abc123'); + final calendarEvent = CalendarEvent( + description: eventDescription, + ); + final blobCalendarEvents = [ + BlobCalendarEvent( + blobId: blobId, + calendarEventList: [calendarEvent], + ), + ]; + when(calendarEventDataSource.parse(any, any)) + .thenAnswer((_) async => blobCalendarEvents); + + when(mailboxDashboardController.selectedEmail).thenReturn(Rxn(PresentationEmail())); + when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(EmailUIAction())); + when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle))); + when(mailboxDashboardController.accountId).thenReturn(Rxn(AccountFixtures.aliceAccountId)); + when(emailSupervisorController.scrollPhysicsPageView).thenReturn(Rxn()); + + singleEmailController.onInit(); + mailboxDashboardController.accountId.refresh(); + + // act + singleEmailController.parseCalendarEventAction( + accountId: AccountFixtures.aliceAccountId, + blobIds: {blobId}, + ); + await untilCalled(calendarEventDataSource.parse(any, any)); + await Future.delayed(Duration.zero); + + // assert + expect( + singleEmailController.blobCalendarEvent.value, + BlobCalendarEvent( + blobId: blobId, + calendarEventList: [CalendarEvent(description: expectedEventDescription)], + ), + ); + }); + }); }