From 42c230a413df9f9a94bb4638783ad12a4cd9b0d9 Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Tue, 23 Jan 2024 18:48:32 +0100 Subject: [PATCH 1/7] DS UI for message attachment's preview --- ts/features/messages/analytics/index.ts | 2 +- .../MessageAttachment/DSPdfViewer.tsx | 49 ++++ .../components/MessageDetail/index.tsx | 7 +- .../designsystem/DSInfoScreenComponent.tsx | 65 +++++ .../designsystem/DSLoadingSpinnerOverlay.tsx | 100 ++++++++ .../messages/navigation/MessagesNavigator.tsx | 29 ++- ts/features/messages/navigation/params.ts | 4 +- .../messages/screens/DSMessageAttachment.tsx | 238 ++++++++++++++++++ .../messages/screens/MessageAttachment.tsx | 6 - .../messages/store/reducers/downloads.ts | 16 ++ ts/features/pn/utils/__tests__/index.test.ts | 3 +- ts/store/reducers/entities/services/types.ts | 3 +- 12 files changed, 506 insertions(+), 16 deletions(-) create mode 100644 ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx create mode 100644 ts/features/messages/designsystem/DSInfoScreenComponent.tsx create mode 100644 ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx create mode 100644 ts/features/messages/screens/DSMessageAttachment.tsx diff --git a/ts/features/messages/analytics/index.ts b/ts/features/messages/analytics/index.ts index 96fde7ede6e..c8939737924 100644 --- a/ts/features/messages/analytics/index.ts +++ b/ts/features/messages/analytics/index.ts @@ -109,7 +109,7 @@ export function trackThirdPartyMessageAttachmentBadFormat( export function trackThirdPartyMessageAttachmentCorruptedFile( messageId: UIMessageId, - serviceId: ServiceId | undefined + serviceId?: ServiceId ) { void mixpanelTrack( "THIRD_PARTY_MESSAGE_ATTACHMENT_CORRUPTED_FILE", diff --git a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx new file mode 100644 index 00000000000..9ec31ae22df --- /dev/null +++ b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx @@ -0,0 +1,49 @@ +import React, { useState } from "react"; +import { StyleSheet } from "react-native"; +import Pdf from "react-native-pdf"; +import { IOColors } from "@pagopa/io-app-design-system"; +import { DSLoadingSpinnerOverlay } from "../../designsystem/DSLoadingSpinnerOverlay"; +import I18n from "../../../../i18n"; + +const styles = StyleSheet.create({ + pdf: { + flex: 1, + backgroundColor: IOColors.bluegrey + } +}); + +type OwnProps = { + downloadPath: string; +}; + +type Props = OwnProps & Omit, "source">; + +export const DSPdfViewer = ({ + style, + downloadPath, + onError, + onLoadComplete, + ...rest +}: Props) => { + const [isLoading, setIsLoading] = useState(true); + return ( + + { + setIsLoading(false); + onLoadComplete?.(...args); + }} + onError={(...args) => { + setIsLoading(false); + onError?.(...args); + }} + /> + + ); +}; diff --git a/ts/features/messages/components/MessageDetail/index.tsx b/ts/features/messages/components/MessageDetail/index.tsx index 31a2bf26c1d..245dc7c0bad 100644 --- a/ts/features/messages/components/MessageDetail/index.tsx +++ b/ts/features/messages/components/MessageDetail/index.tsx @@ -155,17 +155,20 @@ const MessageDetailsComponent = ({ const messageTitle = useIOSelector(state => messageTitleSelector(state, messageId)) ?? title; + const serviceIdOpt = service?.id; const openAttachment = useCallback( (attachment: UIAttachment) => { navigation.navigate(MESSAGES_ROUTES.MESSAGES_NAVIGATOR, { screen: MESSAGES_ROUTES.MESSAGE_DETAIL_ATTACHMENT, params: { messageId, - attachmentId: attachment.id + serviceId: serviceIdOpt, + attachmentId: attachment.id, + isPN: false } }); }, - [messageId, navigation] + [messageId, navigation, serviceIdOpt] ); const renderThirdPartyAttachments = useCallback( diff --git a/ts/features/messages/designsystem/DSInfoScreenComponent.tsx b/ts/features/messages/designsystem/DSInfoScreenComponent.tsx new file mode 100644 index 00000000000..8ef4d2a62b7 --- /dev/null +++ b/ts/features/messages/designsystem/DSInfoScreenComponent.tsx @@ -0,0 +1,65 @@ +import * as React from "react"; +import { StyleSheet, Text, View } from "react-native"; +import { Body, H2, VSpacer } from "@pagopa/io-app-design-system"; +import { useFocusEffect } from "@react-navigation/native"; +import { setAccessibilityFocus } from "../../../utils/accessibility"; + +type Props = { + image: React.ReactNode; + title: string; + // this is necessary in order to render text with different formatting + body?: string | React.ReactNode; +}; + +const styles = StyleSheet.create({ + main: { + padding: 24, + flex: 1, + alignItems: "center", + justifyContent: "center" + }, + textAlignCenter: { + textAlign: "center" + } +}); + +const renderNode = (body: string | React.ReactNode) => { + if (typeof body === "string") { + return ( + + {body} + + ); + } + + return body; +}; + +/** + * A base screen that displays one image, text, and one bottom button + * @param props + * @constructor + */ +export const DSInfoScreenComponent: React.FunctionComponent = props => { + const elementRef = React.createRef(); + useFocusEffect( + React.useCallback(() => setAccessibilityFocus(elementRef), [elementRef]) + ); + + return ( + + {props.image} + +

+ {props.title} +

+ + {renderNode(props.body)} +
+ ); +}; diff --git a/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx b/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx new file mode 100644 index 00000000000..d6871e5ac3b --- /dev/null +++ b/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx @@ -0,0 +1,100 @@ +import * as React from "react"; +import { StyleSheet, View } from "react-native"; +import { + Body, + ButtonOutline, + IOColors, + IOStyles, + LoadingSpinner, + hexToRgba +} from "@pagopa/io-app-design-system"; +import I18n from "../../../i18n"; + +const styles = StyleSheet.create({ + back: { + zIndex: 0 + }, + container: { + flex: 1 + }, + overlay: { + position: "absolute", + inset: 0, + top: 0, + bottom: 0, + left: 0, + right: 0, + backgroundColor: IOColors.white, + zIndex: 1, + justifyContent: "center", + opacity: 1 + }, + refreshBox: { + height: 100, + flex: 1, + justifyContent: "center", + alignItems: "center" + }, + textCaption: { + padding: 24 + }, + whiteBg: { + backgroundColor: IOColors.white + } +}); + +type Props = Readonly<{ + backgroundColor?: string; + isLoading: boolean; + loadingCaption?: string; + opacity?: number; + onCancel?: () => void; + children: React.ReactNode; +}>; + +/** + * A Component to display and overlay spinner conditionally + */ +export const DSLoadingSpinnerOverlay = ({ + backgroundColor, + isLoading, + loadingCaption, + opacity = 1, + onCancel, + children +}: Props) => ( + + {isLoading && ( + + + + + + {loadingCaption || I18n.t("global.remoteStates.wait")} + + + {onCancel && ( + + + + )} + + + )} + {children} + +); diff --git a/ts/features/messages/navigation/MessagesNavigator.tsx b/ts/features/messages/navigation/MessagesNavigator.tsx index 75527b7e274..54cad7af9ae 100644 --- a/ts/features/messages/navigation/MessagesNavigator.tsx +++ b/ts/features/messages/navigation/MessagesNavigator.tsx @@ -10,42 +10,65 @@ import { useIOSelector } from "../../../store/hooks"; import { isGestureEnabled } from "../../../utils/navigation"; import { isPnEnabledSelector } from "../../../store/reducers/backendStatus"; import { MessageDetailAttachment } from "../screens/MessageAttachment"; +import { isDesignSystemEnabledSelector } from "../../../store/reducers/persistedPreferences"; +import { DSMessageAttachment } from "../screens/DSMessageAttachment"; import { MessagesParamsList } from "./params"; import { MESSAGES_ROUTES } from "./routes"; const Stack = createStackNavigator(); export const MessagesStackNavigator = () => { + const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); const isPnEnabled = useIOSelector(isPnEnabledSelector); return ( {isPnEnabled && ( - + )} ); diff --git a/ts/features/messages/navigation/params.ts b/ts/features/messages/navigation/params.ts index 162ed2e37a7..2377739f3d4 100644 --- a/ts/features/messages/navigation/params.ts +++ b/ts/features/messages/navigation/params.ts @@ -3,15 +3,15 @@ import EUCOVIDCERT_ROUTES from "../../euCovidCert/navigation/routes"; import PN_ROUTES from "../../pn/navigation/routes"; import { MessageRouterScreenNavigationParams } from "../screens/MessageRouterScreen"; import { MessageDetailScreenNavigationParams } from "../screens/MessageDetailScreen"; -import { MessageDetailAttachmentNavigationParams } from "../screens/MessageAttachment"; import { EUCovidCertParamsList } from "../../euCovidCert/navigation/params"; import { PnParamsList } from "../../pn/navigation/params"; +import { DSMessageAttachmentNavigationParams } from "../screens/DSMessageAttachment"; import { MESSAGES_ROUTES } from "./routes"; export type MessagesParamsList = { [MESSAGES_ROUTES.MESSAGE_ROUTER]: MessageRouterScreenNavigationParams; [MESSAGES_ROUTES.MESSAGE_DETAIL]: MessageDetailScreenNavigationParams; - [MESSAGES_ROUTES.MESSAGE_DETAIL_ATTACHMENT]: MessageDetailAttachmentNavigationParams; + [MESSAGES_ROUTES.MESSAGE_DETAIL_ATTACHMENT]: DSMessageAttachmentNavigationParams; [EUCOVIDCERT_ROUTES.MAIN]: NavigatorScreenParams; [PN_ROUTES.MAIN]: NavigatorScreenParams; }; diff --git a/ts/features/messages/screens/DSMessageAttachment.tsx b/ts/features/messages/screens/DSMessageAttachment.tsx new file mode 100644 index 00000000000..c1af95a3c76 --- /dev/null +++ b/ts/features/messages/screens/DSMessageAttachment.tsx @@ -0,0 +1,238 @@ +import React, { useCallback, useState } from "react"; +import { pipe } from "fp-ts/lib/function"; +import * as B from "fp-ts/lib/boolean"; +import ReactNativeBlobUtil from "react-native-blob-util"; +import { FooterWithButtons, Pictogram } from "@pagopa/io-app-design-system"; +import { DSInfoScreenComponent } from "../designsystem/DSInfoScreenComponent"; +import I18n from "../../../i18n"; +import { useIOSelector } from "../../../store/hooks"; +import { downloadedMessageAttachmentSelector } from "../store/reducers/downloads"; +import { UIAttachment, UIAttachmentId, UIMessageId } from "../types"; +import { isIos } from "../../../utils/platform"; +import { share } from "../../../utils/share"; +import { IOToast } from "../../../components/Toast"; +import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; +import { DSPdfViewer } from "../components/MessageAttachment/DSPdfViewer"; +import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; +import { MessagesParamsList } from "../navigation/params"; +import { + trackThirdPartyMessageAttachmentCorruptedFile, + trackThirdPartyMessageAttachmentPreviewSuccess, + trackThirdPartyMessageAttachmentUserAction +} from "../analytics"; +import { ServiceId } from "../../../../definitions/backend/ServiceId"; +import { + trackPNAttachmentOpen, + trackPNAttachmentOpeningSuccess, + trackPNAttachmentSave, + trackPNAttachmentSaveShare, + trackPNAttachmentShare +} from "../../pn/analytics"; + +export type DSMessageAttachmentNavigationParams = Readonly<{ + messageId: UIMessageId; + attachmentId: UIAttachmentId; + isPN: boolean; + serviceId?: ServiceId; +}>; + +const renderFooter = ( + attachment: UIAttachment, + downloadPath: string, + isPN: boolean, + attachmentCategory?: string +) => + isIos ? ( + { + onShare(isPN, attachmentCategory); + ReactNativeBlobUtil.ios.presentOptionsMenu(downloadPath); + }, + label: I18n.t("messagePDFPreview.singleBtn") + } + }} + /> + ) : ( + { + onShare(isPN, attachmentCategory); + share(`file://${downloadPath}`, undefined, false)().catch(_ => { + IOToast.show(I18n.t("messagePDFPreview.errors.sharing")); + }); + }, + label: I18n.t("global.buttons.share") + } + }} + third={{ + type: "Outline", + buttonProps: { + accessibilityLabel: I18n.t("messagePDFPreview.save"), + onPress: () => { + onDownload(isPN, attachmentCategory); + ReactNativeBlobUtil.MediaCollection.copyToMediaStore( + { + name: attachment.displayName, + parentFolder: "", + mimeType: attachment.contentType + }, + "Download", + downloadPath + ) + .then(_ => { + IOToast.show( + I18n.t("messagePDFPreview.savedAtLocation", { + name: attachment.displayName + }) + ); + }) + .catch(_ => { + IOToast.error(I18n.t("messagePDFPreview.errors.saving")); + }); + }, + label: I18n.t("messagePDFPreview.save") + } + }} + secondary={{ + type: "Solid", + buttonProps: { + accessibilityLabel: I18n.t("messagePDFPreview.open"), + onPress: () => { + onOpen(isPN, attachmentCategory); + ReactNativeBlobUtil.android + .actionViewIntent(downloadPath, attachment.contentType) + .catch(_ => { + IOToast.error(I18n.t("messagePDFPreview.errors.opening")); + }); + }, + label: I18n.t("messagePDFPreview.open") + } + }} + /> + ); + +const onPDFError = ( + messageId: UIMessageId, + isPN: boolean, + serviceId?: ServiceId, + attachmentCategory?: string +) => + pipe( + isPN, + B.fold( + () => { + trackThirdPartyMessageAttachmentCorruptedFile(messageId, serviceId); + IOToast.error(I18n.t("messageDetails.attachments.corruptedFile")); + }, + () => trackPNAttachmentOpeningSuccess("error", attachmentCategory) + ) + ); + +const onLoadComplete = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentPreviewSuccess(), + () => trackPNAttachmentOpeningSuccess("displayer", attachmentCategory) + ) + ); + +const onShare = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("share"), + () => + pipe( + isIos, + B.fold( + () => trackPNAttachmentShare(attachmentCategory), + () => trackPNAttachmentSaveShare(attachmentCategory) + ) + ) + ) + ); + +const onOpen = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("open"), + () => trackPNAttachmentOpen(attachmentCategory) + ) + ); + +const onDownload = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("download"), + () => trackPNAttachmentSave(attachmentCategory) + ) + ); + +export const DSMessageAttachment = ( + props: IOStackNavigationRouteProps< + MessagesParamsList, + "MESSAGE_DETAIL_ATTACHMENT" + > +): React.ReactElement => { + const { messageId, attachmentId, isPN, serviceId } = props.route.params; + const [isPDFRenderingError, setIsPDFRenderingError] = useState(false); + + const downloadedAttachment = useIOSelector(state => + downloadedMessageAttachmentSelector(state, messageId, attachmentId) + ); + const attachmentOpt = downloadedAttachment?.attachment; + const attachmentCategory = attachmentOpt?.category; + const downloadPathOpt = downloadedAttachment?.path; + + const onPDFRenderingError = useCallback(() => { + setIsPDFRenderingError(true); + onPDFError(messageId, isPN, serviceId, attachmentCategory); + }, [attachmentCategory, messageId, isPN, serviceId]); + + useHeaderSecondLevel({ + title: I18n.t("messagePDFPreview.title"), + supportRequest: true + }); + + if (!attachmentOpt || !downloadPathOpt) { + return ( + } + title={I18n.t("global.genericError")} + body={I18n.t("messageDetails.submitBugText")} + /> + ); + } + + // Safe area view testId message-attachment-preview + return ( + <> + {isPDFRenderingError ? ( + } + title={I18n.t("messagePDFPreview.errors.previewing.title")} + body={I18n.t("messagePDFPreview.errors.previewing.body")} + /> + ) : ( + onLoadComplete(isPN, attachmentCategory)} + /> + )} + {renderFooter(attachmentOpt, downloadPathOpt, isPN, attachmentCategory)} + + ); +}; diff --git a/ts/features/messages/screens/MessageAttachment.tsx b/ts/features/messages/screens/MessageAttachment.tsx index 03d545ed4c6..b37d463145e 100644 --- a/ts/features/messages/screens/MessageAttachment.tsx +++ b/ts/features/messages/screens/MessageAttachment.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useRef } from "react"; import { useNavigation } from "@react-navigation/native"; import * as O from "fp-ts/lib/Option"; import I18n from "../../../i18n"; -import { UIAttachmentId, UIMessageId } from "../types"; import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; import { MessageAttachmentPreview } from "../components/MessageAttachmentPreview"; import { MessagesParamsList } from "../navigation/params"; @@ -16,11 +15,6 @@ import { trackThirdPartyMessageAttachmentUserAction } from "../analytics"; -export type MessageDetailAttachmentNavigationParams = Readonly<{ - messageId: UIMessageId; - attachmentId: UIAttachmentId; -}>; - export const MessageDetailAttachment = ( props: IOStackNavigationRouteProps< MessagesParamsList, diff --git a/ts/features/messages/store/reducers/downloads.ts b/ts/features/messages/store/reducers/downloads.ts index 5fd32447df3..3cf6630f74e 100644 --- a/ts/features/messages/store/reducers/downloads.ts +++ b/ts/features/messages/store/reducers/downloads.ts @@ -1,3 +1,5 @@ +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { createSelector } from "reselect"; import { getType } from "typesafe-actions"; @@ -107,3 +109,17 @@ export const downloadPotForMessageAttachmentSelector = createSelector( return pot.none; } ); + +export const downloadedMessageAttachmentSelector = ( + state: GlobalState, + messageId: UIMessageId, + attachmentId: UIAttachmentId +) => + pipe( + state.entities.messages.downloads[messageId], + O.fromNullable, + O.chainNullableK(messageDownloads => messageDownloads[attachmentId]), + O.map(pot.toOption), + O.flatten, + O.toUndefined + ); diff --git a/ts/features/pn/utils/__tests__/index.test.ts b/ts/features/pn/utils/__tests__/index.test.ts index 3798dad00b0..f16d3b7a63a 100644 --- a/ts/features/pn/utils/__tests__/index.test.ts +++ b/ts/features/pn/utils/__tests__/index.test.ts @@ -3,6 +3,7 @@ import { isPNOptInMessage } from ".."; import { UIService } from "../../../../store/reducers/entities/services/types"; import { GlobalState } from "../../../../store/reducers/types"; import { CTAS } from "../../../messages/types/MessageCTA"; +import { ServiceId } from "../../../../../definitions/backend/ServiceId"; const pnOptInServiceId = () => "optInServiceId"; const navigateToServiceLink = () => @@ -188,7 +189,7 @@ const isPNOptInMessageTestInput: Array = [ CTAs: getMaybeCTAs(), service: { ...getMockService(), - id: "NotTheOptInOne" + id: "NotTheOptInOne" as ServiceId }, state: getMockState() }, diff --git a/ts/store/reducers/entities/services/types.ts b/ts/store/reducers/entities/services/types.ts index 2e1d6ee3cdd..e1bdac128a4 100644 --- a/ts/store/reducers/entities/services/types.ts +++ b/ts/store/reducers/entities/services/types.ts @@ -1,11 +1,12 @@ import { ImageURISource } from "react-native"; import { ServicePublic } from "../../../../../definitions/backend/ServicePublic"; +import { ServiceId } from "../../../../../definitions/backend/ServiceId"; /** * Domain-specific representation of a Service with aggregated data. */ export type UIService = { - id: string; + id: ServiceId; name: string; organizationName: string; organizationFiscalCode: string; From 8e398226c7b04f73764ae9d435b2ac58d8eafc6b Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Wed, 24 Jan 2024 11:50:37 +0100 Subject: [PATCH 2/7] Tests for the new selector --- .../reducers/__tests__/downloads.test.ts | 227 +++++++++++++++++- 1 file changed, 226 insertions(+), 1 deletion(-) diff --git a/ts/features/messages/store/reducers/__tests__/downloads.test.ts b/ts/features/messages/store/reducers/__tests__/downloads.test.ts index 3191b856d39..49bee044cdd 100644 --- a/ts/features/messages/store/reducers/__tests__/downloads.test.ts +++ b/ts/features/messages/store/reducers/__tests__/downloads.test.ts @@ -1,10 +1,235 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import { mockPdfAttachment } from "../../../__mocks__/attachment"; import { downloadAttachment, removeCachedAttachment } from "../../actions"; -import { Downloads, downloadsReducer } from "../downloads"; +import { + Download, + DownloadError, + Downloads, + INITIAL_STATE, + downloadedMessageAttachmentSelector, + downloadsReducer +} from "../downloads"; +import { + UIAttachment, + UIAttachmentId, + UIMessageId, + WithSkipMixpanelTrackingOnFailure +} from "../../../types"; +import { GlobalState } from "../../../../../store/reducers/types"; const path = "/path/attachment.pdf"; +describe("downloadedMessageAttachmentSelector", () => { + it("Should return undefined for an unmatching messageId", () => { + const attachmentId = "1" as UIAttachmentId; + const successDownload = { + attachment: { + messageId: "01HMXFQ803Q8JGQECKQF0EX6KX" as UIMessageId, + id: attachmentId + } as UIAttachment, + path: "randomPath" + } as Download; + const downloadSuccessAction = downloadAttachment.success(successDownload); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadSuccessAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return undefined for a matching messageId with an unmatching attachmentId", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const unrelatedAttachmentId = "2"; + const successDownload = { + attachment: { + messageId, + id: unrelatedAttachmentId + } as UIAttachment, + path: "randomPath" + } as Download; + const downloadSuccessAction = downloadAttachment.success(successDownload); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadSuccessAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const attachmentId = "1" as UIAttachmentId; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return undefined for an attachment that is loading", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const uiAttachmentRequest = { + messageId, + id: attachmentId, + skipMixpanelTrackingOnFailure: true + } as WithSkipMixpanelTrackingOnFailure; + const downloadRequestAction = + downloadAttachment.request(uiAttachmentRequest); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadRequestAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return undefined for an attachment that got an error", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const failedDownload = { + attachment: { + messageId, + id: attachmentId + } as UIAttachment, + error: new Error("An error") + } as DownloadError; + const downloadFailureAction = downloadAttachment.failure(failedDownload); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadFailureAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return undefined for an attachment that was cancelled before finishing the download", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const uiAttachmentCancelled = { + messageId, + id: attachmentId + } as UIAttachment; + const downloadCancelAction = downloadAttachment.cancel( + uiAttachmentCancelled + ); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadCancelAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return undefined for an attachment that was removed by a removeCachedAttachment action", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const successDownload = { + attachment: { + messageId, + id: attachmentId + } as UIAttachment, + path: "randomPath" + } as Download; + const removedCachedAttachmentAction = + removeCachedAttachment(successDownload); + const downloadsState = downloadsReducer( + INITIAL_STATE, + removedCachedAttachmentAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeUndefined(); + }); + it("Should return data for a matching downloaded attachment", () => { + const messageId = "01HMXFE7192J01KNK02BJAPMBR" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const downloadPath = "randomPath"; + const successDownload = { + attachment: { + messageId, + id: attachmentId + } as UIAttachment, + path: downloadPath + } as Download; + const downloadSuccessAction = downloadAttachment.success(successDownload); + const downloadsState = downloadsReducer( + INITIAL_STATE, + downloadSuccessAction + ); + const globalState = { + entities: { + messages: { + downloads: downloadsState + } + } + } as GlobalState; + const downloadedAttachment = downloadedMessageAttachmentSelector( + globalState, + messageId, + attachmentId + ); + expect(downloadedAttachment).toBeDefined(); + expect(downloadedAttachment?.attachment).toBeDefined(); + expect(downloadedAttachment?.attachment.messageId).toBe(messageId); + expect(downloadedAttachment?.attachment.id).toBe(attachmentId); + expect(downloadedAttachment?.path).toBe(downloadPath); + }); +}); + describe("downloadsReducer", () => { describe("given no download", () => { const initialState = {}; From f55256b8b529d7cd059132ea864a0d267e7d0ec2 Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Wed, 24 Jan 2024 15:22:36 +0100 Subject: [PATCH 3/7] WiP --- ts/components/LoadingSpinnerOverlay.tsx | 100 ++++++++++-------- ts/components/ui/BoxedRefreshIndicator.tsx | 21 ++-- ts/components/ui/Overlay.tsx | 58 +++++----- .../MessageAttachment/DSPdfViewer.tsx | 6 +- .../designsystem/DSInfoScreenComponent.tsx | 65 ------------ .../designsystem/DSLoadingSpinnerOverlay.tsx | 100 ------------------ .../messages/screens/DSMessageAttachment.tsx | 16 +-- 7 files changed, 106 insertions(+), 260 deletions(-) delete mode 100644 ts/features/messages/designsystem/DSInfoScreenComponent.tsx delete mode 100644 ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx diff --git a/ts/components/LoadingSpinnerOverlay.tsx b/ts/components/LoadingSpinnerOverlay.tsx index 57e9bbd8217..0bc93506229 100644 --- a/ts/components/LoadingSpinnerOverlay.tsx +++ b/ts/components/LoadingSpinnerOverlay.tsx @@ -1,22 +1,27 @@ -import { Text as NBButtonText } from "native-base"; import * as React from "react"; import { StyleSheet, View } from "react-native"; -import { IOColors, hexToRgba } from "@pagopa/io-app-design-system"; +import { + ButtonOutline, + IOColors, + hexToRgba +} from "@pagopa/io-app-design-system"; import I18n from "../i18n"; -import variables from "../theme/variables"; +import { useIOSelector } from "../store/hooks"; +import { isDesignSystemEnabledSelector } from "../store/reducers/persistedPreferences"; import ButtonDefaultOpacity from "./ButtonDefaultOpacity"; -import BoxedRefreshIndicator from "./ui/BoxedRefreshIndicator"; import { Overlay } from "./ui/Overlay"; import { IOStyles } from "./core/variables/IOStyles"; import { Body } from "./core/typography/Body"; +import BoxedRefreshIndicator from "./ui/BoxedRefreshIndicator"; const styles = StyleSheet.create({ textCaption: { - padding: variables.contentPadding + padding: 24 } }); type Props = Readonly<{ + children?: React.ReactNode; isLoading: boolean; loadingCaption?: string; loadingOpacity?: number; @@ -26,51 +31,56 @@ type Props = Readonly<{ /** * A Component to display and overlay spinner conditionally */ -class LoadingSpinnerOverlay extends React.Component { - public render() { - const { - children, - isLoading, - loadingCaption, - loadingOpacity = 0.7, - onCancel - } = this.props; - return ( - - - {loadingCaption || I18n.t("global.remoteStates.wait")} - - - } - action={ - onCancel && ( - +const LoadingSpinnerOverlay = ({ + children, + isLoading, + loadingCaption, + loadingOpacity, + onCancel +}: Props) => { + const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); + return ( + + + {loadingCaption || I18n.t("global.remoteStates.wait")} + + + } + action={ + onCancel && ( + + {isDesignSystemEnabled ? ( + + ) : ( - - {I18n.t("global.buttons.cancel")} - + {I18n.t("global.buttons.cancel")} - - ) - } - /> - ) - } - > - {children} - - ); - } -} + )} + + ) + } + /> + ) + } + > + {children} + + ); +}; export default LoadingSpinnerOverlay; diff --git a/ts/components/ui/BoxedRefreshIndicator.tsx b/ts/components/ui/BoxedRefreshIndicator.tsx index c0fb1ddf565..c1aac1c240d 100644 --- a/ts/components/ui/BoxedRefreshIndicator.tsx +++ b/ts/components/ui/BoxedRefreshIndicator.tsx @@ -1,6 +1,8 @@ import * as React from "react"; import { StyleSheet, View } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; +import { IOColors, LoadingSpinner } from "@pagopa/io-app-design-system"; +import { useIOSelector } from "../../store/hooks"; +import { isDesignSystemEnabledSelector } from "../../store/reducers/persistedPreferences"; import { RefreshIndicator } from "./RefreshIndicator"; const styles = StyleSheet.create({ @@ -22,12 +24,15 @@ interface Props { action?: React.ReactNode; } -const BoxedRefreshIndicator: React.SFC = props => ( - - - {props.caption ? props.caption : null} - {props.action ? props.action : null} - -); +const BoxedRefreshIndicator = ({ action, caption, white }: Props) => { + const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); + return ( + + {isDesignSystemEnabled ? : } + {caption ? caption : null} + {action ? action : null} + + ); +}; export default BoxedRefreshIndicator; diff --git a/ts/components/ui/Overlay.tsx b/ts/components/ui/Overlay.tsx index abe4e00cad0..6aa2ece0a73 100644 --- a/ts/components/ui/Overlay.tsx +++ b/ts/components/ui/Overlay.tsx @@ -1,9 +1,6 @@ -import { IOColors } from "@pagopa/io-app-design-system"; import * as React from "react"; import { StyleSheet, View } from "react-native"; - -const DEFAULT_OVERLAY_OPACITY = 1; -const DEFAULT_BACKGROUND_COLOR = IOColors.white; +import { IOColors } from "@pagopa/io-app-design-system"; const styles = StyleSheet.create({ container: { @@ -16,7 +13,7 @@ const styles = StyleSheet.create({ bottom: 0, left: 0, right: 0, - backgroundColor: DEFAULT_BACKGROUND_COLOR, + backgroundColor: IOColors.white, zIndex: 1, justifyContent: "center" }, @@ -26,9 +23,10 @@ const styles = StyleSheet.create({ }); type Props = Readonly<{ + backgroundColor?: string; + children?: React.ReactNode; foreground?: React.ReactNode; opacity?: number; - backgroundColor?: string; }>; /** @@ -36,28 +34,26 @@ type Props = Readonly<{ * * Used for loading spinners and error screens. */ -export const Overlay: React.SFC = props => { - const { - opacity = DEFAULT_OVERLAY_OPACITY, - backgroundColor = DEFAULT_BACKGROUND_COLOR - } = props; - return ( - - {props.foreground && ( - - {props.foreground} - - )} - - {props.children} - - ); -}; +export const Overlay = ({ + backgroundColor = IOColors.white, + children, + foreground, + opacity = 1 +}: Props) => ( + + {foreground && ( + + {foreground} + + )} + {children} + +); diff --git a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx index 9ec31ae22df..f4a238a990f 100644 --- a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx +++ b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx @@ -2,8 +2,8 @@ import React, { useState } from "react"; import { StyleSheet } from "react-native"; import Pdf from "react-native-pdf"; import { IOColors } from "@pagopa/io-app-design-system"; -import { DSLoadingSpinnerOverlay } from "../../designsystem/DSLoadingSpinnerOverlay"; import I18n from "../../../../i18n"; +import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"; const styles = StyleSheet.create({ pdf: { @@ -27,7 +27,7 @@ export const DSPdfViewer = ({ }: Props) => { const [isLoading, setIsLoading] = useState(true); return ( - @@ -44,6 +44,6 @@ export const DSPdfViewer = ({ onError?.(...args); }} /> - + ); }; diff --git a/ts/features/messages/designsystem/DSInfoScreenComponent.tsx b/ts/features/messages/designsystem/DSInfoScreenComponent.tsx deleted file mode 100644 index 8ef4d2a62b7..00000000000 --- a/ts/features/messages/designsystem/DSInfoScreenComponent.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from "react"; -import { StyleSheet, Text, View } from "react-native"; -import { Body, H2, VSpacer } from "@pagopa/io-app-design-system"; -import { useFocusEffect } from "@react-navigation/native"; -import { setAccessibilityFocus } from "../../../utils/accessibility"; - -type Props = { - image: React.ReactNode; - title: string; - // this is necessary in order to render text with different formatting - body?: string | React.ReactNode; -}; - -const styles = StyleSheet.create({ - main: { - padding: 24, - flex: 1, - alignItems: "center", - justifyContent: "center" - }, - textAlignCenter: { - textAlign: "center" - } -}); - -const renderNode = (body: string | React.ReactNode) => { - if (typeof body === "string") { - return ( - - {body} - - ); - } - - return body; -}; - -/** - * A base screen that displays one image, text, and one bottom button - * @param props - * @constructor - */ -export const DSInfoScreenComponent: React.FunctionComponent = props => { - const elementRef = React.createRef(); - useFocusEffect( - React.useCallback(() => setAccessibilityFocus(elementRef), [elementRef]) - ); - - return ( - - {props.image} - -

- {props.title} -

- - {renderNode(props.body)} -
- ); -}; diff --git a/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx b/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx deleted file mode 100644 index d6871e5ac3b..00000000000 --- a/ts/features/messages/designsystem/DSLoadingSpinnerOverlay.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from "react"; -import { StyleSheet, View } from "react-native"; -import { - Body, - ButtonOutline, - IOColors, - IOStyles, - LoadingSpinner, - hexToRgba -} from "@pagopa/io-app-design-system"; -import I18n from "../../../i18n"; - -const styles = StyleSheet.create({ - back: { - zIndex: 0 - }, - container: { - flex: 1 - }, - overlay: { - position: "absolute", - inset: 0, - top: 0, - bottom: 0, - left: 0, - right: 0, - backgroundColor: IOColors.white, - zIndex: 1, - justifyContent: "center", - opacity: 1 - }, - refreshBox: { - height: 100, - flex: 1, - justifyContent: "center", - alignItems: "center" - }, - textCaption: { - padding: 24 - }, - whiteBg: { - backgroundColor: IOColors.white - } -}); - -type Props = Readonly<{ - backgroundColor?: string; - isLoading: boolean; - loadingCaption?: string; - opacity?: number; - onCancel?: () => void; - children: React.ReactNode; -}>; - -/** - * A Component to display and overlay spinner conditionally - */ -export const DSLoadingSpinnerOverlay = ({ - backgroundColor, - isLoading, - loadingCaption, - opacity = 1, - onCancel, - children -}: Props) => ( - - {isLoading && ( - - - - - - {loadingCaption || I18n.t("global.remoteStates.wait")} - - - {onCancel && ( - - - - )} - - - )} - {children} - -); diff --git a/ts/features/messages/screens/DSMessageAttachment.tsx b/ts/features/messages/screens/DSMessageAttachment.tsx index c1af95a3c76..8d7c43453ce 100644 --- a/ts/features/messages/screens/DSMessageAttachment.tsx +++ b/ts/features/messages/screens/DSMessageAttachment.tsx @@ -2,8 +2,7 @@ import React, { useCallback, useState } from "react"; import { pipe } from "fp-ts/lib/function"; import * as B from "fp-ts/lib/boolean"; import ReactNativeBlobUtil from "react-native-blob-util"; -import { FooterWithButtons, Pictogram } from "@pagopa/io-app-design-system"; -import { DSInfoScreenComponent } from "../designsystem/DSInfoScreenComponent"; +import { FooterWithButtons } from "@pagopa/io-app-design-system"; import I18n from "../../../i18n"; import { useIOSelector } from "../../../store/hooks"; import { downloadedMessageAttachmentSelector } from "../store/reducers/downloads"; @@ -28,6 +27,7 @@ import { trackPNAttachmentSaveShare, trackPNAttachmentShare } from "../../pn/analytics"; +import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent"; export type DSMessageAttachmentNavigationParams = Readonly<{ messageId: UIMessageId; @@ -208,10 +208,10 @@ export const DSMessageAttachment = ( if (!attachmentOpt || !downloadPathOpt) { return ( - } + ); } @@ -220,10 +220,10 @@ export const DSMessageAttachment = ( return ( <> {isPDFRenderingError ? ( - } + ) : ( Date: Wed, 24 Jan 2024 15:44:56 +0100 Subject: [PATCH 4/7] Fixed failing test snapshots --- ts/components/LoadingSpinnerOverlay.tsx | 2 +- .../__tests__/WebviewComponent.test.tsx | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ts/components/LoadingSpinnerOverlay.tsx b/ts/components/LoadingSpinnerOverlay.tsx index 0bc93506229..22271727f72 100644 --- a/ts/components/LoadingSpinnerOverlay.tsx +++ b/ts/components/LoadingSpinnerOverlay.tsx @@ -35,7 +35,7 @@ const LoadingSpinnerOverlay = ({ children, isLoading, loadingCaption, - loadingOpacity, + loadingOpacity = 0.7, onCancel }: Props) => { const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); diff --git a/ts/components/__tests__/WebviewComponent.test.tsx b/ts/components/__tests__/WebviewComponent.test.tsx index 3b8b632bd24..9b0fc0c36b2 100644 --- a/ts/components/__tests__/WebviewComponent.test.tsx +++ b/ts/components/__tests__/WebviewComponent.test.tsx @@ -1,11 +1,28 @@ import { render } from "@testing-library/react-native"; +import { Provider } from "react-redux"; +import configureMockStore from "redux-mock-store"; import React from "react"; import WebviewComponent from "../WebviewComponent"; +import { appReducer } from "../../store/reducers"; +import { applicationChangeState } from "../../store/actions/application"; +import { GlobalState } from "../../store/reducers/types"; describe("WebviewComponent tests", () => { it("snapshot for component", () => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const enrichedState = { + ...globalState, + persistedPreferences: { + ...globalState.persistedPreferences, + isDesignSystemEnabled: false + } + }; + const mockStore = configureMockStore(); + const store: ReturnType = mockStore(enrichedState); const component = render( - + + + ); expect(component).toMatchSnapshot(); }); From 4d34f55a1e4f150c5f897f0d14e6ac90c623dc4d Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Thu, 25 Jan 2024 10:23:25 +0100 Subject: [PATCH 5/7] Component and Screen snapshot tests --- .../MessageAttachment/DSPdfViewer.tsx | 2 +- .../__test__/DSPdfViewer.test.tsx | 29 + .../__snapshots__/DSPdfViewer.test.tsx.snap | 250 +++ .../messages/screens/DSMessageAttachment.tsx | 2 - .../__tests__/DSMessageAttachment.test.tsx | 72 + .../__tests__/MessageRouterScreen.test.tsx | 4 +- .../DSMessageAttachment.test.tsx.snap | 1479 +++++++++++++++++ 7 files changed, 1833 insertions(+), 5 deletions(-) create mode 100644 ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx create mode 100644 ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap create mode 100644 ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx create mode 100644 ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap diff --git a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx index f4a238a990f..2e9256d876a 100644 --- a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx +++ b/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx @@ -33,7 +33,7 @@ export const DSPdfViewer = ({ > { setIsLoading(false); diff --git a/ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx b/ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx new file mode 100644 index 00000000000..31917e7afcd --- /dev/null +++ b/ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import { render } from "@testing-library/react-native"; +import configureMockStore from "redux-mock-store"; +import { Provider } from "react-redux"; +import { DSPdfViewer } from "../DSPdfViewer"; +import { appReducer } from "../../../../../store/reducers"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { GlobalState } from "../../../../../store/reducers/types"; + +describe("DSPdfViwer", () => { + it("should match the snapshot", () => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const enrichedState = { + ...globalState, + persistedPreferences: { + ...globalState.persistedPreferences, + isDesignSystemEnabled: true + } + }; + const mockStore = configureMockStore(); + const store: ReturnType = mockStore(enrichedState); + const component = render( + + + + ); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap b/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap new file mode 100644 index 00000000000..17969a6ed4d --- /dev/null +++ b/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap @@ -0,0 +1,250 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DSPdfViwer should match the snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + Loading document... + + + + + + +`; diff --git a/ts/features/messages/screens/DSMessageAttachment.tsx b/ts/features/messages/screens/DSMessageAttachment.tsx index 8d7c43453ce..c0d64d22e16 100644 --- a/ts/features/messages/screens/DSMessageAttachment.tsx +++ b/ts/features/messages/screens/DSMessageAttachment.tsx @@ -215,8 +215,6 @@ export const DSMessageAttachment = ( /> ); } - - // Safe area view testId message-attachment-preview return ( <> {isPDFRenderingError ? ( diff --git a/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx b/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx new file mode 100644 index 00000000000..174a2279d91 --- /dev/null +++ b/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx @@ -0,0 +1,72 @@ +import { createStore } from "redux"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { UIAttachmentId, UIMessageId } from "../../types"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import { MESSAGES_ROUTES } from "../../navigation/routes"; +import { appReducer } from "../../../../store/reducers"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { DSMessageAttachment } from "../DSMessageAttachment"; +import { ServiceId } from "../../../../../definitions/backend/ServiceId"; + +describe("DSMessageAttachment", () => { + it("Should match the snapshot when there is an error", () => { + const messageId = "01HMZWRG7549N76017YR8YBSG2" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const serviceId = "01HMZXFS84T1Q1BN6GXRYT63VJ" as ServiceId; + const screen = renderScreen(messageId, attachmentId, serviceId, "failure"); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("Should match the snapshot when everything went fine", () => { + const messageId = "01HMZWRG7549N76017YR8YBSG2" as UIMessageId; + const attachmentId = "1" as UIAttachmentId; + const serviceId = "01HMZXFS84T1Q1BN6GXRYT63VJ" as ServiceId; + const screen = renderScreen(messageId, attachmentId, serviceId, "success"); + expect(screen.toJSON()).toMatchSnapshot(); + }); +}); + +const renderScreen = ( + messageId: UIMessageId, + attachmentId: UIAttachmentId, + serviceId: ServiceId, + configuration: "failure" | "success" +) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const enrichedState = { + ...globalState, + persistedPreferences: { + ...globalState.persistedPreferences, + isDesignSystemEnabled: true + }, + entities: { + ...globalState.entities, + messages: { + ...globalState.entities.messages, + downloads: { + ...globalState.entities.messages.downloads, + messageId: + configuration === "success" + ? { + attachmentId: pot.some({ + attachment: { + messageId, + id: attachmentId, + cagegory: "default" + }, + path: "file:///fileName.pdf" + }) + } + : undefined + } + } + } + }; + const store = createStore(appReducer, enrichedState as any); + + return renderScreenWithNavigationStoreContext( + DSMessageAttachment, + MESSAGES_ROUTES.MESSAGE_DETAIL_ATTACHMENT, + { messageId, attachmentId, isPN: false, serviceId }, + store + ); +}; diff --git a/ts/features/messages/screens/__tests__/MessageRouterScreen.test.tsx b/ts/features/messages/screens/__tests__/MessageRouterScreen.test.tsx index 678d072ba1d..dfe8164405d 100644 --- a/ts/features/messages/screens/__tests__/MessageRouterScreen.test.tsx +++ b/ts/features/messages/screens/__tests__/MessageRouterScreen.test.tsx @@ -6,7 +6,7 @@ import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWr import { MessageRouterScreen } from "../MessageRouterScreen"; import { getMessageDataAction } from "../../store/actions"; import { ServiceId } from "../../../../../definitions/backend/ServiceId"; -import * as ASD from "../../../../store/hooks"; +import * as IOHooks from "../../../../store/hooks"; import { MessageGetStatus } from "../../store/reducers/messageGetStatus"; import { MESSAGES_ROUTES } from "../../navigation/routes"; @@ -107,7 +107,7 @@ const renderScreen = ( const mockedDispatch = jest.fn(); jest - .spyOn(ASD, "useIODispatch") + .spyOn(IOHooks, "useIODispatch") .mockImplementation(() => mockedDispatch as Dispatch); const renderedMessageRouterScreen = renderScreenWithNavigationStoreContext( diff --git a/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap b/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap new file mode 100644 index 00000000000..8d4ca478e74 --- /dev/null +++ b/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap @@ -0,0 +1,1479 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DSMessageAttachment Should match the snapshot when everything went fine 1`] = ` + + + + + + + + + + + + + + + + + PDF Preview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + There is a temporary problem, please try again. + + + + If the problem is not resolved, report it with the '?' icon at the top right, thank you! + + + + + + + + + + + + + + +`; + +exports[`DSMessageAttachment Should match the snapshot when there is an error 1`] = ` + + + + + + + + + + + + + + + + + PDF Preview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + There is a temporary problem, please try again. + + + + If the problem is not resolved, report it with the '?' icon at the top right, thank you! + + + + + + + + + + + + + + +`; From 2926db23fe61bba9101ede398af150b5a4709926 Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Thu, 25 Jan 2024 18:22:24 +0100 Subject: [PATCH 6/7] Tests for remaining UI components --- .../__tests__/LoadingSpinnerOverlay.test.tsx | 77 +++ .../LoadingSpinnerOverlay.test.tsx.snap | 654 ++++++++++++++++++ ts/components/ui/BoxedRefreshIndicator.tsx | 6 +- .../__test__/BoxedRefreshIndicator.test.tsx | 43 ++ ts/components/ui/__test__/Overlay.test.tsx | 27 + .../BoxedRefreshIndicator.test.tsx.snap | 339 +++++++++ .../__snapshots__/Overlay.test.tsx.snap | 78 +++ .../__tests__/DSMessageAttachment.test.tsx | 52 +- .../DSMessageAttachment.test.tsx.snap | 565 +++++++++------ 9 files changed, 1602 insertions(+), 239 deletions(-) create mode 100644 ts/components/__tests__/LoadingSpinnerOverlay.test.tsx create mode 100644 ts/components/__tests__/__snapshots__/LoadingSpinnerOverlay.test.tsx.snap create mode 100644 ts/components/ui/__test__/BoxedRefreshIndicator.test.tsx create mode 100644 ts/components/ui/__test__/Overlay.test.tsx create mode 100644 ts/components/ui/__test__/__snapshots__/BoxedRefreshIndicator.test.tsx.snap create mode 100644 ts/components/ui/__test__/__snapshots__/Overlay.test.tsx.snap diff --git a/ts/components/__tests__/LoadingSpinnerOverlay.test.tsx b/ts/components/__tests__/LoadingSpinnerOverlay.test.tsx new file mode 100644 index 00000000000..98acfdc04b3 --- /dev/null +++ b/ts/components/__tests__/LoadingSpinnerOverlay.test.tsx @@ -0,0 +1,77 @@ +import * as React from "react"; +import { Text } from "react-native"; +import { render } from "@testing-library/react-native"; +import configureMockStore from "redux-mock-store"; +import { Provider } from "react-redux"; +import { appReducer } from "../../store/reducers"; +import { applicationChangeState } from "../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../store/actions/persistedPreferences"; +import { GlobalState } from "../../store/reducers/types"; +import LoadingSpinnerOverlay from "../LoadingSpinnerOverlay"; + +describe("LoadingSpinnerOverlay", () => { + it("Should match base no-loading snapshot", () => { + const component = renderComponent(false); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match base loading snapshot", () => { + const component = renderComponent(true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match all-properties and not-loading snapshot", () => { + const child = This is a child; + const loadingCaption = "This is the loading caption"; + const loadingOpacity = 0.65; + const onCancelCallback = () => undefined; + const component = renderComponent( + false, + child, + loadingCaption, + loadingOpacity, + onCancelCallback + ); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match all-properties and loading snapshot", () => { + const child = This is a child; + const loadingCaption = "This is the loading caption"; + const loadingOpacity = 0.65; + const onCancelCallback = () => undefined; + const component = renderComponent( + true, + child, + loadingCaption, + loadingOpacity, + onCancelCallback + ); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); + +const renderComponent = ( + isLoading: boolean, + children?: React.ReactNode, + loadingCaption?: string, + loadingOpacity?: number, + onCancel?: () => void +) => { + const initialState = appReducer(undefined, applicationChangeState("active")); + const dsState = appReducer( + initialState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const mockStore = configureMockStore(); + const store: ReturnType = mockStore(dsState); + return render( + + + {children} + + + ); +}; diff --git a/ts/components/__tests__/__snapshots__/LoadingSpinnerOverlay.test.tsx.snap b/ts/components/__tests__/__snapshots__/LoadingSpinnerOverlay.test.tsx.snap new file mode 100644 index 00000000000..5f42ae771f3 --- /dev/null +++ b/ts/components/__tests__/__snapshots__/LoadingSpinnerOverlay.test.tsx.snap @@ -0,0 +1,654 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LoadingSpinnerOverlay Should match all-properties and loading snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + This is the loading caption + + + + + + + Cancel + + + + + + + + + This is a child + + + +`; + +exports[`LoadingSpinnerOverlay Should match all-properties and not-loading snapshot 1`] = ` + + + + This is a child + + + +`; + +exports[`LoadingSpinnerOverlay Should match base loading snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + Wait a few seconds.. + + + + + + +`; + +exports[`LoadingSpinnerOverlay Should match base no-loading snapshot 1`] = ` + + + +`; diff --git a/ts/components/ui/BoxedRefreshIndicator.tsx b/ts/components/ui/BoxedRefreshIndicator.tsx index c1aac1c240d..eb102cb687b 100644 --- a/ts/components/ui/BoxedRefreshIndicator.tsx +++ b/ts/components/ui/BoxedRefreshIndicator.tsx @@ -18,11 +18,11 @@ const styles = StyleSheet.create({ } }); -interface Props { +type Props = { + action?: React.ReactNode; caption?: React.ReactNode; white?: boolean; - action?: React.ReactNode; -} +}; const BoxedRefreshIndicator = ({ action, caption, white }: Props) => { const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); diff --git a/ts/components/ui/__test__/BoxedRefreshIndicator.test.tsx b/ts/components/ui/__test__/BoxedRefreshIndicator.test.tsx new file mode 100644 index 00000000000..3245dc4e74d --- /dev/null +++ b/ts/components/ui/__test__/BoxedRefreshIndicator.test.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { Text } from "react-native"; +import { render } from "@testing-library/react-native"; +import configureMockStore from "redux-mock-store"; +import { Provider } from "react-redux"; +import { appReducer } from "../../../store/reducers"; +import { applicationChangeState } from "../../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../../store/actions/persistedPreferences"; +import { GlobalState } from "../../../store/reducers/types"; +import BoxedRefreshIndicator from "../BoxedRefreshIndicator"; + +describe("BoxedRefreshIndicator", () => { + it("Should match base snapshot", () => { + const component = renderComponent(); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match all-properties snapshot", () => { + const action = This is the action; + const caption = This is the caption; + const white = true; + const component = renderComponent(action, caption, white); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); + +const renderComponent = ( + action?: React.ReactNode, + caption?: React.ReactNode, + white?: boolean +) => { + const initialState = appReducer(undefined, applicationChangeState("active")); + const dsState = appReducer( + initialState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const mockStore = configureMockStore(); + const store: ReturnType = mockStore(dsState); + return render( + + + + ); +}; diff --git a/ts/components/ui/__test__/Overlay.test.tsx b/ts/components/ui/__test__/Overlay.test.tsx new file mode 100644 index 00000000000..930cbc12f71 --- /dev/null +++ b/ts/components/ui/__test__/Overlay.test.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import { render } from "@testing-library/react-native"; +import { Text } from "react-native"; +import { Overlay } from "../Overlay"; + +describe("Overlay", () => { + it("Should match base snapshot", () => { + const component = render(); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match all-properties snapshot", () => { + const backgroundColor = "#FF0000"; + const opacity = 0.65; + const foreground = This is a foreground; + const children = This is a child; + const component = render( + + {children} + + ); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/ts/components/ui/__test__/__snapshots__/BoxedRefreshIndicator.test.tsx.snap b/ts/components/ui/__test__/__snapshots__/BoxedRefreshIndicator.test.tsx.snap new file mode 100644 index 00000000000..a0c0c9aebab --- /dev/null +++ b/ts/components/ui/__test__/__snapshots__/BoxedRefreshIndicator.test.tsx.snap @@ -0,0 +1,339 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BoxedRefreshIndicator Should match all-properties snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + This is the caption + + + This is the action + + +`; + +exports[`BoxedRefreshIndicator Should match base snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + +`; diff --git a/ts/components/ui/__test__/__snapshots__/Overlay.test.tsx.snap b/ts/components/ui/__test__/__snapshots__/Overlay.test.tsx.snap new file mode 100644 index 00000000000..8b954f15681 --- /dev/null +++ b/ts/components/ui/__test__/__snapshots__/Overlay.test.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Overlay Should match all-properties snapshot 1`] = ` + + + + This is a foreground + + + + + This is a child + + + +`; + +exports[`Overlay Should match base snapshot 1`] = ` + + + +`; diff --git a/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx b/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx index 174a2279d91..9328dbbdb5b 100644 --- a/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx +++ b/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx @@ -1,12 +1,13 @@ import { createStore } from "redux"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { UIAttachmentId, UIMessageId } from "../../types"; +import { UIAttachment, UIAttachmentId, UIMessageId } from "../../types"; import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; import { MESSAGES_ROUTES } from "../../navigation/routes"; import { appReducer } from "../../../../store/reducers"; import { applicationChangeState } from "../../../../store/actions/application"; import { DSMessageAttachment } from "../DSMessageAttachment"; import { ServiceId } from "../../../../../definitions/backend/ServiceId"; +import { downloadAttachment } from "../../store/actions"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; describe("DSMessageAttachment", () => { it("Should match the snapshot when there is an error", () => { @@ -31,37 +32,22 @@ const renderScreen = ( serviceId: ServiceId, configuration: "failure" | "success" ) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const enrichedState = { - ...globalState, - persistedPreferences: { - ...globalState.persistedPreferences, - isDesignSystemEnabled: true - }, - entities: { - ...globalState.entities, - messages: { - ...globalState.entities.messages, - downloads: { - ...globalState.entities.messages.downloads, - messageId: - configuration === "success" - ? { - attachmentId: pot.some({ - attachment: { - messageId, - id: attachmentId, - cagegory: "default" - }, - path: "file:///fileName.pdf" - }) - } - : undefined - } - } - } - }; - const store = createStore(appReducer, enrichedState as any); + const initialState = appReducer(undefined, applicationChangeState("active")); + const designSystemState = appReducer( + initialState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const withDownloadState = appReducer( + designSystemState, + downloadAttachment.success({ + attachment: { id: attachmentId, messageId } as UIAttachment, + path: "file:///fileName.pdf" + }) + ); + const store = createStore( + appReducer, + (configuration === "success" ? withDownloadState : designSystemState) as any + ); return renderScreenWithNavigationStoreContext( DSMessageAttachment, diff --git a/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap b/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap index 8d4ca478e74..46778782234 100644 --- a/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap +++ b/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap @@ -483,250 +483,409 @@ exports[`DSMessageAttachment Should match the snapshot when everything went fine } } > - - - + - - - - - - - - - - - + - - - + > + + + + + + + + + + + + - + + Loading document... + + + + + + + + + - There is a temporary problem, please try again. -
+ } + > - - If the problem is not resolved, report it with the '?' icon at the top right, thank you! - + + + + + Save or share + + + + + - - + + From 33e78ab5032c6c8a3a0722db70dcb48b1f95908d Mon Sep 17 00:00:00 2001 From: Andrea Piai Date: Fri, 26 Jan 2024 15:32:28 +0100 Subject: [PATCH 7/7] Renamed files --- .../LegacyMessageAttachmentPreview.tsx} | 42 +- .../{DSPdfViewer.tsx => LegacyPdfViewer.tsx} | 8 +- .../PdfViewer.tsx | 8 +- ...SPdfViewer.test.tsx => PdfViewer.test.tsx} | 6 +- ....test.tsx.snap => PdfViewer.test.tsx.snap} | 2 +- .../MessageAttachmentPreview.test.tsx | 8 +- .../messages/navigation/MessagesNavigator.tsx | 8 +- ts/features/messages/navigation/params.ts | 4 +- .../messages/screens/DSMessageAttachment.tsx | 236 ---- .../screens/LegacyMessageAttachment.tsx | 76 ++ .../messages/screens/MessageAttachment.tsx | 272 +++- ...nt.test.tsx => MessageAttachment.test.tsx} | 6 +- ...x.snap => MessageAttachment.test.tsx.snap} | 1146 ++++++++--------- .../pn/screens/AttachmentPreviewScreen.tsx | 4 +- 14 files changed, 914 insertions(+), 912 deletions(-) rename ts/features/messages/components/{MessageAttachmentPreview.tsx => MessageAttachment/LegacyMessageAttachmentPreview.tsx} (85%) rename ts/features/messages/components/MessageAttachment/{DSPdfViewer.tsx => LegacyPdfViewer.tsx} (93%) rename ts/features/messages/components/{MessageDetail => MessageAttachment}/PdfViewer.tsx (94%) rename ts/features/messages/components/MessageAttachment/__test__/{DSPdfViewer.test.tsx => PdfViewer.test.tsx} (87%) rename ts/features/messages/components/MessageAttachment/__test__/__snapshots__/{DSPdfViewer.test.tsx.snap => PdfViewer.test.tsx.snap} (99%) delete mode 100644 ts/features/messages/screens/DSMessageAttachment.tsx create mode 100644 ts/features/messages/screens/LegacyMessageAttachment.tsx rename ts/features/messages/screens/__tests__/{DSMessageAttachment.test.tsx => MessageAttachment.test.tsx} (94%) rename ts/features/messages/screens/__tests__/__snapshots__/{DSMessageAttachment.test.tsx.snap => MessageAttachment.test.tsx.snap} (99%) diff --git a/ts/features/messages/components/MessageAttachmentPreview.tsx b/ts/features/messages/components/MessageAttachment/LegacyMessageAttachmentPreview.tsx similarity index 85% rename from ts/features/messages/components/MessageAttachmentPreview.tsx rename to ts/features/messages/components/MessageAttachment/LegacyMessageAttachmentPreview.tsx index bd635ea16ef..c20551a8d11 100644 --- a/ts/features/messages/components/MessageAttachmentPreview.tsx +++ b/ts/features/messages/components/MessageAttachment/LegacyMessageAttachmentPreview.tsx @@ -3,28 +3,28 @@ import { useNavigation } from "@react-navigation/native"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { ActivityIndicator, SafeAreaView, StyleSheet } from "react-native"; import ReactNativeBlobUtil from "react-native-blob-util"; -import image from "../../../../img/servicesStatus/error-detail-icon.png"; -import { H2 } from "../../../components/core/typography/H2"; -import { renderInfoRasterImage } from "../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../components/ui/FooterWithButtons"; -import I18n from "../../../i18n"; +import image from "../../../../../img/servicesStatus/error-detail-icon.png"; +import { H2 } from "../../../../components/core/typography/H2"; +import { renderInfoRasterImage } from "../../../../components/infoScreen/imageRendering"; +import { InfoScreenComponent } from "../../../../components/infoScreen/InfoScreenComponent"; +import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent"; +import FooterWithButtons from "../../../../components/ui/FooterWithButtons"; +import I18n from "../../../../i18n"; import { cancelPreviousAttachmentDownload, downloadAttachment -} from "../store/actions"; -import { useIODispatch, useIOSelector } from "../../../store/hooks"; -import { downloadPotForMessageAttachmentSelector } from "../store/reducers/downloads"; -import { UIAttachment, UIMessageId } from "../types"; -import variables from "../../../theme/variables"; -import { emptyContextualHelp } from "../../../utils/emptyContextualHelp"; -import { isIos } from "../../../utils/platform"; -import { isStrictNone } from "../../../utils/pot"; -import { share } from "../../../utils/share"; -import { showToast } from "../../../utils/showToast"; -import { confirmButtonProps } from "../../../components/buttons/ButtonConfigurations"; -import PdfViewer from "./MessageDetail/PdfViewer"; +} from "../../store/actions"; +import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { downloadPotForMessageAttachmentSelector } from "../../store/reducers/downloads"; +import { UIAttachment, UIMessageId } from "../../types"; +import variables from "../../../../theme/variables"; +import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; +import { isIos } from "../../../../utils/platform"; +import { isStrictNone } from "../../../../utils/pot"; +import { share } from "../../../../utils/share"; +import { showToast } from "../../../../utils/showToast"; +import { confirmButtonProps } from "../../../../components/buttons/ButtonConfigurations"; +import LegacyPdfViewer from "./LegacyPdfViewer"; type Props = { messageId: UIMessageId; @@ -86,7 +86,7 @@ const renderPDF = ( I18n.t("messagePDFPreview.errors.previewing.body") ) ) : ( - ); -export const MessageAttachmentPreview = ({ +export const LegacyMessageAttachmentPreview = ({ enableDownloadAttachment = true, ...props }: Props): React.ReactElement => { diff --git a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx b/ts/features/messages/components/MessageAttachment/LegacyPdfViewer.tsx similarity index 93% rename from ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx rename to ts/features/messages/components/MessageAttachment/LegacyPdfViewer.tsx index 2e9256d876a..d10f3fa1d80 100644 --- a/ts/features/messages/components/MessageAttachment/DSPdfViewer.tsx +++ b/ts/features/messages/components/MessageAttachment/LegacyPdfViewer.tsx @@ -2,8 +2,8 @@ import React, { useState } from "react"; import { StyleSheet } from "react-native"; import Pdf from "react-native-pdf"; import { IOColors } from "@pagopa/io-app-design-system"; -import I18n from "../../../../i18n"; import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"; +import I18n from "../../../../i18n"; const styles = StyleSheet.create({ pdf: { @@ -18,7 +18,7 @@ type OwnProps = { type Props = OwnProps & Omit, "source">; -export const DSPdfViewer = ({ +const LegacyPdfViewer = ({ style, downloadPath, onError, @@ -26,9 +26,11 @@ export const DSPdfViewer = ({ ...rest }: Props) => { const [isLoading, setIsLoading] = useState(true); + return ( ); }; + +export default LegacyPdfViewer; diff --git a/ts/features/messages/components/MessageDetail/PdfViewer.tsx b/ts/features/messages/components/MessageAttachment/PdfViewer.tsx similarity index 94% rename from ts/features/messages/components/MessageDetail/PdfViewer.tsx rename to ts/features/messages/components/MessageAttachment/PdfViewer.tsx index 791b519fcda..b26f69052c2 100644 --- a/ts/features/messages/components/MessageDetail/PdfViewer.tsx +++ b/ts/features/messages/components/MessageAttachment/PdfViewer.tsx @@ -2,8 +2,8 @@ import React, { useState } from "react"; import { StyleSheet } from "react-native"; import Pdf from "react-native-pdf"; import { IOColors } from "@pagopa/io-app-design-system"; -import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"; import I18n from "../../../../i18n"; +import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"; const styles = StyleSheet.create({ pdf: { @@ -18,7 +18,7 @@ type OwnProps = { type Props = OwnProps & Omit, "source">; -const PdfViewer = ({ +export const PdfViewer = ({ style, downloadPath, onError, @@ -26,11 +26,9 @@ const PdfViewer = ({ ...rest }: Props) => { const [isLoading, setIsLoading] = useState(true); - return ( ); }; - -export default PdfViewer; diff --git a/ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx b/ts/features/messages/components/MessageAttachment/__test__/PdfViewer.test.tsx similarity index 87% rename from ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx rename to ts/features/messages/components/MessageAttachment/__test__/PdfViewer.test.tsx index 31917e7afcd..72145e61fab 100644 --- a/ts/features/messages/components/MessageAttachment/__test__/DSPdfViewer.test.tsx +++ b/ts/features/messages/components/MessageAttachment/__test__/PdfViewer.test.tsx @@ -2,12 +2,12 @@ import * as React from "react"; import { render } from "@testing-library/react-native"; import configureMockStore from "redux-mock-store"; import { Provider } from "react-redux"; -import { DSPdfViewer } from "../DSPdfViewer"; +import { PdfViewer } from "../PdfViewer"; import { appReducer } from "../../../../../store/reducers"; import { applicationChangeState } from "../../../../../store/actions/application"; import { GlobalState } from "../../../../../store/reducers/types"; -describe("DSPdfViwer", () => { +describe("PdfViewer", () => { it("should match the snapshot", () => { const globalState = appReducer(undefined, applicationChangeState("active")); const enrichedState = { @@ -21,7 +21,7 @@ describe("DSPdfViwer", () => { const store: ReturnType = mockStore(enrichedState); const component = render( - + ); expect(component.toJSON()).toMatchSnapshot(); diff --git a/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap b/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/PdfViewer.test.tsx.snap similarity index 99% rename from ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap rename to ts/features/messages/components/MessageAttachment/__test__/__snapshots__/PdfViewer.test.tsx.snap index 17969a6ed4d..07e6e5f8ead 100644 --- a/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/DSPdfViewer.test.tsx.snap +++ b/ts/features/messages/components/MessageAttachment/__test__/__snapshots__/PdfViewer.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DSPdfViwer should match the snapshot 1`] = ` +exports[`PdfViewer should match the snapshot 1`] = ` ; -jest.mock("../MessageDetail/PdfViewer", () => () => mockPdfViewer); +jest.mock("../MessageAttachment/LegacyPdfViewer", () => () => mockPdfViewer); describe("MessageAttachmentPreview", () => { describe("when enableDownloadAttachment is false", () => { @@ -67,7 +67,7 @@ describe("MessageAttachmentPreview", () => { }); const renderComponent = ( - props: React.ComponentProps, + props: React.ComponentProps, downloads: Downloads = {} ) => { const globalState = appReducer(undefined, applicationChangeState("active")); @@ -84,7 +84,7 @@ const renderComponent = ( return { component: renderScreenWithNavigationStoreContext( - () => , + () => , "DUMMY", {}, store diff --git a/ts/features/messages/navigation/MessagesNavigator.tsx b/ts/features/messages/navigation/MessagesNavigator.tsx index 54cad7af9ae..56680906c65 100644 --- a/ts/features/messages/navigation/MessagesNavigator.tsx +++ b/ts/features/messages/navigation/MessagesNavigator.tsx @@ -9,9 +9,9 @@ import PN_ROUTES from "../../pn/navigation/routes"; import { useIOSelector } from "../../../store/hooks"; import { isGestureEnabled } from "../../../utils/navigation"; import { isPnEnabledSelector } from "../../../store/reducers/backendStatus"; -import { MessageDetailAttachment } from "../screens/MessageAttachment"; +import { LegacyMessageDetailAttachment } from "../screens/LegacyMessageAttachment"; import { isDesignSystemEnabledSelector } from "../../../store/reducers/persistedPreferences"; -import { DSMessageAttachment } from "../screens/DSMessageAttachment"; +import { MessageAttachment } from "../screens/MessageAttachment"; import { MessagesParamsList } from "./params"; import { MESSAGES_ROUTES } from "./routes"; @@ -46,7 +46,9 @@ export const MessagesStackNavigator = () => { ; [PN_ROUTES.MAIN]: NavigatorScreenParams; }; diff --git a/ts/features/messages/screens/DSMessageAttachment.tsx b/ts/features/messages/screens/DSMessageAttachment.tsx deleted file mode 100644 index c0d64d22e16..00000000000 --- a/ts/features/messages/screens/DSMessageAttachment.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React, { useCallback, useState } from "react"; -import { pipe } from "fp-ts/lib/function"; -import * as B from "fp-ts/lib/boolean"; -import ReactNativeBlobUtil from "react-native-blob-util"; -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import I18n from "../../../i18n"; -import { useIOSelector } from "../../../store/hooks"; -import { downloadedMessageAttachmentSelector } from "../store/reducers/downloads"; -import { UIAttachment, UIAttachmentId, UIMessageId } from "../types"; -import { isIos } from "../../../utils/platform"; -import { share } from "../../../utils/share"; -import { IOToast } from "../../../components/Toast"; -import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; -import { DSPdfViewer } from "../components/MessageAttachment/DSPdfViewer"; -import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; -import { MessagesParamsList } from "../navigation/params"; -import { - trackThirdPartyMessageAttachmentCorruptedFile, - trackThirdPartyMessageAttachmentPreviewSuccess, - trackThirdPartyMessageAttachmentUserAction -} from "../analytics"; -import { ServiceId } from "../../../../definitions/backend/ServiceId"; -import { - trackPNAttachmentOpen, - trackPNAttachmentOpeningSuccess, - trackPNAttachmentSave, - trackPNAttachmentSaveShare, - trackPNAttachmentShare -} from "../../pn/analytics"; -import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent"; - -export type DSMessageAttachmentNavigationParams = Readonly<{ - messageId: UIMessageId; - attachmentId: UIAttachmentId; - isPN: boolean; - serviceId?: ServiceId; -}>; - -const renderFooter = ( - attachment: UIAttachment, - downloadPath: string, - isPN: boolean, - attachmentCategory?: string -) => - isIos ? ( - { - onShare(isPN, attachmentCategory); - ReactNativeBlobUtil.ios.presentOptionsMenu(downloadPath); - }, - label: I18n.t("messagePDFPreview.singleBtn") - } - }} - /> - ) : ( - { - onShare(isPN, attachmentCategory); - share(`file://${downloadPath}`, undefined, false)().catch(_ => { - IOToast.show(I18n.t("messagePDFPreview.errors.sharing")); - }); - }, - label: I18n.t("global.buttons.share") - } - }} - third={{ - type: "Outline", - buttonProps: { - accessibilityLabel: I18n.t("messagePDFPreview.save"), - onPress: () => { - onDownload(isPN, attachmentCategory); - ReactNativeBlobUtil.MediaCollection.copyToMediaStore( - { - name: attachment.displayName, - parentFolder: "", - mimeType: attachment.contentType - }, - "Download", - downloadPath - ) - .then(_ => { - IOToast.show( - I18n.t("messagePDFPreview.savedAtLocation", { - name: attachment.displayName - }) - ); - }) - .catch(_ => { - IOToast.error(I18n.t("messagePDFPreview.errors.saving")); - }); - }, - label: I18n.t("messagePDFPreview.save") - } - }} - secondary={{ - type: "Solid", - buttonProps: { - accessibilityLabel: I18n.t("messagePDFPreview.open"), - onPress: () => { - onOpen(isPN, attachmentCategory); - ReactNativeBlobUtil.android - .actionViewIntent(downloadPath, attachment.contentType) - .catch(_ => { - IOToast.error(I18n.t("messagePDFPreview.errors.opening")); - }); - }, - label: I18n.t("messagePDFPreview.open") - } - }} - /> - ); - -const onPDFError = ( - messageId: UIMessageId, - isPN: boolean, - serviceId?: ServiceId, - attachmentCategory?: string -) => - pipe( - isPN, - B.fold( - () => { - trackThirdPartyMessageAttachmentCorruptedFile(messageId, serviceId); - IOToast.error(I18n.t("messageDetails.attachments.corruptedFile")); - }, - () => trackPNAttachmentOpeningSuccess("error", attachmentCategory) - ) - ); - -const onLoadComplete = (isPN: boolean, attachmentCategory?: string) => - pipe( - isPN, - B.fold( - () => trackThirdPartyMessageAttachmentPreviewSuccess(), - () => trackPNAttachmentOpeningSuccess("displayer", attachmentCategory) - ) - ); - -const onShare = (isPN: boolean, attachmentCategory?: string) => - pipe( - isPN, - B.fold( - () => trackThirdPartyMessageAttachmentUserAction("share"), - () => - pipe( - isIos, - B.fold( - () => trackPNAttachmentShare(attachmentCategory), - () => trackPNAttachmentSaveShare(attachmentCategory) - ) - ) - ) - ); - -const onOpen = (isPN: boolean, attachmentCategory?: string) => - pipe( - isPN, - B.fold( - () => trackThirdPartyMessageAttachmentUserAction("open"), - () => trackPNAttachmentOpen(attachmentCategory) - ) - ); - -const onDownload = (isPN: boolean, attachmentCategory?: string) => - pipe( - isPN, - B.fold( - () => trackThirdPartyMessageAttachmentUserAction("download"), - () => trackPNAttachmentSave(attachmentCategory) - ) - ); - -export const DSMessageAttachment = ( - props: IOStackNavigationRouteProps< - MessagesParamsList, - "MESSAGE_DETAIL_ATTACHMENT" - > -): React.ReactElement => { - const { messageId, attachmentId, isPN, serviceId } = props.route.params; - const [isPDFRenderingError, setIsPDFRenderingError] = useState(false); - - const downloadedAttachment = useIOSelector(state => - downloadedMessageAttachmentSelector(state, messageId, attachmentId) - ); - const attachmentOpt = downloadedAttachment?.attachment; - const attachmentCategory = attachmentOpt?.category; - const downloadPathOpt = downloadedAttachment?.path; - - const onPDFRenderingError = useCallback(() => { - setIsPDFRenderingError(true); - onPDFError(messageId, isPN, serviceId, attachmentCategory); - }, [attachmentCategory, messageId, isPN, serviceId]); - - useHeaderSecondLevel({ - title: I18n.t("messagePDFPreview.title"), - supportRequest: true - }); - - if (!attachmentOpt || !downloadPathOpt) { - return ( - - ); - } - return ( - <> - {isPDFRenderingError ? ( - - ) : ( - onLoadComplete(isPN, attachmentCategory)} - /> - )} - {renderFooter(attachmentOpt, downloadPathOpt, isPN, attachmentCategory)} - - ); -}; diff --git a/ts/features/messages/screens/LegacyMessageAttachment.tsx b/ts/features/messages/screens/LegacyMessageAttachment.tsx new file mode 100644 index 00000000000..d60ee13fd11 --- /dev/null +++ b/ts/features/messages/screens/LegacyMessageAttachment.tsx @@ -0,0 +1,76 @@ +import React, { useEffect, useRef } from "react"; +import { useNavigation } from "@react-navigation/native"; +import * as O from "fp-ts/lib/Option"; +import I18n from "../../../i18n"; +import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; +import { LegacyMessageAttachmentPreview } from "../components/MessageAttachment/LegacyMessageAttachmentPreview"; +import { MessagesParamsList } from "../navigation/params"; +import { showToast } from "../../../utils/showToast"; +import { getServiceByMessageId } from "../store/reducers/paginatedById"; +import { useIOSelector } from "../../../store/hooks"; +import { thirdPartyMessageUIAttachment } from "../store/reducers/thirdPartyById"; +import { + trackThirdPartyMessageAttachmentCorruptedFile, + trackThirdPartyMessageAttachmentPreviewSuccess, + trackThirdPartyMessageAttachmentUserAction +} from "../analytics"; + +export const LegacyMessageDetailAttachment = ( + props: IOStackNavigationRouteProps< + MessagesParamsList, + "MESSAGE_DETAIL_ATTACHMENT" + > +): React.ReactElement => { + const navigation = useNavigation(); + const messageId = props.route.params.messageId; + const attachmentId = props.route.params.attachmentId; + // This ref is needed otherwise the auto back on the useEffect will fire multiple + // times, since its dependencies change during the back navigation + const autoBackOnErrorHandled = useRef(false); + const serviceId = useIOSelector(state => + getServiceByMessageId(state, messageId) + ); + + const maybeThirdPartyMessageUIAttachment = useIOSelector(state => + thirdPartyMessageUIAttachment(state)(messageId)(attachmentId) + ); + + useEffect(() => { + // This condition happens only if this screen is shown without having + // first retrieved the third party message (so it should never happen) + if ( + !autoBackOnErrorHandled.current && + O.isNone(maybeThirdPartyMessageUIAttachment) + ) { + // eslint-disable-next-line functional/immutable-data + autoBackOnErrorHandled.current = true; + showToast(I18n.t("messageDetails.attachments.downloadFailed")); + navigation.goBack(); + } + }, [navigation, maybeThirdPartyMessageUIAttachment]); + + return O.isSome(maybeThirdPartyMessageUIAttachment) ? ( + { + trackThirdPartyMessageAttachmentCorruptedFile(messageId, serviceId); + showToast(I18n.t("messageDetails.attachments.corruptedFile")); + }} + onLoadComplete={() => { + trackThirdPartyMessageAttachmentPreviewSuccess(); + }} + onDownload={() => { + trackThirdPartyMessageAttachmentUserAction("download"); + }} + onOpen={() => { + trackThirdPartyMessageAttachmentUserAction("open"); + }} + onShare={() => { + trackThirdPartyMessageAttachmentUserAction("share"); + }} + /> + ) : ( + <> + ); +}; diff --git a/ts/features/messages/screens/MessageAttachment.tsx b/ts/features/messages/screens/MessageAttachment.tsx index b37d463145e..5945c03aed9 100644 --- a/ts/features/messages/screens/MessageAttachment.tsx +++ b/ts/features/messages/screens/MessageAttachment.tsx @@ -1,76 +1,236 @@ -import React, { useEffect, useRef } from "react"; -import { useNavigation } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; +import React, { useCallback, useState } from "react"; +import { pipe } from "fp-ts/lib/function"; +import * as B from "fp-ts/lib/boolean"; +import ReactNativeBlobUtil from "react-native-blob-util"; +import { FooterWithButtons } from "@pagopa/io-app-design-system"; import I18n from "../../../i18n"; +import { useIOSelector } from "../../../store/hooks"; +import { downloadedMessageAttachmentSelector } from "../store/reducers/downloads"; +import { UIAttachment, UIAttachmentId, UIMessageId } from "../types"; +import { isIos } from "../../../utils/platform"; +import { share } from "../../../utils/share"; +import { IOToast } from "../../../components/Toast"; +import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; +import { PdfViewer } from "../components/MessageAttachment/PdfViewer"; import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; -import { MessageAttachmentPreview } from "../components/MessageAttachmentPreview"; import { MessagesParamsList } from "../navigation/params"; -import { showToast } from "../../../utils/showToast"; -import { getServiceByMessageId } from "../store/reducers/paginatedById"; -import { useIOSelector } from "../../../store/hooks"; -import { thirdPartyMessageUIAttachment } from "../store/reducers/thirdPartyById"; import { trackThirdPartyMessageAttachmentCorruptedFile, trackThirdPartyMessageAttachmentPreviewSuccess, trackThirdPartyMessageAttachmentUserAction } from "../analytics"; +import { ServiceId } from "../../../../definitions/backend/ServiceId"; +import { + trackPNAttachmentOpen, + trackPNAttachmentOpeningSuccess, + trackPNAttachmentSave, + trackPNAttachmentSaveShare, + trackPNAttachmentShare +} from "../../pn/analytics"; +import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent"; + +export type MessageAttachmentNavigationParams = Readonly<{ + messageId: UIMessageId; + attachmentId: UIAttachmentId; + isPN: boolean; + serviceId?: ServiceId; +}>; + +const renderFooter = ( + attachment: UIAttachment, + downloadPath: string, + isPN: boolean, + attachmentCategory?: string +) => + isIos ? ( + { + onShare(isPN, attachmentCategory); + ReactNativeBlobUtil.ios.presentOptionsMenu(downloadPath); + }, + label: I18n.t("messagePDFPreview.singleBtn") + } + }} + /> + ) : ( + { + onShare(isPN, attachmentCategory); + share(`file://${downloadPath}`, undefined, false)().catch(_ => { + IOToast.show(I18n.t("messagePDFPreview.errors.sharing")); + }); + }, + label: I18n.t("global.buttons.share") + } + }} + third={{ + type: "Outline", + buttonProps: { + accessibilityLabel: I18n.t("messagePDFPreview.save"), + onPress: () => { + onDownload(isPN, attachmentCategory); + ReactNativeBlobUtil.MediaCollection.copyToMediaStore( + { + name: attachment.displayName, + parentFolder: "", + mimeType: attachment.contentType + }, + "Download", + downloadPath + ) + .then(_ => { + IOToast.show( + I18n.t("messagePDFPreview.savedAtLocation", { + name: attachment.displayName + }) + ); + }) + .catch(_ => { + IOToast.error(I18n.t("messagePDFPreview.errors.saving")); + }); + }, + label: I18n.t("messagePDFPreview.save") + } + }} + secondary={{ + type: "Solid", + buttonProps: { + accessibilityLabel: I18n.t("messagePDFPreview.open"), + onPress: () => { + onOpen(isPN, attachmentCategory); + ReactNativeBlobUtil.android + .actionViewIntent(downloadPath, attachment.contentType) + .catch(_ => { + IOToast.error(I18n.t("messagePDFPreview.errors.opening")); + }); + }, + label: I18n.t("messagePDFPreview.open") + } + }} + /> + ); + +const onPDFError = ( + messageId: UIMessageId, + isPN: boolean, + serviceId?: ServiceId, + attachmentCategory?: string +) => + pipe( + isPN, + B.fold( + () => { + trackThirdPartyMessageAttachmentCorruptedFile(messageId, serviceId); + IOToast.error(I18n.t("messageDetails.attachments.corruptedFile")); + }, + () => trackPNAttachmentOpeningSuccess("error", attachmentCategory) + ) + ); + +const onLoadComplete = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentPreviewSuccess(), + () => trackPNAttachmentOpeningSuccess("displayer", attachmentCategory) + ) + ); + +const onShare = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("share"), + () => + pipe( + isIos, + B.fold( + () => trackPNAttachmentShare(attachmentCategory), + () => trackPNAttachmentSaveShare(attachmentCategory) + ) + ) + ) + ); + +const onOpen = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("open"), + () => trackPNAttachmentOpen(attachmentCategory) + ) + ); -export const MessageDetailAttachment = ( +const onDownload = (isPN: boolean, attachmentCategory?: string) => + pipe( + isPN, + B.fold( + () => trackThirdPartyMessageAttachmentUserAction("download"), + () => trackPNAttachmentSave(attachmentCategory) + ) + ); + +export const MessageAttachment = ( props: IOStackNavigationRouteProps< MessagesParamsList, "MESSAGE_DETAIL_ATTACHMENT" > ): React.ReactElement => { - const navigation = useNavigation(); - const messageId = props.route.params.messageId; - const attachmentId = props.route.params.attachmentId; - // This ref is needed otherwise the auto back on the useEffect will fire multiple - // times, since its dependencies change during the back navigation - const autoBackOnErrorHandled = useRef(false); - const serviceId = useIOSelector(state => - getServiceByMessageId(state, messageId) - ); + const { messageId, attachmentId, isPN, serviceId } = props.route.params; + const [isPDFRenderingError, setIsPDFRenderingError] = useState(false); - const maybeThirdPartyMessageUIAttachment = useIOSelector(state => - thirdPartyMessageUIAttachment(state)(messageId)(attachmentId) + const downloadedAttachment = useIOSelector(state => + downloadedMessageAttachmentSelector(state, messageId, attachmentId) ); + const attachmentOpt = downloadedAttachment?.attachment; + const attachmentCategory = attachmentOpt?.category; + const downloadPathOpt = downloadedAttachment?.path; - useEffect(() => { - // This condition happens only if this screen is shown without having - // first retrieved the third party message (so it should never happen) - if ( - !autoBackOnErrorHandled.current && - O.isNone(maybeThirdPartyMessageUIAttachment) - ) { - // eslint-disable-next-line functional/immutable-data - autoBackOnErrorHandled.current = true; - showToast(I18n.t("messageDetails.attachments.downloadFailed")); - navigation.goBack(); - } - }, [navigation, maybeThirdPartyMessageUIAttachment]); + const onPDFRenderingError = useCallback(() => { + setIsPDFRenderingError(true); + onPDFError(messageId, isPN, serviceId, attachmentCategory); + }, [attachmentCategory, messageId, isPN, serviceId]); - return O.isSome(maybeThirdPartyMessageUIAttachment) ? ( - { - trackThirdPartyMessageAttachmentCorruptedFile(messageId, serviceId); - showToast(I18n.t("messageDetails.attachments.corruptedFile")); - }} - onLoadComplete={() => { - trackThirdPartyMessageAttachmentPreviewSuccess(); - }} - onDownload={() => { - trackThirdPartyMessageAttachmentUserAction("download"); - }} - onOpen={() => { - trackThirdPartyMessageAttachmentUserAction("open"); - }} - onShare={() => { - trackThirdPartyMessageAttachmentUserAction("share"); - }} - /> - ) : ( - <> + useHeaderSecondLevel({ + title: I18n.t("messagePDFPreview.title"), + supportRequest: true + }); + + if (!attachmentOpt || !downloadPathOpt) { + return ( + + ); + } + return ( + <> + {isPDFRenderingError ? ( + + ) : ( + onLoadComplete(isPN, attachmentCategory)} + /> + )} + {renderFooter(attachmentOpt, downloadPathOpt, isPN, attachmentCategory)} + ); }; diff --git a/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx b/ts/features/messages/screens/__tests__/MessageAttachment.test.tsx similarity index 94% rename from ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx rename to ts/features/messages/screens/__tests__/MessageAttachment.test.tsx index 9328dbbdb5b..5531553a12f 100644 --- a/ts/features/messages/screens/__tests__/DSMessageAttachment.test.tsx +++ b/ts/features/messages/screens/__tests__/MessageAttachment.test.tsx @@ -4,12 +4,12 @@ import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWr import { MESSAGES_ROUTES } from "../../navigation/routes"; import { appReducer } from "../../../../store/reducers"; import { applicationChangeState } from "../../../../store/actions/application"; -import { DSMessageAttachment } from "../DSMessageAttachment"; +import { MessageAttachment } from "../MessageAttachment"; import { ServiceId } from "../../../../../definitions/backend/ServiceId"; import { downloadAttachment } from "../../store/actions"; import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; -describe("DSMessageAttachment", () => { +describe("MessageAttachment", () => { it("Should match the snapshot when there is an error", () => { const messageId = "01HMZWRG7549N76017YR8YBSG2" as UIMessageId; const attachmentId = "1" as UIAttachmentId; @@ -50,7 +50,7 @@ const renderScreen = ( ); return renderScreenWithNavigationStoreContext( - DSMessageAttachment, + MessageAttachment, MESSAGES_ROUTES.MESSAGE_DETAIL_ATTACHMENT, { messageId, attachmentId, isPN: false, serviceId }, store diff --git a/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap b/ts/features/messages/screens/__tests__/__snapshots__/MessageAttachment.test.tsx.snap similarity index 99% rename from ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap rename to ts/features/messages/screens/__tests__/__snapshots__/MessageAttachment.test.tsx.snap index 46778782234..18daafbbdfa 100644 --- a/ts/features/messages/screens/__tests__/__snapshots__/DSMessageAttachment.test.tsx.snap +++ b/ts/features/messages/screens/__tests__/__snapshots__/MessageAttachment.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DSMessageAttachment Should match the snapshot when everything went fine 1`] = ` +exports[`MessageAttachment Should match the snapshot when there is an error 1`] = ` - - - - - - - - - - - - - - - - - - - - + - - Loading document... - - - - - - - - - + + + + + + + + + + + + + + + style={ + Array [ + Object { + "textAlign": "center", + }, + Object { + "fontSize": 24, + "lineHeight": 34, + }, + Object { + "color": "#17324D", + "fontFamily": "Titillium Web", + "fontStyle": "normal", + "fontWeight": "600", + }, + ] + } + weight="SemiBold" + > + There is a temporary problem, please try again. + - + - - - - Save or share - - - - - + } + style={ + Array [ + Object { + "textAlign": "center", + }, + Object { + "fontSize": 14, + "lineHeight": 21, + }, + Object { + "color": "#636B82", + "fontFamily": "Titillium Web", + "fontStyle": "normal", + "fontWeight": "400", + }, + ] + } + weight="Regular" + > + If the problem is not resolved, report it with the '?' icon at the top right, thank you! +
- - + + @@ -898,7 +739,7 @@ exports[`DSMessageAttachment Should match the snapshot when everything went fine `; -exports[`DSMessageAttachment Should match the snapshot when there is an error 1`] = ` +exports[`MessageAttachment Should match the snapshot when everything went fine 1`] = ` - - - + - - - - - - - - - - - + - - - + > + + + + + + + + + + + + - + + Loading document... + + + + + + + + + - There is a temporary problem, please try again. - + } + > - - If the problem is not resolved, report it with the '?' icon at the top right, thank you! - + + + + + Save or share + + + + + - - + + diff --git a/ts/features/pn/screens/AttachmentPreviewScreen.tsx b/ts/features/pn/screens/AttachmentPreviewScreen.tsx index 8f4acc12ef3..e516603718a 100644 --- a/ts/features/pn/screens/AttachmentPreviewScreen.tsx +++ b/ts/features/pn/screens/AttachmentPreviewScreen.tsx @@ -5,7 +5,6 @@ import * as O from "fp-ts/lib/Option"; import { PnParamsList } from "../navigation/params"; import { UIMessageId, UIAttachmentId } from "../../messages/types"; import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; -import { MessageAttachmentPreview } from "../../messages/components/MessageAttachmentPreview"; import { useIOSelector } from "../../../store/hooks"; import { pnMessageAttachmentSelector } from "../store/reducers"; import { @@ -16,6 +15,7 @@ import { trackPNAttachmentShare } from "../analytics"; import { isIos } from "../../../utils/platform"; +import { LegacyMessageAttachmentPreview } from "../../messages/components/MessageAttachment/LegacyMessageAttachmentPreview"; export type AttachmentPreviewScreenNavigationParams = Readonly<{ messageId: UIMessageId; @@ -51,7 +51,7 @@ export const AttachmentPreviewScreen = ({ }, [maybePnMessageAttachment, navigation]); return O.isSome(maybePnMessageAttachment) ? ( -