From 26916b76f9c867a354613d4d6b48543ace4bcb8f Mon Sep 17 00:00:00 2001 From: Alice Di Rico <83651704+Ladirico@users.noreply.github.com> Date: Wed, 22 May 2024 15:41:25 +0200 Subject: [PATCH 1/5] feat: [IOPID-1548] DS add new cie errors (#5750) ## Short description Add CIE errors screens. Change generic error design and add errors 22 and 1001. ## List of changes proposed in this pull request - Fix the incorrect visualization of error 1002 (using a custom WizardScreen -> create component CustomWizardScreen) - Add tests - Add screens for error 1001 and 22 and generic - Add new route to visualize CIE error - Change `CieConsentDataUsageScreen` from class component to function component - Change folder of UnlockAccessScreen from onboarding to authentication > [!Tip] > [![Run e2e tests](https://github.com/pagopa/io-app/actions/workflows/test-e2e.yml/badge.svg?branch=IOPID-1548-ds-cie-errors)](https://github.com/pagopa/io-app/actions/workflows/test-e2e.yml) > [!Note] > 1. To test all error cases, I used Proxyman by forcing the response that should come from the BE. So the flow is not correct, in fact error 1002 is not displayed with the L3 access type > 2. The A11Y test were not realised because the same components were tested in many other PR > 3. I modified the component for error 1002, which is in common with SPID, so I additionally tested the spid flow for error 1002. ## iOS screens

| Error 1001 | Error 1002 | Error 22 | Error Generic | SPID Error 1002 | | - | - | - | - | - | |

## Android screens

| Error 1001 | Error 1002 | Error 22 | Error Generic | SPID Error 1002 | | - | - | - | - | - | |

## How to test IOS: before building the app run `yarn cie-ios:prod` and then perform authentication flow with CIE Android: run the application using .env.production --------- Co-authored-by: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> Co-authored-by: mariateresaventura <90319761+mariateresaventura@users.noreply.github.com> --- locales/de/index.yml | 1 - locales/en/index.yml | 12 +- locales/it/index.yml | 12 +- ts/components/screens/CustomWizardScreen.tsx | 126 +++++++ ts/navigation/AuthenticationNavigator.tsx | 12 + .../params/AuthenticationParamsList.ts | 4 + ts/navigation/routes.ts | 2 + ts/screens/authentication/IdpLoginScreen.tsx | 11 +- .../UnlockAccessComponent.tsx} | 112 +++--- .../authentication/UnlockAccessScreen.tsx | 16 + .../__tests__/UnlockAccessComponent.test.tsx | 109 ++++++ .../__tests__/UnlockAccessScreen.test.tsx | 54 +++ .../authentication/cie/CieAuthErrorScreen.tsx | 39 +++ .../cie/CieConsentDataUsageScreen.tsx | 319 ++++++++---------- .../cie/__test__/AuthErrorScreen.test.tsx | 70 ++++ .../cie/components/AuthErrorScreen.tsx | 65 ++++ .../__tests__/UnlockAccessScreen.test.tsx | 63 ---- 17 files changed, 706 insertions(+), 321 deletions(-) create mode 100644 ts/components/screens/CustomWizardScreen.tsx rename ts/screens/{onboarding/UnlockAccessScreen.tsx => authentication/UnlockAccessComponent.tsx} (53%) create mode 100644 ts/screens/authentication/UnlockAccessScreen.tsx create mode 100644 ts/screens/authentication/__tests__/UnlockAccessComponent.test.tsx create mode 100644 ts/screens/authentication/__tests__/UnlockAccessScreen.test.tsx create mode 100644 ts/screens/authentication/cie/CieAuthErrorScreen.tsx create mode 100644 ts/screens/authentication/cie/__test__/AuthErrorScreen.test.tsx create mode 100644 ts/screens/authentication/cie/components/AuthErrorScreen.tsx delete mode 100644 ts/screens/onboarding/__tests__/UnlockAccessScreen.test.tsx diff --git a/locales/de/index.yml b/locales/de/index.yml index a4f53abb282..ebe57cfb63a 100644 --- a/locales/de/index.yml +++ b/locales/de/index.yml @@ -700,7 +700,6 @@ authentication: subtitlel2: "Um die App nutzen zu können, müsst du zunächst den Zugang zu IO entsperren." subtitlel3: "Um mit all deinen SPID-Anmeldedaten oder deiner CIE auf die App zugreifen zu können, musst du den Zugang entsperren." learnmore: "Mehr erfahren" - loginIO: "Schließen" unlockmodal: title: "Was bedeutet das?" description1_1: "Wenn du den Zugang zu IO aus Sicherheitsgründen gesperrt hast, musst du ihn zunächst entsperren, um die App wieder öffnen zu können." diff --git a/locales/en/index.yml b/locales/en/index.yml index b5b80c99fc3..18c70b86d43 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -607,6 +607,16 @@ authentication: expiredCardHeaderTitle: Login with CIE expiredCardContent: The card used is no longer valid or has expired. Use SPID to enter the app. expiredCardHelp: How to renew your Electronic Identity Card? + cie_errors: + error_22: + title: You haven't agreed to share your data + subtitle: To enter, you must agree to share some data + error_1001: + title: Sorry, you don't meet the minimum age requirement + subtitle: You must be at least 18 years old + generic: + title: Sorry, access was not available. + subtitle: A problem occurred while logging in. Please try again in a few minutes. cie: genericTitle: Login with CIE cie: CIE @@ -751,7 +761,7 @@ authentication: subtitlel2: In order to use the app, you must first unlock access to IO. subtitlel3: To be able to access the app with all your SPID or CIE identities, unlock access. learnmore: Learn more - loginIO: Close + loginIO: Not now unlockmodal: title: What does it mean? description1_1: If you have blocked access to IO for security reasons, you must first unlock it in order to re-enter the app. diff --git a/locales/it/index.yml b/locales/it/index.yml index 5d4aa87c9d4..af7f473f317 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -607,6 +607,16 @@ authentication: expiredCardHeaderTitle: Entra con CIE expiredCardContent: La carta utilizzata potrebbe essere scaduta o non più valida. Prova ad usare SPID per entrare in app. expiredCardHelp: Come rinnovare la propria Carta di Identità Elettronica? + cie_errors: + error_22: + title: Non hai dato il consenso all’invio dei dati + subtitle: Per accedere, è necessario acconsentire all’invio di alcuni dati. + error_1001: + title: Non hai l'età minima richiesta + subtitle: Per accedere, devi avere almeno 18 anni + generic: + title: Non è stato possibile accedere + subtitle: Si è verificato un problema durante l’accesso. Riprova tra qualche minuto. cie: genericTitle: Entra con CIE cie: CIE @@ -751,7 +761,7 @@ authentication: subtitlel2: Per poter usare l'app, devi prima sbloccare l'accesso a IO. subtitlel3: Per poter accedere all’app con tutte le tue identità SPID o CIE, sblocca l’accesso. learnmore: Scopri di più - loginIO: Chiudi + loginIO: Non ora, entra su IO unlockmodal: title: Cosa significa? description1_1: Se hai bloccato l’accesso a IO per motivi di sicurezza, per poter rientrare in app devi prima sbloccarlo. diff --git a/ts/components/screens/CustomWizardScreen.tsx b/ts/components/screens/CustomWizardScreen.tsx new file mode 100644 index 00000000000..95cce99b303 --- /dev/null +++ b/ts/components/screens/CustomWizardScreen.tsx @@ -0,0 +1,126 @@ +import { + Body, + ButtonLink, + ButtonLinkProps, + ButtonSolid, + ButtonSolidProps, + ContentWrapper, + H3, + IOPictograms, + IOStyles, + Pictogram, + VSpacer +} from "@pagopa/io-app-design-system"; +import { SafeAreaView } from "react-native-safe-area-context"; +import * as React from "react"; +import { StyleSheet, View } from "react-native"; +import { + BodyProps, + ComposedBodyFromArray +} from "../core/typography/ComposedBodyFromArray"; +export type CustomWizardScreenProps = { + title: string; + description?: string | Array; + pictogram: IOPictograms; + primaryButton: Pick< + ButtonSolidProps, + "label" | "accessibilityLabel" | "onPress" | "testID" + >; + actionButton?: Pick< + ButtonLinkProps, + "label" | "accessibilityLabel" | "onPress" | "testID" + >; + buttonLink?: Pick< + ButtonLinkProps, + "label" | "accessibilityLabel" | "onPress" | "testID" + >; +}; + +/** + * A common screen used in a wizard flow to show a pictogram, a title, a description and one or two buttons. + */ +const CustomWizardScreen = ({ + title, + description, + pictogram, + primaryButton, + actionButton, + buttonLink +}: CustomWizardScreenProps) => ( + + + + + {actionButton && ( + <> + + + + + + + )} + + +); + +type CustomWizardBodyProps = { + title: string; + description?: string | Array; + pictogram: IOPictograms; + buttonLink?: Pick< + ButtonLinkProps, + "label" | "accessibilityLabel" | "onPress" | "testID" + >; +}; + +const WizardBody = ({ + title, + description, + pictogram, + buttonLink +}: CustomWizardBodyProps) => ( + + + + + + +

{title}

+ {description && ( + <> + + {typeof description === "string" ? ( + {description} + ) : ( + + )} + + )} + {buttonLink && ( + + + + + )} +
+
+); + +const styles = StyleSheet.create({ + textCenter: { + textAlign: "center" + }, + wizardContent: { + ...IOStyles.flex, + ...IOStyles.horizontalContentPadding, + ...IOStyles.centerJustified + } +}); + +export { CustomWizardScreen }; diff --git a/ts/navigation/AuthenticationNavigator.tsx b/ts/navigation/AuthenticationNavigator.tsx index 48644b11715..48876222651 100644 --- a/ts/navigation/AuthenticationNavigator.tsx +++ b/ts/navigation/AuthenticationNavigator.tsx @@ -21,6 +21,8 @@ import { AuthSessionPage } from "../screens/authentication/idpAuthSessionHandler import CieNotSupported from "../components/cie/CieNotSupported"; import RootedDeviceModal from "../screens/modal/RootedDeviceModal"; import { isGestureEnabled } from "../utils/navigation"; +import CieAuthErrorScreen from "../screens/authentication/cie/CieAuthErrorScreen"; +import UnlockAccessScreen from "../screens/authentication/UnlockAccessScreen"; import { AuthenticationParamsList } from "./params/AuthenticationParamsList"; import ROUTES from "./routes"; import CloseButton from "./components/CloseButton"; @@ -121,6 +123,16 @@ const AuthenticationStackNavigator = () => ( component={CieWrongCiePinScreen} /> + + + + { ); } else if (pot.isError(requestState)) { if (errorCode === "1002") { - return ; + // TODO: refactor this logic and + // change this UnlockAccessComponent with navigation + // props.navigation.navigate(ROUTES.AUTHENTICATION, { + // screen: ROUTES.UNLOCK_ACCESS_SCREEN, + // params: { authLevel: "L2" } + // }); + // jira ticket: https://pagopa.atlassian.net/browse/IOPID-1547 + return ; } else { return ( { - const { identifier } = props; +const UnlockAccessComponent = (props: UnlockAccessProps) => { + const { authLevel } = props; const navigation = useIONavigation(); const ModalContent = () => ( @@ -84,67 +81,50 @@ const UnlockAccessScreen = (props: Props) => { 100 ); + const onPressActionButton = () => { + if (authLevel === "L2") { + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.AUTHENTICATION_LANDING + }); + } + // for the future developement: add here + // the navigation to continue the flow + // future development jira task: + // https://pagopa.atlassian.net/browse/IOPID-1228 + }; + return ( - - + openWebUrl("https://ioapp.it/") }} - secondaryActionProps={{ + actionButton={{ testID: "button-link-test", - label: I18n.t("authentication.unlock.loginIO"), - accessibilityLabel: I18n.t("authentication.unlock.loginIO"), - onPress: () => - navigation.navigate(ROUTES.AUTHENTICATION, { - screen: ROUTES.AUTHENTICATION_LANDING - }) + label: + authLevel === "L2" + ? I18n.t("global.buttons.close") + : I18n.t("authentication.unlock.loginIO"), + onPress: onPressActionButton }} - > - - - - - - - -

- {I18n.t("authentication.unlock.title")} -

-
- - - - {identifier === "SPID" - ? I18n.t("authentication.unlock.subtitlel2") - : I18n.t("authentication.unlock.subtitlel3")} - - - - - - -
- {veryLongAutoResizableBottomSheetWithFooter} -
-
-
+ /> + {veryLongAutoResizableBottomSheetWithFooter} + ); }; -export default UnlockAccessScreen; +export default UnlockAccessComponent; diff --git a/ts/screens/authentication/UnlockAccessScreen.tsx b/ts/screens/authentication/UnlockAccessScreen.tsx new file mode 100644 index 00000000000..3c5dd983f85 --- /dev/null +++ b/ts/screens/authentication/UnlockAccessScreen.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { Route, useRoute } from "@react-navigation/native"; +import ROUTES from "../../navigation/routes"; +import UnlockAccessComponent, { + UnlockAccessProps +} from "./UnlockAccessComponent"; + +const UnlockAccessScreen = () => { + const route = + useRoute>(); + const { authLevel } = route.params; + + return ; +}; + +export default UnlockAccessScreen; diff --git a/ts/screens/authentication/__tests__/UnlockAccessComponent.test.tsx b/ts/screens/authentication/__tests__/UnlockAccessComponent.test.tsx new file mode 100644 index 00000000000..5f3ff318cb3 --- /dev/null +++ b/ts/screens/authentication/__tests__/UnlockAccessComponent.test.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import { render, fireEvent } from "@testing-library/react-native"; +import { View } from "react-native"; +import { createStore } from "redux"; +import UnlockAccessComponent, { + UnlockAccessProps +} from "../../authentication/UnlockAccessComponent"; +import { useIOBottomSheetAutoresizableModal } from "../../../utils/hooks/bottomSheet"; +import { openWebUrl } from "../../../utils/url"; +import { useIONavigation } from "../../../navigation/params/AppParamsList"; +import I18n from "../../../i18n"; +import ROUTES from "../../../navigation/routes"; +import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; +import { appReducer } from "../../../store/reducers"; +import { applicationChangeState } from "../../../store/actions/application"; + +jest.mock("../../../utils/hooks/bottomSheet"); +jest.mock("../../../utils/url"); +jest.mock("../../../navigation/params/AppParamsList"); + +const mockedUseIOBottomSheetAutoresizableModal = + useIOBottomSheetAutoresizableModal as jest.Mock; +const mockedOpenWebUrl = openWebUrl as jest.Mock; +const mockedUseIONavigation = useIONavigation as jest.Mock; + +describe("UnlockAccessComponent", () => { + const mockNavigation = { + navigate: jest.fn() + }; + + beforeEach(() => { + mockedUseIONavigation.mockReturnValue(mockNavigation); + mockedUseIOBottomSheetAutoresizableModal.mockReturnValue({ + present: jest.fn(), + bottomSheet: null + }); + jest.clearAllMocks(); + }); + + it("renders correctly with authLevel L2", () => { + const { getByText, getAllByText } = renderComponent("L2"); + + expect(getAllByText(I18n.t("authentication.unlock.title"))).toHaveLength(2); + expect(getByText(I18n.t("authentication.unlock.subtitlel2"))).toBeTruthy(); + }); + + it("renders correctly with authLevel L3", () => { + const { getByText, getAllByText } = renderComponent("L3"); + + expect(getAllByText(I18n.t("authentication.unlock.title"))).toHaveLength(2); + expect(getByText(I18n.t("authentication.unlock.subtitlel3"))).toBeTruthy(); + }); + + it("calls openWebUrl when primary button is pressed", () => { + const { getByTestId } = renderComponent("L2"); + const button = getByTestId("button-solid-test"); + + fireEvent.press(button); + + expect(mockedOpenWebUrl).toHaveBeenCalledWith("https://ioapp.it/"); + }); + + it("calls navigation.navigate when action button is pressed with authLevel L2", () => { + const { getByTestId } = renderComponent("L2"); + const button = getByTestId("button-link-test"); + + fireEvent.press(button); + + expect(mockNavigation.navigate).toHaveBeenCalledWith( + ROUTES.AUTHENTICATION, + { + screen: ROUTES.AUTHENTICATION_LANDING + } + ); + }); + + it("calls present modal when learn more link is pressed", () => { + const presentMock = jest.fn(); + const bottomSheetMock = ; + mockedUseIOBottomSheetAutoresizableModal.mockReturnValueOnce({ + present: presentMock, + bottomSheet: bottomSheetMock + }); + + const { getByTestId } = renderComponent("L2"); + const link = getByTestId("learn-more-link-test"); + + fireEvent.press(link); + + expect(presentMock).toHaveBeenCalled(); + }); +}); + +const renderComponent = (authLevel?: "L2" | "L3") => { + if (authLevel) { + const props: UnlockAccessProps = { authLevel }; + return render(); + } else { + const globalState = appReducer(undefined, applicationChangeState("active")); + const store = createStore(appReducer, globalState as any); + + return renderScreenWithNavigationStoreContext( + UnlockAccessComponent, + "DUMMY", + {}, + store + ); + } +}; diff --git a/ts/screens/authentication/__tests__/UnlockAccessScreen.test.tsx b/ts/screens/authentication/__tests__/UnlockAccessScreen.test.tsx new file mode 100644 index 00000000000..b4adeeb8c47 --- /dev/null +++ b/ts/screens/authentication/__tests__/UnlockAccessScreen.test.tsx @@ -0,0 +1,54 @@ +import configureMockStore from "redux-mock-store"; +import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; +import { appReducer } from "../../../store/reducers"; +import { applicationChangeState } from "../../../store/actions/application"; +import ROUTES from "../../../navigation/routes"; +import { GlobalState } from "../../../store/reducers/types"; +import { UnlockAccessProps } from "../../authentication/UnlockAccessComponent"; +import UnlockAccessScreen from "../../authentication/UnlockAccessScreen"; +import I18n from "../../../i18n"; + +describe("UnlockAccessScreen", () => { + it("render UnlockAccessComponent with authLevel L2", () => { + const component = renderComponent({ authLevel: "L2" }); + + expect( + component.screen.getAllByText(I18n.t("authentication.unlock.title")) + ).toHaveLength(2); + + expect( + component.screen.getByText(I18n.t("authentication.unlock.subtitlel2")) + ).toBeTruthy(); + }); + + it("render UnlockAccessComponent with authLevel L3", () => { + const component = renderComponent({ authLevel: "L3" }); + + expect( + component.screen.getAllByText(I18n.t("authentication.unlock.title")) + ).toHaveLength(2); + expect( + component.screen.getByText(I18n.t("authentication.unlock.subtitlel3")) + ).toBeTruthy(); + }); +}); + +const renderComponent = (props: UnlockAccessProps) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + + const mockStore = configureMockStore(); + const store: ReturnType = mockStore({ + ...globalState + } as GlobalState); + + return { + screen: renderScreenWithNavigationStoreContext( + UnlockAccessScreen, + ROUTES.UNLOCK_ACCESS_SCREEN, + { + ...props + }, + store + ) + }; +}; diff --git a/ts/screens/authentication/cie/CieAuthErrorScreen.tsx b/ts/screens/authentication/cie/CieAuthErrorScreen.tsx new file mode 100644 index 00000000000..a8fe15a30c8 --- /dev/null +++ b/ts/screens/authentication/cie/CieAuthErrorScreen.tsx @@ -0,0 +1,39 @@ +import { Route, useRoute } from "@react-navigation/native"; +import React, { useCallback } from "react"; +import { useIONavigation } from "../../../navigation/params/AppParamsList"; +import ROUTES from "../../../navigation/routes"; +import AuthErrorScreen from "./components/AuthErrorScreen"; + +export type AuthErrorScreenProps = { + errorCode?: string; +}; + +const CieAuthErrorScreen = () => { + const route = + useRoute>(); + const { errorCode } = route.params; + + const navigation = useIONavigation(); + + const onRetry = useCallback(() => { + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.CIE_PIN_SCREEN + }); + }, [navigation]); + + const onCancel = useCallback(() => { + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.AUTHENTICATION_LANDING + }); + }, [navigation]); + + return ( + + ); +}; + +export default CieAuthErrorScreen; diff --git a/ts/screens/authentication/cie/CieConsentDataUsageScreen.tsx b/ts/screens/authentication/cie/CieConsentDataUsageScreen.tsx index 9dc9877ef5e..65b9d7854e2 100644 --- a/ts/screens/authentication/cie/CieConsentDataUsageScreen.tsx +++ b/ts/screens/authentication/cie/CieConsentDataUsageScreen.tsx @@ -2,230 +2,175 @@ * A screen to display, by a webview, the consent to send user sensitive data * to backend and proceed with the onboarding process */ -import * as React from "react"; -import { Alert, BackHandler, NativeEventSubscription } from "react-native"; -import WebView from "react-native-webview"; +import React, { useCallback, useEffect, useState } from "react"; import { WebViewHttpErrorEvent, WebViewNavigation } from "react-native-webview/lib/WebViewTypes"; -import { connect } from "react-redux"; import { VSpacer } from "@pagopa/io-app-design-system"; import { Route, useRoute } from "@react-navigation/native"; -import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay"; -import GenericErrorComponent from "../../../components/screens/GenericErrorComponent"; -import TopScreenComponent from "../../../components/screens/TopScreenComponent"; -import I18n from "../../../i18n"; +import WebView from "react-native-webview"; +import { useIODispatch } from "../../../store/hooks"; import { loginFailure, loginSuccess } from "../../../store/actions/authentication"; -import { resetToAuthenticationRoute } from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; import { SessionToken } from "../../../types/SessionToken"; import { onLoginUriChanged } from "../../../utils/login"; +import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay"; +import { trackLoginCieDataSharingError } from "../analytics/cieAnalytics"; import { originSchemasWhiteList } from "../originSchemasWhiteList"; -import { GlobalState } from "../../../store/reducers/types"; -import { isCieLoginUatEnabledSelector } from "../../../features/cieLogin/store/selectors"; -import { withTrailingPoliceCarLightEmojii } from "../../../utils/strings"; -import UnlockAccessScreen from "../../onboarding/UnlockAccessScreen"; -import { - trackLoginCieConsentDataUsageScreen, - trackLoginCieDataSharingError -} from "../analytics/cieAnalytics"; +import ROUTES from "../../../navigation/routes"; +import { useIONavigation } from "../../../navigation/params/AppParamsList"; +import { useOnboardingAbortAlert } from "../../../utils/hooks/useOnboardingAbortAlert"; +import { useHardwareBackButton } from "../../../hooks/useHardwareBackButton"; export type CieConsentDataUsageScreenNavigationParams = { cieConsentUri: string; + errorCodeDebugMode?: string; }; -type State = { - hasError: boolean; - errorCode?: string; - isLoginSuccess?: boolean; -}; - -type Props = ReturnType & - ReturnType; - -const loaderComponent = ( +const LoaderComponent = () => ( ); -type CieConsentDataUsageScreenProps = Props & - CieConsentDataUsageScreenNavigationParams; -class CieConsentDataUsageScreen extends React.Component< - CieConsentDataUsageScreenProps, - State -> { - private subscription: NativeEventSubscription | undefined; - constructor(props: CieConsentDataUsageScreenProps) { - super(props); - trackLoginCieConsentDataUsageScreen(); - this.state = { - hasError: false, - isLoginSuccess: undefined - }; - } +const CieConsentDataUsageScreen = () => { + const route = + useRoute< + Route< + typeof ROUTES.CIE_CONSENT_DATA_USAGE, + CieConsentDataUsageScreenNavigationParams + > + >(); + const { cieConsentUri } = route.params; + const dispatch = useIODispatch(); + const [hasError, setHasError] = useState(false); + const [isLoginSuccess, setIsLoginSuccess] = useState(); + const [errorCode, setErrorCode] = useState(); + const { showAlert } = useOnboardingAbortAlert(); + const navigation = useIONavigation(); + const loginSuccessDispatch = useCallback( + (token: SessionToken) => dispatch(loginSuccess({ token, idp: "cie" })), + [dispatch] + ); + + const loginFailureDispatch = useCallback( + (error: Error) => dispatch(loginFailure({ error, idp: "cie" })), + [dispatch] + ); + + const navigateToLandingScreen = useCallback(() => { + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.AUTHENTICATION_LANDING + }); + }, [navigation]); - private showAbortAlert = (): boolean => { + const showAbortAlert = useCallback((): boolean => { // if the screen is in error state, skip the confirmation alert to go back at the landing screen - if (this.state.hasError) { - this.props.resetNavigation(); + if (hasError) { + navigateToLandingScreen(); return true; } - Alert.alert( - I18n.t("onboarding.alert.title"), - I18n.t("onboarding.alert.description"), - [ - { - text: I18n.t("global.buttons.cancel"), - style: "cancel" - }, - { - text: I18n.t("global.buttons.exit"), - style: "default", - onPress: this.props.resetNavigation - } - ] - ); + showAlert(); return true; - }; - - public componentDidMount() { - // eslint-disable-next-line functional/immutable-data - this.subscription = BackHandler.addEventListener( - "hardwareBackPress", - this.showAbortAlert - ); - } - - public componentWillUnmount() { - this.subscription?.remove(); - } + }, [hasError, navigateToLandingScreen, showAlert]); - get cieAuthorizationUri(): string { - return this.props.cieConsentUri; - } + useHardwareBackButton(() => { + showAbortAlert(); + return true; + }); - private handleWebViewError = () => { - this.setState({ hasError: true }); - }; + const handleWebViewError = useCallback(() => setHasError(true), []); - private handleHttpError = (event: WebViewHttpErrorEvent) => { - this.props.loginFailure( - new Error( - `HTTP error ${event.nativeEvent.description} with Authorization uri` - ) - ); - }; - - private handleLoginSuccess = (token: SessionToken) => { - this.setState({ isLoginSuccess: true, hasError: false }, () => { - this.props.loginSuccess(token); - }); - }; - - private handleShouldStartLoading = (event: WebViewNavigation): boolean => { - const isLoginUrlWithToken = onLoginUriChanged( - this.handleLoginFailure, - this.handleLoginSuccess - )(event); - // URL can be loaded if it's not the login URL containing the session token - this avoids - // making a (useless) GET request with the session in the URL - return !isLoginUrlWithToken; - }; - - private handleLoginFailure = (errorCode?: string) => { - this.props.loginFailure( - new Error(`login CIE failure with code ${errorCode || "n/a"}`) - ); - this.setState({ hasError: true, errorCode }); - }; - - private getContent = () => { - if (this.state.isLoginSuccess) { - return loaderComponent; + const handleHttpError = useCallback( + (event: WebViewHttpErrorEvent) => { + loginFailureDispatch( + new Error( + `HTTP error ${event.nativeEvent.description} with Authorization uri` + ) + ); + }, + [loginFailureDispatch] + ); + + const handleLoginSuccess = useCallback( + (token: SessionToken) => { + setIsLoginSuccess(true); + setHasError(false); + loginSuccessDispatch(token); + }, + [loginSuccessDispatch] + ); + + const handleLoginFailure = useCallback( + (errorCode?: string) => { + setHasError(true); + setErrorCode(errorCode); + loginFailureDispatch( + new Error(`login CIE failure with code ${errorCode || "n/a"}`) + ); + }, + [loginFailureDispatch] + ); + + const handleShouldStartLoading = useCallback( + (event: WebViewNavigation): boolean => { + const isLoginUrlWithToken = onLoginUriChanged( + handleLoginFailure, + handleLoginSuccess + )(event); + // URL can be loaded if it's not the login URL containing the session token - this avoids + // making a (useless) GET request with the session in the URL + return !isLoginUrlWithToken; + }, + [handleLoginFailure, handleLoginSuccess] + ); + + useEffect(() => { + if (hasError && errorCode === "22") { + trackLoginCieDataSharingError(); } - if (this.state.hasError) { - if (this.state.errorCode === "22") { - trackLoginCieDataSharingError(); + }, [errorCode, hasError]); + + useEffect(() => { + if (hasError) { + if (errorCode === "1002") { + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.UNLOCK_ACCESS_SCREEN, + params: { authLevel: "L2" } + }); + return; } - if (this.state.errorCode === "1002") { - return ; - } else { - const errorTranslationKey = this.state.errorCode - ? `authentication.errors.spid.error_${this.state.errorCode}` - : "authentication.errors.network.title"; - return ( - - ); - } - } else { - return ( - loaderComponent} - onError={this.handleWebViewError} - onHttpError={this.handleHttpError} - /> - ); + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.AUTH_ERROR_SCREEN, + params: { errorCode } + }); } - }; + }, [errorCode, hasError, navigation]); - public render(): React.ReactNode { - const goBack = this.state.hasError ? false : this.showAbortAlert; + if (isLoginSuccess) { + return ; + } + if (!hasError) { return ( - - {this.getContent()} - + } + onError={handleWebViewError} + onHttpError={handleHttpError} + /> ); } -} - -const mapStateToProps = (state: GlobalState) => ({ - isCieUatEnabled: isCieLoginUatEnabledSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - resetNavigation: () => resetToAuthenticationRoute(), - loginSuccess: (token: SessionToken) => - dispatch(loginSuccess({ token, idp: "cie" })), - loginFailure: (error: Error) => dispatch(loginFailure({ error, idp: "cie" })) -}); - -const CieConsentDataUsageScreenFC = (props: Props) => { - const { cieConsentUri } = - useRoute< - Route<"CIE_CONSENT_DATA_USAGE", CieConsentDataUsageScreenNavigationParams> - >().params; - return ; + return null; }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(CieConsentDataUsageScreenFC); +export default CieConsentDataUsageScreen; diff --git a/ts/screens/authentication/cie/__test__/AuthErrorScreen.test.tsx b/ts/screens/authentication/cie/__test__/AuthErrorScreen.test.tsx new file mode 100644 index 00000000000..4943d8a75b1 --- /dev/null +++ b/ts/screens/authentication/cie/__test__/AuthErrorScreen.test.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { fireEvent, render } from "@testing-library/react-native"; +import AuthErrorScreen from "../components/AuthErrorScreen"; +import I18n from "../../../../i18n"; + +describe("AuthErrorScreen", () => { + const testCases = [ + { + description: "renders correctly with generic error code", + errorCode: "generic", + expectedTitle: I18n.t("authentication.cie_errors.generic.title"), + expectedSubtitle: I18n.t("authentication.cie_errors.generic.subtitle"), + expectRetryCalled: true, + expectCancelCalled: true + }, + { + description: 'renders correctly with error code "22"', + errorCode: "22", + expectedTitle: I18n.t("authentication.cie_errors.error_22.title"), + expectedSubtitle: I18n.t("authentication.cie_errors.error_22.subtitle"), + expectRetryCalled: true, + expectCancelCalled: true + }, + { + description: 'renders correctly with error code "1001"', + errorCode: "1001", + expectedTitle: I18n.t("authentication.cie_errors.error_1001.title"), + expectedSubtitle: I18n.t("authentication.cie_errors.error_1001.subtitle"), + expectRetryCalled: false, + expectCancelCalled: true + } + ]; + + testCases.forEach( + ({ + description, + errorCode, + expectedTitle, + expectedSubtitle, + expectRetryCalled, + expectCancelCalled + }) => { + test(description, () => { + const onRetryMock = jest.fn(); + const onCancelMock = jest.fn(); + + const { getByText } = render( + + ); + + expect(getByText(expectedTitle)).toBeDefined(); + expect(getByText(expectedSubtitle)).toBeDefined(); + + if (expectRetryCalled) { + fireEvent.press(getByText(I18n.t("global.buttons.retry"))); + expect(onRetryMock).toHaveBeenCalled(); + } + + if (expectCancelCalled) { + fireEvent.press(getByText(I18n.t("global.buttons.close"))); + expect(onCancelMock).toHaveBeenCalled(); + } + }); + } + ); +}); diff --git a/ts/screens/authentication/cie/components/AuthErrorScreen.tsx b/ts/screens/authentication/cie/components/AuthErrorScreen.tsx new file mode 100644 index 00000000000..55e0ee4e444 --- /dev/null +++ b/ts/screens/authentication/cie/components/AuthErrorScreen.tsx @@ -0,0 +1,65 @@ +/* eslint-disable no-console */ +import React from "react"; +import { + OperationResultScreenContent, + OperationResultScreenContentProps +} from "../../../../components/screens/OperationResultScreenContent"; +import I18n from "../../../../i18n"; + +type Props = { + errorCode?: string; + onRetry: () => void; + onCancel: () => void; +}; + +const AuthErrorScreen = ({ + errorCode = "generic", + onRetry, + onCancel +}: Props) => { + const errorsObject: { + [key: string]: OperationResultScreenContentProps; + } = { + "22": { + pictogram: "accessDenied", + title: I18n.t("authentication.cie_errors.error_22.title"), + subtitle: I18n.t("authentication.cie_errors.error_22.subtitle"), + action: { + onPress: onRetry, + label: I18n.t("global.buttons.retry") + }, + secondaryAction: { + onPress: onCancel, + label: I18n.t("global.buttons.close") + } + }, + "1001": { + pictogram: "identityCheck", + title: I18n.t("authentication.cie_errors.error_1001.title"), + subtitle: I18n.t("authentication.cie_errors.error_1001.subtitle"), + action: { + onPress: onCancel, + label: I18n.t("global.buttons.close") + } + }, + generic: { + pictogram: "umbrellaNew", + title: I18n.t("authentication.cie_errors.generic.title"), + subtitle: I18n.t("authentication.cie_errors.generic.subtitle"), + action: { + onPress: onRetry, + label: I18n.t("global.buttons.retry") + }, + secondaryAction: { + onPress: onCancel, + label: I18n.t("global.buttons.close") + } + } + }; + + const errorDetails = errorsObject[errorCode] || errorsObject.generic; + + return ; +}; + +export default AuthErrorScreen; diff --git a/ts/screens/onboarding/__tests__/UnlockAccessScreen.test.tsx b/ts/screens/onboarding/__tests__/UnlockAccessScreen.test.tsx deleted file mode 100644 index 2c26211f0dd..00000000000 --- a/ts/screens/onboarding/__tests__/UnlockAccessScreen.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { fireEvent } from "@testing-library/react-native"; -import { createStore } from "redux"; -import { applicationChangeState } from "../../../store/actions/application"; -import { appReducer } from "../../../store/reducers"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import UnlockAccessScreen from "../UnlockAccessScreen"; - -const mockOpenWebUrl = jest.fn(); - -jest.mock("../../../utils/url", () => ({ - openWebUrl: (_: string) => { - mockOpenWebUrl(); - } -})); - -describe("UnlockAccessScreen", async () => { - it("the components into the page should be render correctly", () => { - const component = renderComponent(); - expect(component).toBeDefined(); - expect(component.getByTestId("container-test")).not.toBeNull(); - expect(component.getByTestId("title-test")).toBeDefined(); - expect(component.getByTestId("subtitle-test")).toBeDefined(); - const learnMoreButton = component.getByTestId("learn-more-link-test"); - expect(learnMoreButton).toBeDefined(); - - const unlockProfileButton = component.getByTestId("button-solid-test"); - expect(unlockProfileButton).toBeDefined(); - const closeButton = component.getByTestId("button-link-test"); - expect(closeButton).toBeDefined(); - }); - it("click on button to unlock profile", () => { - const component = renderComponent(); - expect(component).toBeDefined(); - const unlockProfileButton = component.getByTestId("button-solid-test"); - - if (unlockProfileButton) { - fireEvent.press(unlockProfileButton); - expect(mockOpenWebUrl).toHaveBeenCalled(); - } - }); - it("click on button to go back to landing page", () => { - const component = renderComponent(); - expect(component).toBeDefined(); - const closeButton = component.getByTestId("button-link-test"); - - if (closeButton) { - fireEvent.press(closeButton); - expect(mockOpenWebUrl).toHaveBeenCalled(); - } - }); -}); - -const renderComponent = () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - - return renderScreenWithNavigationStoreContext( - UnlockAccessScreen, - "DUMMY", - {}, - store - ); -}; From 439e7b0885f8fe01b9d26d36701fb8ae36e5dd28 Mon Sep 17 00:00:00 2001 From: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> Date: Wed, 22 May 2024 17:36:55 +0200 Subject: [PATCH 2/5] chore(release): 2.61.0-rc.0 --- CHANGELOG.md | 28 +++++++++++++++++++++++++ android/app/build.gradle | 4 ++-- ios/ItaliaApp.xcodeproj/project.pbxproj | 4 ++-- ios/ItaliaApp/Info.plist | 4 ++-- ios/ItaliaAppTests/Info.plist | 4 ++-- package.json | 2 +- publiccode.yml | 4 ++-- 7 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed6cfdaa26..712be5972e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.61.0-rc.0](https://github.com/pagopa/io-app/compare/2.60.0-rc.2...2.61.0-rc.0) (2024-05-22) + + +### Features + +* [[IOCOM-1391](https://pagopa.atlassian.net/browse/IOCOM-1391)] Flag to enable Messages Home with the new DS ([#5766](https://github.com/pagopa/io-app/issues/5766)) ([fdca533](https://github.com/pagopa/io-app/commit/fdca533b583000ecd4c066ba0ca17f8a74e3786e)) +* [[IOCOM-823](https://pagopa.atlassian.net/browse/IOCOM-823)] Chips for Inbox and Archived messages on new DS Messages' Home ([#5770](https://github.com/pagopa/io-app/issues/5770)) ([cb333d9](https://github.com/pagopa/io-app/commit/cb333d920955eaf96124f1b054ca2d53f8ef072e)) +* [[IOPID-1548](https://pagopa.atlassian.net/browse/IOPID-1548)] DS add new cie errors ([#5750](https://github.com/pagopa/io-app/issues/5750)) ([26916b7](https://github.com/pagopa/io-app/commit/26916b76f9c867a354613d4d6b48543ace4bcb8f)) +* [[IOPID-1725](https://pagopa.atlassian.net/browse/IOPID-1725)] New DS on CIE certificates KO screen ([#5775](https://github.com/pagopa/io-app/issues/5775)) ([26db96b](https://github.com/pagopa/io-app/commit/26db96bf693acc46a4f14faab3f4040a9bb95969)), closes [/github.com/pagopa/io-app/blob/d5d5681817064085219e3854e4fef56431c4519b/ts/screens/authentication/cie/CieCardReaderScreen.tsx#L282](https://github.com/pagopa//github.com/pagopa/io-app/blob/d5d5681817064085219e3854e4fef56431c4519b/ts/screens/authentication/cie/CieCardReaderScreen.tsx/issues/L282) [/github.com/pagopa/io-app/blob/d5d5681817064085219e3854e4fef56431c4519b/ts/screens/authentication/cie/CieCardReaderScreen.tsx#L298](https://github.com/pagopa//github.com/pagopa/io-app/blob/d5d5681817064085219e3854e4fef56431c4519b/ts/screens/authentication/cie/CieCardReaderScreen.tsx/issues/L298) +* **IT Wallet:** [[SIW-980](https://pagopa.atlassian.net/browse/SIW-980)] Add eID issuing IDP selection screen ([#5752](https://github.com/pagopa/io-app/issues/5752)) ([d5d5681](https://github.com/pagopa/io-app/commit/d5d5681817064085219e3854e4fef56431c4519b)) + + +### Bug Fixes + +* [[IOPAE-1129](https://pagopa.atlassian.net/browse/IOPAE-1129)] Service Details bottom gradient displayed only with CTAs ([#5781](https://github.com/pagopa/io-app/issues/5781)) ([8164a03](https://github.com/pagopa/io-app/commit/8164a036eec31c81b750dfd15aa5cc93f003d0da)) +* [[IOPAE-1164](https://pagopa.atlassian.net/browse/IOPAE-1164)] Fix infinite scroll in `InstitutionServicesScreen` ([#5774](https://github.com/pagopa/io-app/issues/5774)) ([8760edc](https://github.com/pagopa/io-app/commit/8760edc9242e945baf027dd4410fd3945a5a473b)) +* `LandingScreen`'s `Carousel` translations ([#5789](https://github.com/pagopa/io-app/issues/5789)) ([04c1105](https://github.com/pagopa/io-app/commit/04c11053cd55ce2b2ae394066c80f5cfa7b93877)) + + +### Chores + +* [[IOBP-575](https://pagopa.atlassian.net/browse/IOBP-575)] Removed seconds from a11y label transaction detail date ([#5786](https://github.com/pagopa/io-app/issues/5786)) ([a4e1f79](https://github.com/pagopa/io-app/commit/a4e1f7930f49aca62a31353ea5a9ff163577aa48)) +* **IT Wallet:** [[SIW-1127](https://pagopa.atlassian.net/browse/SIW-1127)] IT Wallet POC utils implementation ([#5779](https://github.com/pagopa/io-app/issues/5779)) ([6610236](https://github.com/pagopa/io-app/commit/6610236817163bea59b303ee056554223b9a7860)) +* **IT Wallet:** [[SIW-1128](https://pagopa.atlassian.net/browse/SIW-1128)] Add `EidCard` and `EidCardPreview` components ([#5772](https://github.com/pagopa/io-app/issues/5772)) ([8d55432](https://github.com/pagopa/io-app/commit/8d55432dcf081d617a89a6d42cc9ed4dd906f32b)) +* [[IOPAE-1145](https://pagopa.atlassian.net/browse/IOPAE-1145)] Add FeaturedInstitution and FeaturedService carousel/card layout ([#5771](https://github.com/pagopa/io-app/issues/5771)) ([2b85bf7](https://github.com/pagopa/io-app/commit/2b85bf750ba18defa8bc8409b66ab1d738f43604)) +* **Cross:** [[IOAPPX-283](https://pagopa.atlassian.net/browse/IOAPPX-283)] Add `IOScrollView` (next iteration of `GradientScroll`, now deprecated) + `IOScrollViewWithLargeHeader` ([#5704](https://github.com/pagopa/io-app/issues/5704)) ([b4f2762](https://github.com/pagopa/io-app/commit/b4f276279a0384e824221e2ba50651f7020f268a)) +* **Cross:** [[IOAPPX-295](https://pagopa.atlassian.net/browse/IOAPPX-295)] Remove nested navigator from DS section ([#5763](https://github.com/pagopa/io-app/issues/5763)) ([55fa198](https://github.com/pagopa/io-app/commit/55fa198da99e3fda5aaefd7a9e689eab8ab59370)) + ## [2.60.0-rc.2](https://github.com/pagopa/io-app/compare/2.60.0-rc.1...2.60.0-rc.2) (2024-05-15) diff --git a/android/app/build.gradle b/android/app/build.gradle index 23701945408..eea73188746 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -145,8 +145,8 @@ android { applicationId "it.pagopa.io.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 100154816 - versionName "2.60.0.2" + versionCode 100154817 + versionName "2.61.0.0" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { // We configure the CMake build only if you decide to opt-in for the New Architecture. diff --git a/ios/ItaliaApp.xcodeproj/project.pbxproj b/ios/ItaliaApp.xcodeproj/project.pbxproj index fb0b9806791..3d1b54b6d05 100644 --- a/ios/ItaliaApp.xcodeproj/project.pbxproj +++ b/ios/ItaliaApp.xcodeproj/project.pbxproj @@ -784,7 +784,7 @@ CODE_SIGN_ENTITLEMENTS = ItaliaApp/ItaliaApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = M2X5YQ4BJ7; ENABLE_BITCODE = NO; @@ -825,7 +825,7 @@ CODE_SIGN_ENTITLEMENTS = ItaliaApp/ItaliaApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = M2X5YQ4BJ7; ENABLE_BITCODE = NO; diff --git a/ios/ItaliaApp/Info.plist b/ios/ItaliaApp/Info.plist index c5300077562..c512487335d 100644 --- a/ios/ItaliaApp/Info.plist +++ b/ios/ItaliaApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.60.0 + 2.61.0 CFBundleSignature ???? CFBundleURLTypes @@ -34,7 +34,7 @@ CFBundleVersion - 2 + 0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ItaliaAppTests/Info.plist b/ios/ItaliaAppTests/Info.plist index 76cc1a0a17a..9ece5203d9b 100644 --- a/ios/ItaliaAppTests/Info.plist +++ b/ios/ItaliaAppTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.60.0 + 2.61.0 CFBundleSignature ???? CFBundleVersion - 2 + 0 \ No newline at end of file diff --git a/package.json b/package.json index 415e28cbf78..fc1ca314e3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "italia-app", - "version": "2.60.0-rc.2", + "version": "2.61.0-rc.0", "io_backend_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.32.1-RELEASE/api_backend.yaml", "io_public_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.32.1-RELEASE/api_public.yaml", "io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.31/definitions.yml", diff --git a/publiccode.yml b/publiccode.yml index fa15927aa23..be014de13ed 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -5,11 +5,11 @@ publiccodeYmlVersion: '0.2' name: IO logo: "img/app-logo.svg" -releaseDate: '2024-05-15' +releaseDate: '2024-05-22' url: 'https://github.com/pagopa/io-app' applicationSuite: IO landingURL: 'https://io.italia.it/' -softwareVersion: 2.60.0-rc.2 +softwareVersion: 2.61.0-rc.0 developmentStatus: beta softwareType: standalone/mobile roadmap: 'https://io.italia.it/' From 3bfb1d2df9435ca2c439d289290c98751dbbfd03 Mon Sep 17 00:00:00 2001 From: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> Date: Thu, 23 May 2024 08:45:43 +0200 Subject: [PATCH 3/5] chore: change EIC login flow with the dev server to navigate to `CieConsentDataUsageScreen` (#5788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Short description This PR changes the EIC login flow with the dev server to navigate to `CieConsentDataUsageScreen`.
Details

| 🤖 | 🍏 | | - | - | |

## How to test Set `CIE_LOGIN_WITH_DEV_SERVER_ENABLED=YES` and run the app against the dev server. Try a login with EIC. You should see a first screen to login as in the SPID flow. Tapping "Login" you should navigate to `CieConsentDataUsageScreen` where you see again the screen to login, but there you can test all the login errors (ex. 22, 25, etc.). Co-authored-by: Alice Di Rico <83651704+Ladirico@users.noreply.github.com> --- ts/screens/authentication/cie/CiePinScreen.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ts/screens/authentication/cie/CiePinScreen.tsx b/ts/screens/authentication/cie/CiePinScreen.tsx index e39ca2a240a..76564d69fd5 100644 --- a/ts/screens/authentication/cie/CiePinScreen.tsx +++ b/ts/screens/authentication/cie/CiePinScreen.tsx @@ -27,7 +27,10 @@ import { import { SafeAreaView } from "react-native-safe-area-context"; import { useSelector } from "react-redux"; import { IdpData } from "../../../../definitions/content/IdpData"; -import { CieRequestAuthenticationOverlay } from "../../../components/cie/CieRequestAuthenticationOverlay"; +import { + CieEntityIds, + CieRequestAuthenticationOverlay +} from "../../../components/cie/CieRequestAuthenticationOverlay"; import { ContextualHelpPropsMarkdown } from "../../../components/screens/BaseScreenComponent"; import { BottomTopAnimation, @@ -56,6 +59,7 @@ import { trackLoginCiePinInfo, trackLoginCiePinScreen } from "../analytics/cieAnalytics"; +import { getIdpLoginUri } from "../../../utils/login"; const CIE_PIN_LENGTH = 8; @@ -123,8 +127,10 @@ const CiePinScreen = () => { useEffect(() => { if (authUrlGenerated !== undefined) { if (cieFlowForDevServerEnabled) { - const token = /token=([\d\w]+)/.exec(authUrlGenerated)?.[1]; - doLoginSuccess(token as SessionToken, "cie"); + const loginUri = getIdpLoginUri(CieEntityIds.PROD, 3); + navigation.navigate(ROUTES.CIE_CONSENT_DATA_USAGE, { + cieConsentUri: loginUri + }); } else { navigation.navigate(ROUTES.CIE_CARD_READER_SCREEN, { ciePin: pin, From ca2de7835f5dcc32080b4568f29682bd523f0069 Mon Sep 17 00:00:00 2001 From: Alessandro Dell'Oste Date: Thu, 23 May 2024 17:08:46 +0200 Subject: [PATCH 4/5] chore: [IOPAE-1142] Added featured carousels in `ServicesHomeScreen` (#5793) ## Short description This PR refactors the `ServicesHomeScreen` screen by adding the featured services and the featured institutions carousels.
Details

| New services tab | | - | |

## List of changes proposed in this pull request - Added `CardPressableBase` component - Updated carousels by adding the `CardPressableBase` component - Updated `ServicesHomeScreen` screen to display the national institutions and featured carousels - Updated locales ## How to test Using `io-dev-api-server`, navigate to the services tab. Check that the screen is displayed correctly --- locales/en/index.yml | 5 + locales/it/index.yml | 5 + .../common/components/CardPressableBase.tsx | 35 + .../components/InstitutionListSkeleton.tsx} | 18 + .../components/FeaturedInstitutionCard.tsx | 53 +- .../components/FeaturedInstitutionList.tsx | 76 ++ .../home/components/FeaturedServiceCard.tsx | 66 +- .../home/components/FeaturedServiceList.tsx | 72 ++ .../components/ServicesHomeIntitutionList.tsx | 123 -- .../FeaturedInstitutionCard.test.tsx | 58 + .../__tests__/FeaturedServiceCard.test.tsx | 58 + .../FeaturedInstitutionCard.test.tsx.snap | 990 ++++++++++++++++ .../FeaturedServiceCard.test.tsx.snap | 1024 +++++++++++++++++ .../home/hooks/useInstitutionsFetcher.tsx | 53 +- .../home/screens/ServicesHomeScreen.tsx | 143 ++- .../store/reducers/__tests__/store.test.ts | 108 +- .../services/home/store/reducers/index.ts | 26 +- 17 files changed, 2610 insertions(+), 303 deletions(-) create mode 100644 ts/features/services/common/components/CardPressableBase.tsx rename ts/features/services/{home/components/InstitutionListItemSkeleton.tsx => common/components/InstitutionListSkeleton.tsx} (65%) create mode 100644 ts/features/services/home/components/FeaturedInstitutionList.tsx create mode 100644 ts/features/services/home/components/FeaturedServiceList.tsx delete mode 100644 ts/features/services/home/components/ServicesHomeIntitutionList.tsx create mode 100644 ts/features/services/home/components/__tests__/FeaturedInstitutionCard.test.tsx create mode 100644 ts/features/services/home/components/__tests__/FeaturedServiceCard.test.tsx create mode 100644 ts/features/services/home/components/__tests__/__snapshots__/FeaturedInstitutionCard.test.tsx.snap create mode 100644 ts/features/services/home/components/__tests__/__snapshots__/FeaturedServiceCard.test.tsx.snap diff --git a/locales/en/index.yml b/locales/en/index.yml index 18c70b86d43..b500e69f96b 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -2104,6 +2104,11 @@ services: emptyListMessage: There are no services available at this time, pull down to refresh new: New home: + featured: + services: + title: Featured + institutions: + title: Featured Institutions institutions: title: National institution: diff --git a/locales/it/index.yml b/locales/it/index.yml index af7f473f317..3afe881a8c0 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -2104,6 +2104,11 @@ services: emptyListMessage: Non ci sono servizi disponibili al momento, trascina in basso per aggiornare new: Nuovo home: + featured: + services: + title: In primo piano + institutions: + title: Enti in evidenza institutions: title: Nazionali institution: diff --git a/ts/features/services/common/components/CardPressableBase.tsx b/ts/features/services/common/components/CardPressableBase.tsx new file mode 100644 index 00000000000..f1ae663d5d8 --- /dev/null +++ b/ts/features/services/common/components/CardPressableBase.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Pressable } from "react-native"; +import Animated from "react-native-reanimated"; +import { PressableBaseProps, WithTestID } from "@pagopa/io-app-design-system"; +import { useSpringPressScaleAnimation } from "../../../../components/ui/utils/hooks/useSpringPressScaleAnimation"; + +type CardPressableBaseProps = WithTestID; + +export const CardPressableBase = ({ + onPress, + testID, + accessibilityLabel, + children +}: React.PropsWithChildren) => { + const { onPressIn, onPressOut, animatedScaleStyle } = + useSpringPressScaleAnimation(); + + if (onPress === undefined) { + return <>{children}; + } + + return ( + + {children} + + ); +}; diff --git a/ts/features/services/home/components/InstitutionListItemSkeleton.tsx b/ts/features/services/common/components/InstitutionListSkeleton.tsx similarity index 65% rename from ts/features/services/home/components/InstitutionListItemSkeleton.tsx rename to ts/features/services/common/components/InstitutionListSkeleton.tsx index cbc8765ebf2..ca9c46203a6 100644 --- a/ts/features/services/home/components/InstitutionListItemSkeleton.tsx +++ b/ts/features/services/common/components/InstitutionListSkeleton.tsx @@ -1,6 +1,7 @@ import React from "react"; import { View } from "react-native"; import { + Divider, IOListItemStyles, IOListItemVisualParams, IOStyles, @@ -25,3 +26,20 @@ export const InstitutionListItemSkeleton = () => (
); + +type InstitutionListSkeletonProps = { + size?: number; +}; + +export const InstitutionListSkeleton = ({ + size = 3 +}: InstitutionListSkeletonProps) => ( + + {Array.from({ length: size }).map((_, index) => ( + + + {index < size - 1 ? : undefined} + + ))} + +); diff --git a/ts/features/services/home/components/FeaturedInstitutionCard.tsx b/ts/features/services/home/components/FeaturedInstitutionCard.tsx index 025127a1966..f2bb41c542d 100644 --- a/ts/features/services/home/components/FeaturedInstitutionCard.tsx +++ b/ts/features/services/home/components/FeaturedInstitutionCard.tsx @@ -1,3 +1,6 @@ +import React from "react"; +import { Dimensions, StyleSheet, View } from "react-native"; +import Placeholder from "rn-placeholder"; import { Avatar, H6, @@ -7,18 +10,16 @@ import { VSpacer } from "@pagopa/io-app-design-system"; import { OrganizationFiscalCode } from "@pagopa/ts-commons/lib/strings"; -import React from "react"; -import { Dimensions, StyleSheet, View } from "react-native"; -import Placeholder from "rn-placeholder"; import { WithTestID } from "../../../../types/WithTestID"; import { logoForInstitution } from "../utils"; +import { CardPressableBase } from "../../common/components/CardPressableBase"; export type FeaturedInstitutionCardProps = WithTestID<{ id: string; name: string; accessibilityLabel?: string; isNew?: boolean; - onPress?: (id: string) => void; + onPress?: () => void; }>; export const CARD_WIDTH = @@ -59,28 +60,34 @@ const styles = StyleSheet.create({ }); const FeaturedInstitutionCard = (props: FeaturedInstitutionCardProps) => ( - - - - - - -
- {props.name} -
+ + + + + + +
+ {props.name} +
+
-
+ ); const FeaturedInstitutionCardSkeleton = ({ testID }: WithTestID) => ( diff --git a/ts/features/services/home/components/FeaturedInstitutionList.tsx b/ts/features/services/home/components/FeaturedInstitutionList.tsx new file mode 100644 index 00000000000..3cd8581f665 --- /dev/null +++ b/ts/features/services/home/components/FeaturedInstitutionList.tsx @@ -0,0 +1,76 @@ +import React, { useCallback, useMemo } from "react"; +import { ListItemHeader, VSpacer } from "@pagopa/io-app-design-system"; +import { Institution } from "../../../../../definitions/services/Institution"; +import I18n from "../../../../i18n"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; +import { SERVICES_ROUTES } from "../../common/navigation/routes"; +import { featuredInstitutionsGet } from "../store/actions"; +import { + featuredInstitutionsSelector, + isErrorFeaturedInstitutionsSelector, + isLoadingFeaturedInstitutionsSelector +} from "../store/reducers"; +import { + FeaturedInstitutionsCarousel, + FeaturedInstitutionsCarouselSkeleton +} from "./FeaturedInstitutionsCarousel"; + +export const FeaturedInstitutionList = () => { + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + + const featuredInstitutions = useIOSelector(featuredInstitutionsSelector); + const isError = useIOSelector(isErrorFeaturedInstitutionsSelector); + const isLoading = useIOSelector(isLoadingFeaturedInstitutionsSelector); + + useOnFirstRender(() => dispatch(featuredInstitutionsGet.request())); + + const handlePress = useCallback( + ({ fiscal_code, name }: Institution) => { + navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, { + screen: SERVICES_ROUTES.INSTITUTION_SERVICES, + params: { + institutionId: fiscal_code, + institutionName: name + } + }); + }, + [navigation] + ); + + const mappedFeaturedInstitutions = useMemo( + () => + featuredInstitutions.map(props => ({ + ...props, + onPress: () => handlePress(props) + })), + [featuredInstitutions, handlePress] + ); + + const isVisible = useMemo( + () => isLoading || mappedFeaturedInstitutions.length > 0, + [isLoading, mappedFeaturedInstitutions] + ); + + if (isError || !isVisible) { + return null; + } + + return ( + <> + + {isLoading ? ( + + ) : ( + + )} + + + ); +}; diff --git a/ts/features/services/home/components/FeaturedServiceCard.tsx b/ts/features/services/home/components/FeaturedServiceCard.tsx index 0aab69075dd..ae8fc0dc18b 100644 --- a/ts/features/services/home/components/FeaturedServiceCard.tsx +++ b/ts/features/services/home/components/FeaturedServiceCard.tsx @@ -1,3 +1,6 @@ +import React from "react"; +import { StyleSheet, View } from "react-native"; +import Placeholder from "rn-placeholder"; import { Avatar, Badge, @@ -5,13 +8,12 @@ import { IOColors, IOSpacingScale, IOVisualCostants, + TestID, VSpacer } from "@pagopa/io-app-design-system"; -import React from "react"; -import { StyleSheet, View } from "react-native"; -import Placeholder from "rn-placeholder"; import I18n from "../../../../i18n"; import { WithTestID } from "../../../../types/WithTestID"; +import { CardPressableBase } from "../../common/components/CardPressableBase"; import { logoForService } from "../utils"; import OrganizationNameLabel from "./OrganizationNameLabel"; @@ -61,35 +63,43 @@ const styles = StyleSheet.create({ }); const FeaturedServiceCard = (props: FeaturedServiceCardProps) => ( - - - - {props.isNew && } - - -

- {props.name} -

- {props.organizationName && ( - <> - - - {props.organizationName} - - - )} + + + + {props.isNew && ( + + )} + + +

+ {props.name} +

+ {props.organizationName && ( + <> + + + {props.organizationName} + + + )} +
-
+ ); -const FeaturedServiceCardSkeleton = ({ testID }: WithTestID) => ( +const FeaturedServiceCardSkeleton = ({ testID }: TestID) => ( { + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + + const featuredServices = useIOSelector(featuredServicesSelector); + const isError = useIOSelector(isErrorFeaturedServicesSelector); + const isLoading = useIOSelector(isLoadingFeaturedServicesSelector); + + useOnFirstRender(() => dispatch(featuredServicesGet.request())); + + const handlePress = useCallback( + (serviceId: string) => { + navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, { + screen: SERVICES_ROUTES.SERVICE_DETAIL, + params: { + serviceId: serviceId as NonEmptyString + } + }); + }, + [navigation] + ); + + const mappedFeaturedServices = useMemo( + () => + featuredServices.map(({ organization_name, ...rest }) => ({ + ...rest, + organizationName: organization_name, + onPress: () => handlePress(rest.id) + })), + [featuredServices, handlePress] + ); + + const isVisible = useMemo( + () => isLoading || mappedFeaturedServices.length > 0, + [isLoading, mappedFeaturedServices] + ); + + if (isError || !isVisible) { + return null; + } + + return ( + <> + + {isLoading ? ( + + ) : ( + + )} + + + ); +}; diff --git a/ts/features/services/home/components/ServicesHomeIntitutionList.tsx b/ts/features/services/home/components/ServicesHomeIntitutionList.tsx deleted file mode 100644 index 1717a758a65..00000000000 --- a/ts/features/services/home/components/ServicesHomeIntitutionList.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useCallback, useEffect } from "react"; -import { FlatList, ListRenderItemInfo } from "react-native"; -import { - Divider, - IOStyles, - IOToast, - ListItemHeader, - ListItemNav, - VSpacer -} from "@pagopa/io-app-design-system"; -import { Institution } from "../../../../../definitions/services/Institution"; -import I18n from "../../../../i18n"; -import { useIONavigation } from "../../../../navigation/params/AppParamsList"; -import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; -import { useFirstRender } from "../../common/hooks/useFirstRender"; -import { SERVICES_ROUTES } from "../../common/navigation/routes"; -import { useInstitutionsFetcher } from "../hooks/useInstitutionsFetcher"; -import { logoForInstitution } from "../utils"; -import { InstitutionListItemSkeleton } from "./InstitutionListItemSkeleton"; - -export const ServicesHomeIntitutionList = () => { - const isFirstRender = useFirstRender(); - const navigation = useIONavigation(); - - const { - currentPage, - data, - fetchInstitutions, - isError, - isLoading, - isUpdating, - refreshInstitutions - } = useInstitutionsFetcher(); - - useOnFirstRender(() => fetchInstitutions(0)); - - useEffect(() => { - if (!isFirstRender && isError) { - IOToast.error(I18n.t("global.genericError")); - } - }, [isFirstRender, isError]); - - const handleEndReached = useCallback( - () => fetchInstitutions(currentPage + 1), - [currentPage, fetchInstitutions] - ); - - const navigateToInstitution = useCallback( - (institution: Institution) => - navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, { - screen: SERVICES_ROUTES.INSTITUTION_SERVICES, - params: { - institutionId: institution.id, - institutionName: institution.name - } - }), - [navigation] - ); - - const renderItem = useCallback( - ({ item }: ListRenderItemInfo) => ( - navigateToInstitution(item)} - accessibilityLabel={item.name} - avatarProps={{ - logoUri: logoForInstitution(item) - }} - /> - ), - [navigateToInstitution] - ); - - const renderListFooterComponent = useCallback(() => { - if (isUpdating && currentPage > 0) { - return ( - <> - - - - - - - ); - } - return ; - }, [currentPage, isUpdating]); - - const ListHeaderComponent = ( - - ); - - if (isFirstRender || isLoading) { - return ( - } - contentContainerStyle={IOStyles.horizontalContentPadding} - data={Array.from({ length: 5 })} - keyExtractor={(_, index) => `placeholder-${index}`} - renderItem={() => } - onRefresh={refreshInstitutions} - refreshing={isUpdating} - /> - ); - } - - return ( - } - contentContainerStyle={IOStyles.horizontalContentPadding} - data={data?.institutions || []} - keyExtractor={(item, index) => `institution-${item.id}-${index}`} - renderItem={renderItem} - onEndReached={handleEndReached} - onEndReachedThreshold={0.001} - onRefresh={refreshInstitutions} - refreshing={isUpdating} - ListFooterComponent={renderListFooterComponent} - /> - ); -}; diff --git a/ts/features/services/home/components/__tests__/FeaturedInstitutionCard.test.tsx b/ts/features/services/home/components/__tests__/FeaturedInstitutionCard.test.tsx new file mode 100644 index 00000000000..a8b25fe8012 --- /dev/null +++ b/ts/features/services/home/components/__tests__/FeaturedInstitutionCard.test.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { createStore } from "redux"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { appReducer } from "../../../../../store/reducers"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { SERVICES_ROUTES } from "../../../common/navigation/routes"; +import { + FeaturedInstitutionCard, + FeaturedInstitutionCardProps +} from "../FeaturedInstitutionCard"; + +const testID = "FeaturedInstitutionCardTestID"; + +describe("FeaturedInstitutionCard", () => { + it(`should match the snapshot`, () => { + const component = render({ + id: "1", + name: "### Institution ###", + accessibilityLabel: "### Accessibility Label ###", + onPress: () => undefined, + testID + }); + expect(component).toMatchSnapshot(); + }); + + it(`should render card without pressable wrapper`, () => { + const { queryByTestId } = render({ + id: "1", + name: "### Institution ###", + accessibilityLabel: "### Accessibility Label ###", + testID + }); + expect(queryByTestId(`${testID}-pressable`)).toBeNull(); + }); + + it(`should match the snapshot when isNew is true`, () => { + const component = render({ + id: "1", + name: "### Institution ###", + accessibilityLabel: "### Accessibility Label ###", + onPress: () => undefined, + isNew: true, + testID + }); + expect(component).toMatchSnapshot(); + }); +}); + +function render(props: FeaturedInstitutionCardProps) { + const globalState = appReducer(undefined, applicationChangeState("active")); + return renderScreenWithNavigationStoreContext( + () => , + SERVICES_ROUTES.SERVICES_HOME, + {}, + createStore(appReducer, globalState as any) + ); +} diff --git a/ts/features/services/home/components/__tests__/FeaturedServiceCard.test.tsx b/ts/features/services/home/components/__tests__/FeaturedServiceCard.test.tsx new file mode 100644 index 00000000000..800bb8b080f --- /dev/null +++ b/ts/features/services/home/components/__tests__/FeaturedServiceCard.test.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { createStore } from "redux"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { appReducer } from "../../../../../store/reducers"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { SERVICES_ROUTES } from "../../../common/navigation/routes"; +import { + FeaturedServiceCard, + FeaturedServiceCardProps +} from "../FeaturedServiceCard"; + +const testID = "FeaturedServiceCardTestID"; + +describe("FeaturedServiceCard", () => { + it(`should match the snapshot`, () => { + const component = render({ + id: "1", + name: "### Service ###", + accessibilityLabel: "### Accessibility Label ###", + onPress: () => undefined, + testID + }); + expect(component).toMatchSnapshot(); + }); + + it(`should render card without pressable wrapper`, () => { + const { queryByTestId } = render({ + id: "1", + name: "### Service ###", + accessibilityLabel: "### Accessibility Label ###", + testID + }); + expect(queryByTestId(`${testID}-pressable`)).toBeNull(); + }); + + it(`should match the snapshot when isNew is true`, () => { + const component = render({ + id: "1", + name: "### Service ###", + accessibilityLabel: "### Accessibility Label ###", + onPress: () => undefined, + isNew: true, + testID + }); + expect(component).toMatchSnapshot(); + }); +}); + +function render(props: FeaturedServiceCardProps) { + const globalState = appReducer(undefined, applicationChangeState("active")); + return renderScreenWithNavigationStoreContext( + () => , + SERVICES_ROUTES.SERVICES_HOME, + {}, + createStore(appReducer, globalState as any) + ); +} diff --git a/ts/features/services/home/components/__tests__/__snapshots__/FeaturedInstitutionCard.test.tsx.snap b/ts/features/services/home/components/__tests__/__snapshots__/FeaturedInstitutionCard.test.tsx.snap new file mode 100644 index 00000000000..e38333bde1b --- /dev/null +++ b/ts/features/services/home/components/__tests__/__snapshots__/FeaturedInstitutionCard.test.tsx.snap @@ -0,0 +1,990 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FeaturedInstitutionCard should match the snapshot 1`] = ` + + + + + + + + + + + + + + + SERVICES_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### Institution ### + + + + + + + + + + + + + + + + + +`; + +exports[`FeaturedInstitutionCard should match the snapshot when isNew is true 1`] = ` + + + + + + + + + + + + + + + SERVICES_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### Institution ### + + + + + + + + + + + + + + + + + +`; diff --git a/ts/features/services/home/components/__tests__/__snapshots__/FeaturedServiceCard.test.tsx.snap b/ts/features/services/home/components/__tests__/__snapshots__/FeaturedServiceCard.test.tsx.snap new file mode 100644 index 00000000000..f05754803c8 --- /dev/null +++ b/ts/features/services/home/components/__tests__/__snapshots__/FeaturedServiceCard.test.tsx.snap @@ -0,0 +1,1024 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FeaturedServiceCard should match the snapshot 1`] = ` + + + + + + + + + + + + + + + SERVICES_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### Service ### + + + + + + + + + + + + + + + + +`; + +exports[`FeaturedServiceCard should match the snapshot when isNew is true 1`] = ` + + + + + + + + + + + + + + + SERVICES_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + New + + + + + + ### Service ### + + + + + + + + + + + + + + + + +`; diff --git a/ts/features/services/home/hooks/useInstitutionsFetcher.tsx b/ts/features/services/home/hooks/useInstitutionsFetcher.tsx index b717d358fc1..19bbe29f640 100644 --- a/ts/features/services/home/hooks/useInstitutionsFetcher.tsx +++ b/ts/features/services/home/hooks/useInstitutionsFetcher.tsx @@ -1,3 +1,4 @@ +import { useCallback, useEffect, useState } from "react"; import { ScopeTypeEnum } from "../../../../../definitions/services/ScopeType"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; import { paginatedInstitutionsGet } from "../store/actions"; @@ -22,27 +23,44 @@ export const useInstitutionsFetcher = () => { const isUpdating = useIOSelector(isUpdatingPaginatedInstitutionsSelector); const isError = useIOSelector(isErrorPaginatedInstitutionsSelector); - const fetchPage = (page: number) => { - if (!isLoading && !isUpdating) { - dispatch( - paginatedInstitutionsGet.request({ - offset: page * LIMIT, - limit: LIMIT, - scope: ScopeTypeEnum.NATIONAL - }) - ); - } - }; + const [isRefreshing, setIsRefreshing] = useState(false); - const fetchInstitutions = (page: number) => { - if (isLastPage) { - return; + useEffect(() => { + if (isRefreshing && !isUpdating) { + setIsRefreshing(false); } + }, [isRefreshing, isUpdating]); - fetchPage(page); - }; + const fetchPage = useCallback( + (page: number) => { + if (!isLoading && !isUpdating) { + dispatch( + paginatedInstitutionsGet.request({ + offset: page * LIMIT, + limit: LIMIT, + scope: ScopeTypeEnum.NATIONAL + }) + ); + } + }, + [dispatch, isLoading, isUpdating] + ); + + const fetchInstitutions = useCallback( + (page: number) => { + if (isLastPage) { + return; + } + + fetchPage(page); + }, + [isLastPage, fetchPage] + ); - const refreshInstitutions = () => fetchPage(0); + const refreshInstitutions = useCallback(() => { + setIsRefreshing(true); + fetchPage(0); + }, [fetchPage]); return { currentPage, @@ -50,6 +68,7 @@ export const useInstitutionsFetcher = () => { isError, isLoading, isUpdating, + isRefreshing, fetchInstitutions, refreshInstitutions }; diff --git a/ts/features/services/home/screens/ServicesHomeScreen.tsx b/ts/features/services/home/screens/ServicesHomeScreen.tsx index e06678ab6a4..152b55ecf19 100644 --- a/ts/features/services/home/screens/ServicesHomeScreen.tsx +++ b/ts/features/services/home/screens/ServicesHomeScreen.tsx @@ -1,4 +1,141 @@ -import React from "react"; -import { ServicesHomeIntitutionList } from "../components/ServicesHomeIntitutionList"; +import React, { useCallback, useEffect } from "react"; +import { FlatList, ListRenderItemInfo, StyleSheet } from "react-native"; +import { + Divider, + IOStyles, + IOToast, + ListItemHeader, + ListItemNav, + VSpacer +} from "@pagopa/io-app-design-system"; +import I18n from "../../../../i18n"; +import { Institution } from "../../../../../definitions/services/Institution"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { useIODispatch } from "../../../../store/hooks"; +import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; +import { InstitutionListSkeleton } from "../../common/components/InstitutionListSkeleton"; +import { useFirstRender } from "../../common/hooks/useFirstRender"; +import { SERVICES_ROUTES } from "../../common/navigation/routes"; +import { useInstitutionsFetcher } from "../hooks/useInstitutionsFetcher"; +import { featuredInstitutionsGet, featuredServicesGet } from "../store/actions"; +import { logoForInstitution } from "../utils"; +import { FeaturedInstitutionList } from "../components/FeaturedInstitutionList"; +import { FeaturedServiceList } from "../components/FeaturedServiceList"; -export const ServicesHomeScreen = () => ; +const styles = StyleSheet.create({ + scrollContentContainer: { + flexGrow: 1 + } +}); + +export const ServicesHomeScreen = () => { + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + const isFirstRender = useFirstRender(); + + const { + currentPage, + data, + fetchInstitutions, + isError, + isLoading, + isUpdating, + isRefreshing, + refreshInstitutions + } = useInstitutionsFetcher(); + + useOnFirstRender(() => fetchInstitutions(0)); + + useEffect(() => { + if (!isFirstRender && isError) { + IOToast.error(I18n.t("global.genericError")); + } + }, [isFirstRender, isError]); + + const renderListEmptyComponent = useCallback(() => { + if (isFirstRender || isLoading) { + return ( + <> + + + + ); + } + return <>; + }, [isFirstRender, isLoading]); + + const renderListHeaderComponent = useCallback( + () => ( + <> + + + + + ), + [] + ); + + const renderListFooterComponent = useCallback(() => { + if (isUpdating && !isRefreshing) { + return ; + } + return ; + }, [isUpdating, isRefreshing]); + + const handleRefresh = useCallback(() => { + dispatch(featuredServicesGet.request()); + dispatch(featuredInstitutionsGet.request()); + refreshInstitutions(); + }, [dispatch, refreshInstitutions]); + + const handleEndReached = useCallback( + () => fetchInstitutions(currentPage + 1), + [currentPage, fetchInstitutions] + ); + + const navigateToInstitution = useCallback( + (institution: Institution) => + navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, { + screen: SERVICES_ROUTES.INSTITUTION_SERVICES, + params: { + institutionId: institution.id, + institutionName: institution.name + } + }), + [navigation] + ); + + const renderInstitutionItem = useCallback( + ({ item }: ListRenderItemInfo) => ( + navigateToInstitution(item)} + accessibilityLabel={item.name} + avatarProps={{ + logoUri: logoForInstitution(item) + }} + /> + ), + [navigateToInstitution] + ); + + return ( + } + ListEmptyComponent={renderListEmptyComponent} + ListFooterComponent={renderListFooterComponent} + ListHeaderComponent={renderListHeaderComponent} + contentContainerStyle={[ + styles.scrollContentContainer, + IOStyles.horizontalContentPadding + ]} + data={data?.institutions || []} + keyExtractor={(item, index) => `institution-${item.id}-${index}`} + onEndReached={handleEndReached} + onEndReachedThreshold={0.001} + onRefresh={handleRefresh} + refreshing={isRefreshing} + renderItem={renderInstitutionItem} + /> + ); +}; diff --git a/ts/features/services/home/store/reducers/__tests__/store.test.ts b/ts/features/services/home/store/reducers/__tests__/store.test.ts index e760008aaf6..2bcad4c329f 100644 --- a/ts/features/services/home/store/reducers/__tests__/store.test.ts +++ b/ts/features/services/home/store/reducers/__tests__/store.test.ts @@ -9,8 +9,6 @@ import { isLoadingFeaturedInstitutionsSelector, isLoadingFeaturedServicesSelector, isLoadingPaginatedInstitutionsSelector, - isUpdatingFeaturedInstitutionsSelector, - isUpdatingFeaturedServicesSelector, isUpdatingPaginatedInstitutionsSelector, paginatedInstitutionsCurrentPageSelector, paginatedInstitutionsLastPageSelector, @@ -492,21 +490,19 @@ describe("Services home featuredInstitutions selectors", () => { }) ) ); - expect(featuredInstitutions).toStrictEqual({ - institutions: MOCK_INSTITUTIONS - }); + expect(featuredInstitutions).toStrictEqual(MOCK_INSTITUTIONS); }); it("should return undefined when not pot.some", () => { expect( featuredInstitutionsSelector(appReducer(undefined, {} as Action)) - ).toBeUndefined(); + ).toStrictEqual([]); expect( featuredInstitutionsSelector( appReducer({} as GlobalState, featuredInstitutionsGet.request()) ) - ).toBeUndefined(); + ).toStrictEqual([]); expect( featuredInstitutionsSelector( @@ -515,7 +511,7 @@ describe("Services home featuredInstitutions selectors", () => { featuredInstitutionsGet.failure(MOCK_NETWORK_ERROR) ) ) - ).toBeUndefined(); + ).toStrictEqual([]); }); }); @@ -547,51 +543,6 @@ describe("Services home featuredInstitutions selectors", () => { }); }); - describe("isUpdatingFeaturedInstitutionsSelector", () => { - it("should return true when pot.updating", () => { - const state = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, state as any); - - store.dispatch( - featuredInstitutionsGet.success({ - institutions: MOCK_INSTITUTIONS - }) - ); - store.dispatch(featuredInstitutionsGet.request()); - - const isUpdating = isUpdatingFeaturedInstitutionsSelector( - store.getState() - ); - - expect(isUpdating).toStrictEqual(true); - }); - - it("should return false when not pot.updating", () => { - expect( - isUpdatingFeaturedInstitutionsSelector( - appReducer(undefined, {} as Action) - ) - ).toStrictEqual(false); - - expect( - isUpdatingFeaturedInstitutionsSelector( - appReducer({} as GlobalState, featuredInstitutionsGet.request()) - ) - ).toStrictEqual(false); - - expect( - isUpdatingFeaturedInstitutionsSelector( - appReducer( - {} as GlobalState, - featuredInstitutionsGet.success({ - institutions: MOCK_INSTITUTIONS - }) - ) - ) - ).toStrictEqual(false); - }); - }); - describe("isErrorFeaturedInstitutionsSelector", () => { it("should return true when pot.error", () => { const isError = isErrorFeaturedInstitutionsSelector( @@ -678,21 +629,19 @@ describe("Services home featuredServices selectors", () => { }) ) ); - expect(featuredServices).toStrictEqual({ - services: MOCK_FEATURED_SERVICES - }); + expect(featuredServices).toStrictEqual(MOCK_FEATURED_SERVICES); }); it("should return undefined when not pot.some", () => { expect( featuredServicesSelector(appReducer(undefined, {} as Action)) - ).toBeUndefined(); + ).toStrictEqual([]); expect( featuredServicesSelector( appReducer({} as GlobalState, featuredServicesGet.request()) ) - ).toBeUndefined(); + ).toStrictEqual([]); expect( featuredServicesSelector( @@ -701,7 +650,7 @@ describe("Services home featuredServices selectors", () => { featuredServicesGet.failure(MOCK_NETWORK_ERROR) ) ) - ).toBeUndefined(); + ).toStrictEqual([]); }); }); @@ -731,47 +680,6 @@ describe("Services home featuredServices selectors", () => { }); }); - describe("isUpdatingFeaturedServicesSelector", () => { - it("should return true when pot.updating", () => { - const state = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, state as any); - - store.dispatch( - featuredServicesGet.success({ - services: MOCK_FEATURED_SERVICES - }) - ); - store.dispatch(featuredServicesGet.request()); - - const isUpdating = isUpdatingFeaturedServicesSelector(store.getState()); - - expect(isUpdating).toStrictEqual(true); - }); - - it("should return false when not pot.updating", () => { - expect( - isUpdatingFeaturedServicesSelector(appReducer(undefined, {} as Action)) - ).toStrictEqual(false); - - expect( - isUpdatingFeaturedServicesSelector( - appReducer({} as GlobalState, featuredServicesGet.request()) - ) - ).toStrictEqual(false); - - expect( - isUpdatingFeaturedServicesSelector( - appReducer( - {} as GlobalState, - featuredServicesGet.success({ - services: MOCK_FEATURED_SERVICES - }) - ) - ) - ).toStrictEqual(false); - }); - }); - describe("isErrorFeaturedServicesSelector", () => { it("should return true when pot.error", () => { const isError = isErrorFeaturedServicesSelector( diff --git a/ts/features/services/home/store/reducers/index.ts b/ts/features/services/home/store/reducers/index.ts index faa7cbd604a..b406de31f9b 100644 --- a/ts/features/services/home/store/reducers/index.ts +++ b/ts/features/services/home/store/reducers/index.ts @@ -32,7 +32,7 @@ const homeReducer = ( action: Action ): ServicesHomeState => { switch (action.type) { - // Fetch Institutions actions + // Get Institutions actions case getType(paginatedInstitutionsGet.request): if (pot.isNone(state.paginatedInstitutions)) { return { @@ -159,7 +159,14 @@ export const featuredInstitutionsPotSelector = createSelector( export const featuredInstitutionsSelector = createSelector( featuredInstitutionsPotSelector, - pot.toUndefined + featuredInstitutionsPot => + pot.getOrElse( + pot.map( + featuredInstitutionsPot, + featuredInstitutions => featuredInstitutions.institutions + ), + [] + ) ); export const featuredServicesPotSelector = createSelector( @@ -169,7 +176,14 @@ export const featuredServicesPotSelector = createSelector( export const featuredServicesSelector = createSelector( featuredServicesPotSelector, - pot.toUndefined + featuredServicesPot => + pot.getOrElse( + pot.map( + featuredServicesPot, + featuredServices => featuredServices.services + ), + [] + ) ); export const isLoadingPaginatedInstitutionsSelector = (state: GlobalState) => @@ -184,18 +198,12 @@ export const isErrorPaginatedInstitutionsSelector = (state: GlobalState) => export const isLoadingFeaturedInstitutionsSelector = (state: GlobalState) => pipe(state, featuredInstitutionsPotSelector, pot.isLoading); -export const isUpdatingFeaturedInstitutionsSelector = (state: GlobalState) => - pipe(state, featuredInstitutionsPotSelector, pot.isUpdating); - export const isErrorFeaturedInstitutionsSelector = (state: GlobalState) => pipe(state, featuredInstitutionsPotSelector, pot.isError); export const isLoadingFeaturedServicesSelector = (state: GlobalState) => pipe(state, featuredServicesPotSelector, pot.isLoading); -export const isUpdatingFeaturedServicesSelector = (state: GlobalState) => - pipe(state, featuredServicesPotSelector, pot.isUpdating); - export const isErrorFeaturedServicesSelector = (state: GlobalState) => pipe(state, featuredServicesPotSelector, pot.isError); From 45d20bc77ed78589d826fd806096dab06bb6995b Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 23 May 2024 17:25:28 +0200 Subject: [PATCH 5/5] feat: [IOCOM-1133,IOCOM-1235,IOCOM-1372] Push Notification Opt In screen, new DS (#5734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚠️ This PR depends on #5732 ⚠️ ⚠️ This PR depends on [io-app-design-system#253](https://github.com/pagopa/io-app-design-system/pull/253) ⚠️ ⚠️ This PR depends on [io-app-design-system#256](https://github.com/pagopa/io-app-design-system/pull/256) ⚠️ ## Short description This PR aligns the Push Notifications Opt In screen and the profile push notification settings to the new DS |Both on
upper part|Both on
scrolled down|Bottom sheet| |-|-|-| |![On1](https://github.com/pagopa/io-app/assets/5150343/66a756b4-9706-4cda-a20a-b5ebf92e6f1d)|![Simulator Screenshot - iPhone 15 - 2024-05-10 at 12 26 26](https://github.com/pagopa/io-app/assets/5150343/6e6c0ed5-c44c-4e81-ae9c-477ff6e22658)|![OnBS](https://github.com/pagopa/io-app/assets/5150343/9b079428-5262-4f79-b386-bc7e0b6af892)| |Preview Off
Reminder On|Preview On
Reminder Off|Both Off| |-|-|-| |![Pre](https://github.com/pagopa/io-app/assets/5150343/9955c886-ecf5-4401-8e86-7c11a6c7c486)|![Rem](https://github.com/pagopa/io-app/assets/5150343/ea87c7b0-3ca9-435d-993d-2a2994ff9741)|![Off](https://github.com/pagopa/io-app/assets/5150343/0de87ad0-824a-4eca-8251-67ed8ba58ef1)| |Profile
Both on|Profile
Bottom sheet| |-|-| |![ProfileOnPreOnRem](https://github.com/pagopa/io-app/assets/5150343/4bd1d77a-985f-4d8b-89b0-771cedf5ae8c)|![ProfileBS](https://github.com/pagopa/io-app/assets/5150343/a4ab71eb-e5b1-493a-9e4c-d487d1c0e0ec)| |Profile
On Off|Profile
Off On|Profile
Off Off| |-|-|-| |![ProfileOnPreOffRem](https://github.com/pagopa/io-app/assets/5150343/f8fb2554-e94c-46c9-90da-198195d24f28)|![ProfileOffPreOnRem](https://github.com/pagopa/io-app/assets/5150343/4dc52f4a-8165-4495-90cd-771d1009ea63)|![ProfileOffPreOffRem](https://github.com/pagopa/io-app/assets/5150343/494f7064-d6e7-46a2-92c8-5782c161beda)| ## List of changes proposed in this pull request - All related onboarding screens have been ported to the new DS - A lot of test snapshot have been updated due to a change in margins on the DS library ## How to test Using the io-dev-api-server, configure the profile in order to have both `reminder_status` and `push_notifications_content_type` set to undefined. Perform a login and the opt-in screen should appear. Check that both values are properly set after tapping the "Continue" button. For the profile screen, navigate to it and change switches' values --- .env.local | 2 - .env.production | 2 - img/onboarding/notification_blue.png | Bin 2746 -> 0 bytes img/onboarding/notification_blue@2x.png | Bin 6256 -> 0 bytes img/onboarding/notification_blue@3x.png | Bin 9371 -> 0 bytes img/onboarding/notification_white.png | Bin 2709 -> 2474 bytes img/onboarding/notification_white@2x.png | Bin 6027 -> 5602 bytes img/onboarding/notification_white@3x.png | Bin 9046 -> 8769 bytes locales/it/index.yml | 16 +- .../ui/RNavScreenWithLargeHeader.tsx | 2 +- ts/config.ts | 3 - .../components/NotificationPreviewSample.tsx | 25 +- .../NotificationsPreferencesPreview.tsx | 7 +- .../ProfileNotificationsSettings.tsx | 86 + .../NotificationPreviewSample.test.tsx | 161 +- .../NotificationsPreferencesPreview.test.tsx | 48 + .../ProfileNotificationsSettings.test.tsx | 77 + .../NotificationPreviewSample.test.tsx.snap | 2157 + ...ificationsPreferencesPreview.test.tsx.snap | 2293 + ...ProfileNotificationsSettings.test.tsx.snap | 169793 +++++++++++++++ .../hooks/usePreviewMoreInfo.tsx | 30 +- ...checkNotificationsPermissionsSaga.test.tsx | 82 - ...checkNotificationsPreferencesSaga.test.tsx | 205 + .../checkNotificationsPermissionsSaga.ts | 43 - .../checkNotificationsPreferencesSaga.ts | 60 +- ...boardingNotificationsInfoScreenConsent.tsx | 317 +- ...boardingNotificationsPreferencesScreen.tsx | 270 +- ...ingNotificationsInfoScreenConsent.test.tsx | 56 +- ...ingNotificationsPreferencesScreen.test.tsx | 185 +- ...tificationsInfoScreenConsent.test.tsx.snap | 1489 + ...tificationsPreferencesScreen.test.tsx.snap | 8476 + .../utils/configurePushNotification.ts | 8 +- ts/navigation/OnboardingNavigator.tsx | 18 +- ts/navigation/ProfileNavigator.tsx | 11 +- ...checkNotificationsPermissionsSaga.test.tsx | 82 - .../NotificationsPreferencesScreen.tsx | 184 +- ts/screens/profile/PreferencesScreen.tsx | 2 - .../NotificationsPreferencesScreen.test.tsx | 295 +- ...tificationsPreferencesScreen.test.tsx.snap | 24715 +++ ts/store/reducers/__tests__/profile.test.ts | 442 +- ts/store/reducers/profile.ts | 39 +- 41 files changed, 210600 insertions(+), 1081 deletions(-) delete mode 100644 img/onboarding/notification_blue.png delete mode 100644 img/onboarding/notification_blue@2x.png delete mode 100644 img/onboarding/notification_blue@3x.png create mode 100644 ts/features/pushNotifications/components/ProfileNotificationsSettings.tsx create mode 100644 ts/features/pushNotifications/components/__tests__/NotificationsPreferencesPreview.test.tsx create mode 100644 ts/features/pushNotifications/components/__tests__/ProfileNotificationsSettings.test.tsx create mode 100644 ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationPreviewSample.test.tsx.snap create mode 100644 ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationsPreferencesPreview.test.tsx.snap create mode 100644 ts/features/pushNotifications/components/__tests__/__snapshots__/ProfileNotificationsSettings.test.tsx.snap delete mode 100644 ts/features/pushNotifications/sagas/__tests__/checkNotificationsPermissionsSaga.test.tsx create mode 100644 ts/features/pushNotifications/sagas/__tests__/checkNotificationsPreferencesSaga.test.tsx delete mode 100644 ts/features/pushNotifications/sagas/checkNotificationsPermissionsSaga.ts create mode 100644 ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsInfoScreenConsent.test.tsx.snap create mode 100644 ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsPreferencesScreen.test.tsx.snap delete mode 100644 ts/sagas/startup/__tests__/checkNotificationsPermissionsSaga.test.tsx create mode 100644 ts/screens/profile/__test__/__snapshots__/NotificationsPreferencesScreen.test.tsx.snap diff --git a/.env.local b/.env.local index 14dab675003..b748bf88d3b 100644 --- a/.env.local +++ b/.env.local @@ -59,8 +59,6 @@ PREMIUM_MESSAGES_OPT_IN_ENABLED=YES CDC_ENABLED = YES # Scan additional barcodes (E.g. Data Matrix) in the payment section SCAN_ADDITIONAL_BARCODES_ENABLED = YES -# Opt-in for reminder push notifications -REMINDERS_OPT_IN_ENABLED=YES # FCI (Firma con IO) feature FCI_ENABLED=YES # IDPay diff --git a/.env.production b/.env.production index a17b711a25b..98049503f0d 100644 --- a/.env.production +++ b/.env.production @@ -59,8 +59,6 @@ PREMIUM_MESSAGES_OPT_IN_ENABLED=YES CDC_ENABLED = YES # Scan additional barcodes (E.g. Data Matrix) in the payment section SCAN_ADDITIONAL_BARCODES_ENABLED = YES -# Opt-in for reminder push notifications -REMINDERS_OPT_IN_ENABLED=YES # FCI (Firma con IO) feature FCI_ENABLED=YES # IDPay diff --git a/img/onboarding/notification_blue.png b/img/onboarding/notification_blue.png deleted file mode 100644 index 378ce65d796a741c7fab02ea32d564065a2e0e1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2746 zcmbVOc{mj69-giuNt-Rk&PbCjBl|ki5N1SqL+rVM`C&W$w004dq^Q*QT z&E$}LoSU;gV9EX92yeK#V*~)eC-5&G0TdLS=Nul1u!TVZ_+jy7&gQ7UiM0s;(45J~ z_B;jvoIqGyHMxm7vPQ8E7F?1T*cd?V%Zm6fCe=k{{ZW;xEpPJVq&Qec+92H02Ob_^ zIVfYR`k;KSPB>z@($;&_H0)S~sgU2#cR;;x z>5xQ<*^HWu?_sQN$?;y-G#SS)oGDSn0|tSETm?q(G+GtBJx9fYwu_GX`h|Xq(`@&WtZ=X%;!Xyv8oE8wB@Y^>M8|fFiAymqp3Zsr zt(gC>L$SN$15=XDq@Ge7V|enDiA+<j?{+HQXrN(*tC_nL{oX+2C}z7gQ0IGm zIeQ~(eJb+(St^%$eb$$3b_e-!9ai;;=k7>;-|fjuIGvK9x3J!Ep!7rj z4YqmY%UiFoNa>D6PI`{9mBiij2>Ti@KRl+dOZ6FnM>&(!Lel6pkr?B<)a#m;C#@fP zMxF@1A5=jv*y+};Kt}WS$<+hM!7&qnm$L{Fpoo^?slIHb*fTY1dxun|Ss-hwH`6&% zC6Ok6JJJTm*a=b#U}TxJw&~*!V~pz)x%G#InJ5d)EKL;|5|z9=LxyKDd&Z33`o%nT z18T&N9I&!x3yJwtPmj&@80D+UajhwQqPyOZPGeoD5?M2`q15I9mv-0aQ3ZAmB~y>h z_Kh?gvFL?cB|Ef$Wmy)m78J4J7t#193BSJ;Wr=YivaAgyAq^YZv9q?z*xkJGU;WHqk(3>cWAT#2bov z6}_{PIzj<0AP>iSqwL96sAorExjF3aV4nBhTEhyI#j%4*XolFKnU zraBN8ck{4&e(*MKvlE9;suOPWKu$D!HXaK?os&fKQx}6-Xy@mR(Rvk=OxY#>st^ZR zk#0~75cqEB_oI8+5}_|m#w)06GR!@MVcorg0i1_b^pTtmJ4nHZp!4eMZ7VJ)w8tZ9 z&mD8*yu>%fd+>kAdRGRumPf$4i8kfG{dPU?%Cl7^qjYyq6}kcX%LJxPzj>J;%>H0b zv?Xd0-Rd=_dI&6eX?5Z05+zF6xvE7%EOrvCZz`r2z%yShAmlnyBlXAy$0DlV7g28t zy;e3KvZjrA)ide05jeCbp)wI**6pq0mNqsoupiTFyt6_X)#$$R!S|jom&hrPTMAP1 zT=+8l44L5Ny>erYaE^kfWzA`4*DG*Hd0SLD({Y}03^*1<6e#X{bxa=Y2y4R)l1H)9;I=2 zCFq?H;@oWov49DxG-~1OsO@-=@&Oy(M^W)v8BqaBD5lWZTn&36Xw5>r;kxLLt}*SR zQ(#HYVZxwnpNFndNrQn3Hn6+x7UOyr5qXsy`|lb2%e-nR5+qzQNN){T<)2H8P4w;Q`dvRJ2xVE&vTP%z<(KsnPN(>{VIe- zi>+So%>O*^ET^td3&TB{42KBqpGv_lL^HF+8xU#HyKxgP&r9Gh1Jw(43FUE|1c)7war2AAvH_CNR4lKzONFw(J^94QR2~uWBK1YJW ziV1-yKw4Nu#=~>jwzGj?|F4ijHIGxNdS?qJ)lf|XukLQu;E-m=9#P>n()~DmaaD&v zjw0jel~g@X%d?U-zlIyLyg`C!sJ$RM;Y2?U^&msgY5H*n6+Wd&BK(~ivf7$Z+n-AS z_T!q)a;n5QDkF&QOSbvDvoBXx-$w42@VrA{oa45R<3T7@?@CR6sw(F;&HqisakjRW zPb~z*u_yN*o2Hy{{<43!DVzQ3lU_q!sNoE<=MdZsW{g`M%Ti?AOyoUbOp!VG>D5A{g+C!k0|Hvj+t diff --git a/img/onboarding/notification_blue@2x.png b/img/onboarding/notification_blue@2x.png deleted file mode 100644 index 0aaf19be83f28b79a86192cfcc4186938531e705..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6256 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

*z+(=~msg$(V2XvHbVE?J8va-B#Y&%O1Y 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 diff --git a/img/onboarding/notification_white.png b/img/onboarding/notification_white.png index b3458b5a9a62419e4a7cb5b22f71b602c381f6cd..d719c33999dadb3a6d7a978be09b8a726319caf5 100644 GIT binary patch literal 2474 zcmd^BX*3%O77h)yR7q1Rm14Ts!dR+8X@jaFq^MFwtgm)zEn49*efC-&A(qnGUTsn9 zONH3g-YSi~wNwzXCiWy_PXEn$XJ-D-k9+UA_nhy3_kQR8xN)YadjfnH`2YZbfT4k& z`AJMUv5!35C;oJf+vStM`_#ZX007_z|I0W4*}0-8MUDXTd%A$CA?V6U0(8|e(E$M7 zC-WaWa-9GWhI%>{Asn=s8Q%w#ORZbqG%)<#rDnzeT!FldT;HSXrT`O*K(VIpPKwRB z4erkjsCMkEb&s-7!Cz3TW230wf0O#a3(PJM<`TZEV|Mv`c3MeMYUSuwTzJ^)g0Rkl*%`_75ZkKOh@V>z0S z8mX?HY3f%ahwGj)m~6BgvpIGnVLV+D%8YUz#sE+s!%0{ZWcbr zf~%sVBICQHqdDoVerJJ&rb3^x^X6)}!psdP+7g9<=>t~-IOzpT6Kw@x;9?cQ*l{_D zf+2C&R37$w&C6sMaj}UX@f(XZiuC8>0<41qBEcj(;^8enc?SOrwD?l*%>WMr)GP4} z+Xl?YXppDMQHa^PR#vK$dp)H`%ThE2c>&dTd|YEox&;YkOS}?U9Q%C5Tz!PJ)sn^z zk}w|ZWyaih!S84(?n{UPU98bw`jXO_sc^-7-gf2BTG{B78fu@zZL0{~O&j}fZ#_FVn&2aGYeIBqym@&QIDFDtdL(|LUg>LJ<~ zS|Q6m%CUKbZ62s|R;(nv55=MK5kW8S(-~9A7w464=fAkT!}|S6Nyb7>309^jnfpt# z@t{@`abtgc>wCkPh5IaP|4H~FPW}32KL^39Gvmhg2W^KFvGYa*TKM{Gj(|^0OcP== zSN#@r8g*ExpZ)(Srl&7WwBcTqc`@w}J8%kjP?%Li>(AD>aStn~W!%4W3FaJ#h0Z#qrm-#r0b8w|Rf8NM6UN!SVt=k% z%_S&lI)aiKRP5H|k^c+rUrv+CLCduN7E6mexl=AF5qO|t z)fp2yL|S#)TwX06Y`mR^9Wk=*zP~lO|L1j*%SKy=1_42c#wWoCP6Zzoj3dT+Uyk@w$Di;|B4WmlQ{xVNQz!jHs{Sw7 e{@YEpoXWXlwFZ5BmwNJA0Sxb=^s00nqW%fuScSO& literal 2709 zcmbVOX*3(?8V+g@ON$z!BwE|hP7OsEp|xF1Ns3ZSZLQb^wS^)@Ewxq^EkaRSt+j`! zwPP7XC`whRB|;N>gII2Q@0|PVo-=pukLP>O^PTg(@B6&ZIq&(N+=3hM0!4uU0D#xX zP|u7RQ<>=J{DFBAwdxF*fy>v>+8+Sm7W@ttKz6PO^OD8i%s>ZFJt9V7k~6N_CfWeN z+f?o&Cw2e;C~u^veH+a}{fsewP8a9cB+x_VZ*AHL4RZYoY%>)7%xnJ3~Mh1CB;Tz zu2wl;&TdUcL|$8w7%Z5uwP1iF&(-w$hSTtp?aibOG9zr`Ah%Gu)`!%5ROESw;f8

~*R-iZxndwxe4X~6Dhb7oF z_h;kPks#gRzTdksjB!Y;JB!}J^zmM4;;>QuSY47w@jXePGXzIKXI8NqFrc-8iw|%9yBV;7g zJkdd#TiD<7DV}=b=^DG7%3%a00ybp*4jTpRTGCcueqdNtKBTao6LLmfSI)vw{;~+q zggJ(E;QOYpnCC2q!D*RvKXRGE>Z~TdRGx57EWUg>=GnNoe|OJ&zjuQ^5|7U4s`y=9 z5z7njbg;6j_~IJL@?uPbMPFaP%>{x067JaM$&HsbRd6|~77w~09E=v;mXxe6l+FT5oyW3RFr>XW0JO9W2U#laY2xPt^h7+?CbMN=G{Si1+H?M{;^*&q4NBCIp( z61Y0qYkKMLv#3F6%PRY18xAE^Q9`14UYI1Lh|l?Ip0EbYG+X}SBc5gAYdc6E&_%aO z_7RV?Rvb#}WZ#d}q8OVV{Zz5t&IwgD6FS{uzIpwF#xFi$fk{sPFoA?vxevH%Nbam> zWmZZOK&4;sTq+UyPM{^}g1D&NWVVN8bsmD4-#WD>G1U8OU)Ks~c{@^mM;r(u88U0A zaI#H5#8sDud)?qz>71(>Ez($bGdbFuZaZzcQ4<_6Hb07y`&16Icv>30Se{m@AGb2v z)&7mjefrLrZL0gw;pF(Xg{ihd$7iopR`F6efh;Gt*%|6l#!ZxkqLAlvv+}Rs>wtgR zJ4pXxA+)^P|IPdSjF}1HWXWQRY)w&vh7jF8_=wDjgrF1V=XuT=tWIay6Mb%}QQj>Q zVD2Q$R?p=R!R6N%$;&tDpmsWacLW>WvxRiy+hkGq!#s3M(MG}30@7U;xcql(zLc+gnH2gJ{I*UT+ zlS5DSCDRON`X+nXw&>?C*6-%;9BLs?t1rmqY~jE@7K47!AACyjJ`!4&EG!~gu~V

RO+D|b;c-dc#AF%dw^gMF&^^vTgUuM+;QGdBMk*W4*J9%j)j72ne)Hv#VFJ zJPOLkl5hnJ{e{il)@iU{kfK6lPS6J;4Z9ZOio%rkm2@5T$aOjU znVsDiYf_G{(4Tkj*W20SGi*l;uaE^Rz_PI7nBB7XKAVbmBCq@8w&WZijerX7p0Nj319+=P`I#$5h=5@{YSTH57Lh6_1pRV2& zVmMf~qBN6zsppf$J09%aXSnwCTwH^FUfiE8h*SM^ef(!~edM-HS1w3g>FQ z#~MY17sz)!>B%`$$X`3MzlXIcf-4~C+egFl9-Oip8A^1i-kJ^8!!2|q?5 zWSa8Tj1%cO$4ajM9d-ft>R39K~Ze0D8rAs<^9ZyT)w&4aJz;i)q$Z+vemTY zs$Lepuz@*V?W-AG(B&vEPfMg7QW$D2M~8TmFYIra+w^h>55hF^86%J?z|Eh(4l*{E zP!IxBr>CNr6Zxkr7RhqXf?Ht1NRF$>5>AxW$zfXgu84@zj|+FuyevuTf#zP z#ObLDbzk1I!H*x!3RVVeKpsbd#E@f0z`rW?S3M1>z-EIWoliW``xmw)=(*JP)kBe~ z&7;C&lO!$ml@Y9|$HrPsEKX3jtK#gh4IcC}yP^Npx=!qZyh7RO0WN%T=lP_|+ RpZSXf80o|Hs&yQr{t2LY8+8Bx diff --git a/img/onboarding/notification_white@2x.png b/img/onboarding/notification_white@2x.png index f1ee1212389ffc44fb0ce434df42245b9891ea1e..fd6a244f85cdc35f7172b9e4b36c50251a2d43f5 100644 GIT binary patch literal 5602 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 AIRF3v literal 6027 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 F{{qa|Q?LL4 diff --git a/img/onboarding/notification_white@3x.png b/img/onboarding/notification_white@3x.png index 4abe84597d93eb4bfd5b6efbc2fb08a6cdedcb97..31d60439c221f0a62c77f01595ea27c52f71a204 100644 GIT binary patch literal 8769 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 literal 9046 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 Preferenze" + profileSettings: "Puoi modificare la tua scelta in ogni momento da Profilo > Preferenze" cta: shareData: "Condividi" dontShareData: "Non condividere dati" @@ -522,19 +522,19 @@ profile: contextualHelpContent: !include profile/profile_preferences_notifications.md header: Notifiche push title: Personalizza le notifiche - subtitle: Scegli come ricevere aggiornamenti sui messaggi e promemoria. + subtitle: Scegli se mostrare le anteprime e ricevere promemoria, come in questo esempio. titleExistingUser: Personalizza le notifiche subtitleExistingUser: Ora puoi vedere subito chi ti ha scritto o ricevere promemoria da messaggi importanti, come in questo esempio. reminders: - title: Consenti i promemoria + title: Consenti promemoria description: Su richiesta dell'ente, ricevi notifiche push in prossimità di scadenze o quando hai dei messaggi non letti preview: - title: Mostra un'anteprima - description: Includi nella notifica push il mittente e l’oggetto del messaggio - link: Più info + title: Mostra anteprime + description: Includi nella notifica push il mittente e l’oggetto del messaggio. + link: Scopri di più bottomSheet: - title: Mostra un'anteprima - content: Se l’opzione è attiva le notifiche push mostrano il mittente e l’oggetto dei messaggi, anche quando lo schermo è bloccato. Queste informazioni vengono trattate dal gestore del tuo sistema operativo e da app di terze parti eventualmente in esecuzione. + title: Mostra anteprime + content: "Una notifica push è un messaggio che ricevi sul tuo dispositivo anche quando non usi l'app. Se attivi questa opzione, riceverai notifiche push che ti mostreranno il mittente e l'oggetto del messaggio.\nQueste informazioni sono gestite dal sistema operativo del tuo dispositivo e dalle eventuali app di terze parti che stai utilizzando." cta: Ho capito! error: Si è verificato un errore con la tua richiesta userMetadata: diff --git a/ts/components/ui/RNavScreenWithLargeHeader.tsx b/ts/components/ui/RNavScreenWithLargeHeader.tsx index b1bc4ed0b00..b2d62f8d048 100644 --- a/ts/components/ui/RNavScreenWithLargeHeader.tsx +++ b/ts/components/ui/RNavScreenWithLargeHeader.tsx @@ -136,7 +136,7 @@ export const RNavScreenWithLargeHeader = ({ {description && ( - + {description} )} diff --git a/ts/config.ts b/ts/config.ts index e764daacaa9..e6164c3f9ed 100644 --- a/ts/config.ts +++ b/ts/config.ts @@ -109,9 +109,6 @@ export const cieLoginFlowWithDevServerEnabled = // Native Login Feature Flag export const nativeLoginEnabled = Config.NATIVE_LOGIN_ENABLED === "YES"; -// Opt-in for reminder push notifications -export const remindersOptInEnabled = Config.REMINDERS_OPT_IN_ENABLED === "YES"; - export const isNewServicesEnabled = Config.NEW_SERVICES_ENABLED === "YES"; export const fetchTimeout = pipe( diff --git a/ts/features/pushNotifications/components/NotificationPreviewSample.tsx b/ts/features/pushNotifications/components/NotificationPreviewSample.tsx index 35fca896d9f..13d53bf3506 100644 --- a/ts/features/pushNotifications/components/NotificationPreviewSample.tsx +++ b/ts/features/pushNotifications/components/NotificationPreviewSample.tsx @@ -5,18 +5,16 @@ import { Icon, HSpacer, IOSpacingScale, - IOVisualCostants + Label, + IOStyles, + H6 } from "@pagopa/io-app-design-system"; -import { H4 } from "../../../components/core/typography/H4"; -import { H5 } from "../../../components/core/typography/H5"; -import customVariables from "../../../theme/variables"; import I18n from "../../../i18n"; import { TranslationKeys } from "../../../../locales/locales"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; const notificationMarginVertical: IOSpacingScale = 4; const notificationPaddingVertical: IOSpacingScale = 8; -const notificationPaddingHorizontal: IOSpacingScale = 16; +const notificationPaddingHorizontal: IOSpacingScale = 24; const styles = StyleSheet.create({ notification: { @@ -25,10 +23,9 @@ const styles = StyleSheet.create({ backgroundColor: IOColors.white, borderWidth: 1, borderColor: IOColors.bluegreyLight, - borderRadius: customVariables.borderRadiusBase, - minHeight: 72, + borderRadius: 8, + minHeight: 90, marginVertical: notificationMarginVertical, - marginHorizontal: IOVisualCostants.appMarginDefault, paddingVertical: notificationPaddingVertical, paddingHorizontal: notificationPaddingHorizontal } @@ -77,15 +74,13 @@ export const NotificationPreviewSample = ({ return ( - + -

- {I18n.t(titleKey)} -

-
+
{I18n.t(titleKey)}
+
); diff --git a/ts/features/pushNotifications/components/NotificationsPreferencesPreview.tsx b/ts/features/pushNotifications/components/NotificationsPreferencesPreview.tsx index 9890bcf567f..a4c97827aff 100644 --- a/ts/features/pushNotifications/components/NotificationsPreferencesPreview.tsx +++ b/ts/features/pushNotifications/components/NotificationsPreferencesPreview.tsx @@ -1,6 +1,5 @@ import React from "react"; import { StyleSheet, ImageBackground } from "react-native"; -import NotificationBlueBg from "../../../../img/onboarding/notification_blue.png"; import NotificationWhiteBg from "../../../../img/onboarding/notification_white.png"; import { NotificationPreviewSample } from "./NotificationPreviewSample"; @@ -14,16 +13,14 @@ const styles = StyleSheet.create({ type Props = { previewEnabled: boolean; remindersEnabled: boolean; - isFirstOnboarding: boolean; }; export const NotificationsPreferencesPreview = ({ previewEnabled, - remindersEnabled, - isFirstOnboarding + remindersEnabled }: Props) => ( diff --git a/ts/features/pushNotifications/components/ProfileNotificationsSettings.tsx b/ts/features/pushNotifications/components/ProfileNotificationsSettings.tsx new file mode 100644 index 00000000000..e1080ba4331 --- /dev/null +++ b/ts/features/pushNotifications/components/ProfileNotificationsSettings.tsx @@ -0,0 +1,86 @@ +import { + Banner, + Divider, + ListItemSwitch, + VSpacer +} from "@pagopa/io-app-design-system"; +import React from "react"; +import I18n from "../../../i18n"; +import { usePreviewMoreInfo } from "../hooks/usePreviewMoreInfo"; +import { NotificationsPreferencesPreview } from "./NotificationsPreferencesPreview"; + +type ProfileNotificationSettingsProps = { + disablePreviewSetting: boolean; + disableRemindersSetting: boolean; + isUpdatingPreviewSetting: boolean; + isUpdatingRemindersSetting: boolean; + onPreviewValueChanged?: (value: boolean) => void; + onReminderValueChanged?: (value: boolean) => void; + previewSwitchValue: boolean; + remindersSwitchValue: boolean; + showSettingsPath: boolean; +}; + +export const ProfileNotificationSettings = ({ + disablePreviewSetting, + disableRemindersSetting, + isUpdatingPreviewSetting, + isUpdatingRemindersSetting, + onPreviewValueChanged, + onReminderValueChanged, + previewSwitchValue, + remindersSwitchValue, + showSettingsPath +}: ProfileNotificationSettingsProps) => { + const { present, bottomSheet } = usePreviewMoreInfo(); + return ( + <> + + + + + + + {showSettingsPath && } + {showSettingsPath && ( + + )} + + {bottomSheet} + + ); +}; diff --git a/ts/features/pushNotifications/components/__tests__/NotificationPreviewSample.test.tsx b/ts/features/pushNotifications/components/__tests__/NotificationPreviewSample.test.tsx index 63298a77d96..949611858de 100644 --- a/ts/features/pushNotifications/components/__tests__/NotificationPreviewSample.test.tsx +++ b/ts/features/pushNotifications/components/__tests__/NotificationPreviewSample.test.tsx @@ -1,72 +1,90 @@ import * as React from "react"; +import { createStore } from "redux"; import { render } from "@testing-library/react-native"; import I18n from "../../../../i18n"; import { NotificationPreviewSample } from "../NotificationPreviewSample"; +import { appReducer } from "../../../../store/reducers"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import ROUTES from "../../../../navigation/routes"; -describe("renderNotificationPreviewSample", () => { - describe("Given enabled previews and enabled reminders", () => { - it("then title should match 'onboarding.notifications.preview.reminderOnPreviewOnTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOnMessage' key", () => { - const screen = renderNotificationPreviewSample(true, true); - expect(screen).not.toBeNull(); - - const h4Title = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOnPreviewOnTitle") - ); - expect(h4Title).not.toBeNull(); - - const h5Message = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOnPreviewOnMessage") - ); - expect(h5Message).not.toBeNull(); - }); +describe("NotificationPreviewSample", () => { + it("Given enabled previews and enabled reminders then title should match 'onboarding.notifications.preview.reminderOnPreviewOnTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOnMessage' key", () => { + const screen = renderNotificationPreviewSample(true, true); + expect(screen).not.toBeNull(); + + const h4Title = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOnPreviewOnTitle") + ); + expect(h4Title).not.toBeNull(); + + const h5Message = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOnPreviewOnMessage") + ); + expect(h5Message).not.toBeNull(); }); - describe("Given disabled previews and enabled reminders", () => { - it("then title should match 'onboarding.notifications.preview.reminderOnPreviewOffTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOffMessage' key", () => { - const screen = renderNotificationPreviewSample(false, true); - expect(screen).not.toBeNull(); - - const h4Title = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOnPreviewOffTitle") - ); - expect(h4Title).not.toBeNull(); - - const h5Message = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOnPreviewOffMessage") - ); - expect(h5Message).not.toBeNull(); - }); + + it("Given disabled previews and enabled reminders then title should match 'onboarding.notifications.preview.reminderOnPreviewOffTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOffMessage' key", () => { + const screen = renderNotificationPreviewSample(false, true); + expect(screen).not.toBeNull(); + + const h4Title = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOnPreviewOffTitle") + ); + expect(h4Title).not.toBeNull(); + + const h5Message = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOnPreviewOffMessage") + ); + expect(h5Message).not.toBeNull(); }); - describe("Given enabled previews and disabled reminders", () => { - it("then title should match 'onboarding.notifications.preview.reminderOffPreviewOnTitle' key and message should match 'onboarding.notifications.preview.reminderOffPreviewOnMessage' key", () => { - const screen = renderNotificationPreviewSample(true, false); - expect(screen).not.toBeNull(); - - const h4Title = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOffPreviewOnTitle") - ); - expect(h4Title).not.toBeNull(); - - const h5Message = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOffPreviewOnMessage") - ); - expect(h5Message).not.toBeNull(); - }); + + it("Given enabled previews and disabled reminders then title should match 'onboarding.notifications.preview.reminderOffPreviewOnTitle' key and message should match 'onboarding.notifications.preview.reminderOffPreviewOnMessage' key", () => { + const screen = renderNotificationPreviewSample(true, false); + expect(screen).not.toBeNull(); + + const h4Title = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOffPreviewOnTitle") + ); + expect(h4Title).not.toBeNull(); + + const h5Message = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOffPreviewOnMessage") + ); + expect(h5Message).not.toBeNull(); }); - describe("Given disabled previews and disabled reminders", () => { - it("then title should match 'onboarding.notifications.preview.reminderOffPreviewOffTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOffMessage' key", () => { - const screen = renderNotificationPreviewSample(false, false); - expect(screen).not.toBeNull(); - - const h4Title = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOffPreviewOffTitle") - ); - expect(h4Title).not.toBeNull(); - - const h5Message = screen.queryByText( - I18n.t("onboarding.notifications.preview.reminderOffPreviewOffMessage") - ); - expect(h5Message).not.toBeNull(); - }); + + it("Given disabled previews and disabled reminders then title should match 'onboarding.notifications.preview.reminderOffPreviewOffTitle' key and message should match 'onboarding.notifications.preview.reminderOnPreviewOffMessage' key", () => { + const screen = renderNotificationPreviewSample(false, false); + expect(screen).not.toBeNull(); + + const h4Title = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOffPreviewOffTitle") + ); + expect(h4Title).not.toBeNull(); + + const h5Message = screen.queryByText( + I18n.t("onboarding.notifications.preview.reminderOffPreviewOffMessage") + ); + expect(h5Message).not.toBeNull(); + }); + + it("should match snapshot, preview on, reminder on", () => { + const component = renderComponent(true, true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview on, reminder off", () => { + const component = renderComponent(true, false); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview off, reminder on", () => { + const component = renderComponent(false, true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview off, reminder off", () => { + const component = renderComponent(false, false); + expect(component.toJSON()).toMatchSnapshot(); }); }); @@ -82,3 +100,24 @@ const renderNotificationPreviewSample = ( ); return render(component); }; + +const renderComponent = (previewOn: boolean, reminderOn: boolean) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const store = createStore(appReducer, dsEnabledState as any); + + return renderScreenWithNavigationStoreContext( + () => ( + + ), + ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + {}, + store + ); +}; diff --git a/ts/features/pushNotifications/components/__tests__/NotificationsPreferencesPreview.test.tsx b/ts/features/pushNotifications/components/__tests__/NotificationsPreferencesPreview.test.tsx new file mode 100644 index 00000000000..cdef0455bad --- /dev/null +++ b/ts/features/pushNotifications/components/__tests__/NotificationsPreferencesPreview.test.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { createStore } from "redux"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; +import { appReducer } from "../../../../store/reducers"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import { NotificationsPreferencesPreview } from "../NotificationsPreferencesPreview"; +import ROUTES from "../../../../navigation/routes"; + +describe("NotificationsPreferencesPreview", () => { + it("should match snapshot, preview on, reminder on", () => { + const component = renderComponent(true, true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview on, reminder off", () => { + const component = renderComponent(true, false); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview off, reminder on", () => { + const component = renderComponent(false, true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, preview off, reminder off", () => { + const component = renderComponent(false, false); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); + +const renderComponent = (previewOn: boolean, reminderOn: boolean) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const store = createStore(appReducer, dsEnabledState as any); + + return renderScreenWithNavigationStoreContext( + () => ( + + ), + ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + {}, + store + ); +}; diff --git a/ts/features/pushNotifications/components/__tests__/ProfileNotificationsSettings.test.tsx b/ts/features/pushNotifications/components/__tests__/ProfileNotificationsSettings.test.tsx new file mode 100644 index 00000000000..d3455c06f4e --- /dev/null +++ b/ts/features/pushNotifications/components/__tests__/ProfileNotificationsSettings.test.tsx @@ -0,0 +1,77 @@ +/* eslint-disable no-bitwise */ +import React from "react"; +import { createStore } from "redux"; +import { ProfileNotificationSettings } from "../ProfileNotificationsSettings"; +import { appReducer } from "../../../../store/reducers"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import ROUTES from "../../../../navigation/routes"; + +describe("ProfileNotificationSettings", () => { + [...Array(128).keys()].forEach(index => { + const previewSwitchDisabled = !!(index & 0b00000001); + const previewSwitchIsUpdating = !!(index & 0b00000010); + const previewSwitchValue = !!(index & 0b00000100); + const reminderSwitchDisabled = !!(index & 0b00001000); + const reminderSwitchIsUpdating = !!(index & 0b00010000); + const reminderSwitchValue = !!(index & 0b00100000); + const showSettingsPath = !!(index & 0b01000000); + it(`should match snapshot, preview ${ + previewSwitchDisabled ? "disabled" : "enabled " + }, preview ${ + previewSwitchIsUpdating ? "updating " : "not updating" + }, preview ${previewSwitchValue ? "on " : "off"}, reminder ${ + reminderSwitchDisabled ? "disabled" : "enabled " + }, reminder ${ + reminderSwitchIsUpdating ? "updating " : "not updating" + }, reminder ${reminderSwitchValue ? "on " : "off"}, settings path ${ + showSettingsPath ? "shown" : "hidden" + }`, () => { + const component = renderComponent( + previewSwitchDisabled, + previewSwitchIsUpdating, + previewSwitchValue, + reminderSwitchDisabled, + reminderSwitchIsUpdating, + reminderSwitchValue, + showSettingsPath + ); + expect(component.toJSON()).toMatchSnapshot(); + }); + }); +}); + +const renderComponent = ( + previewSwitchDisabled: boolean, + previewSwitchIsUpdating: boolean, + previewSwitchValue: boolean, + reminderSwitchDisabled: boolean, + reminderSwitchIsUpdating: boolean, + reminderSwitchValue: boolean, + showSettingsPath: boolean +) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const store = createStore(appReducer, dsEnabledState as any); + + return renderScreenWithNavigationStoreContext( + () => ( + + ), + ROUTES.PROFILE_PREFERENCES_NOTIFICATIONS, + {}, + store + ); +}; diff --git a/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationPreviewSample.test.tsx.snap b/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationPreviewSample.test.tsx.snap new file mode 100644 index 00000000000..a91890c3a6d --- /dev/null +++ b/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationPreviewSample.test.tsx.snap @@ -0,0 +1,2157 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotificationPreviewSample should match snapshot, preview off, reminder off 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + + + + + +`; + +exports[`NotificationPreviewSample should match snapshot, preview off, reminder on 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + + + + + +`; + +exports[`NotificationPreviewSample should match snapshot, preview on, reminder off 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + + + + + +`; + +exports[`NotificationPreviewSample should match snapshot, preview on, reminder on 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + + + + + +`; diff --git a/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationsPreferencesPreview.test.tsx.snap b/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationsPreferencesPreview.test.tsx.snap new file mode 100644 index 00000000000..4041733bb9f --- /dev/null +++ b/ts/features/pushNotifications/components/__tests__/__snapshots__/NotificationsPreferencesPreview.test.tsx.snap @@ -0,0 +1,2293 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotificationsPreferencesPreview should match snapshot, preview off, reminder off 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesPreview should match snapshot, preview off, reminder on 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesPreview should match snapshot, preview on, reminder off 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesPreview should match snapshot, preview on, reminder on 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_PREFERENCES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + + + + + + +`; diff --git a/ts/features/pushNotifications/components/__tests__/__snapshots__/ProfileNotificationsSettings.test.tsx.snap b/ts/features/pushNotifications/components/__tests__/__snapshots__/ProfileNotificationsSettings.test.tsx.snap new file mode 100644 index 00000000000..8c487590b2d --- /dev/null +++ b/ts/features/pushNotifications/components/__tests__/__snapshots__/ProfileNotificationsSettings.test.tsx.snap @@ -0,0 +1,169793 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview off, reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview not updating, preview on , reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview off, reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview disabled, preview updating , preview on , reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview off, reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview not updating, preview on , reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview off, reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder disabled, reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder not updating, reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder not updating, reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder not updating, reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder not updating, reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder updating , reminder off, settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder updating , reminder off, settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder updating , reminder on , settings path hidden 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; + +exports[`ProfileNotificationSettings should match snapshot, preview enabled , preview updating , preview on , reminder enabled , reminder updating , reminder on , settings path shown 1`] = ` + + + + + + + + + + + + + + + PROFILE_PREFERENCES_NOTIFICATIONS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + +`; diff --git a/ts/features/pushNotifications/hooks/usePreviewMoreInfo.tsx b/ts/features/pushNotifications/hooks/usePreviewMoreInfo.tsx index af35860b52f..0b4118f0771 100644 --- a/ts/features/pushNotifications/hooks/usePreviewMoreInfo.tsx +++ b/ts/features/pushNotifications/hooks/usePreviewMoreInfo.tsx @@ -1,6 +1,5 @@ -import { ButtonSolid, ContentWrapper } from "@pagopa/io-app-design-system"; import * as React from "react"; -import { Body } from "../../../components/core/typography/Body"; +import { Body } from "@pagopa/io-app-design-system"; import I18n from "../../../i18n"; import { IOBottomSheetModal, @@ -13,10 +12,7 @@ import { * preferences views */ export const usePreviewMoreInfo = (): IOBottomSheetModal => { - const { present, bottomSheet, dismiss } = useIOBottomSheetModal({ - title: I18n.t( - "profile.preferences.notifications.preview.bottomSheet.title" - ), + const { bottomSheet, dismiss, present } = useIOBottomSheetModal({ component: ( {I18n.t( @@ -24,24 +20,8 @@ export const usePreviewMoreInfo = (): IOBottomSheetModal => { )} ), - snapPoint: [400], - footer: ( - - { - dismiss(); - }} - /> - - ) + snapPoint: [340], + title: I18n.t("profile.preferences.notifications.preview.bottomSheet.title") }); - - return { present, bottomSheet, dismiss }; + return { bottomSheet, dismiss, present }; }; diff --git a/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPermissionsSaga.test.tsx b/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPermissionsSaga.test.tsx deleted file mode 100644 index cb703740c2e..00000000000 --- a/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPermissionsSaga.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { CommonActions, StackActions } from "@react-navigation/native"; -import { testSaga } from "redux-saga-test-plan"; -import NavigationService from "../../../../navigation/NavigationService"; -import ROUTES from "../../../../navigation/routes"; -import { - checkNotificationPermissions, - requestNotificationPermissions -} from "../../utils"; -import { checkNotificationsPermissionsSaga } from "../checkNotificationsPermissionsSaga"; -import { notificationsInfoScreenConsent } from "../../store/actions/notifications"; - -describe("checkNotificationsPermissionsSaga", () => { - it("upon saga startup, it should ask for push notifications permission", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions); - }); - - it("if the push notifications permission was given, the saga will terminate ", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(true) - .isDone(); - }); - - it("if the push notifications permission was not given and is first onboarding, the saga will request push notification permissions", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions); - }); - - it("if the saga asks for push permissions and the user give them, the saga will terminate", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(true) - .isDone(); - }); - - it("if the saga asks for push permissions and the user does not give them, the saga navigates to the Info Screen and waits for the notificationsInfoScreenConsent action", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(false) - .call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT - }) - ) - .next() - .take(notificationsInfoScreenConsent); - }); - - it("if the saga is waiting for the notificationsInfoScreenConsent action and the latter is received, the saga terminates", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(false) - .call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT - }) - ) - .next() - .take(notificationsInfoScreenConsent) - .next() - .call(NavigationService.dispatchNavigationAction, StackActions.popToTop()) - .next() - .isDone(); - }); -}); diff --git a/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPreferencesSaga.test.tsx b/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPreferencesSaga.test.tsx new file mode 100644 index 00000000000..433e02f9e7c --- /dev/null +++ b/ts/features/pushNotifications/sagas/__tests__/checkNotificationsPreferencesSaga.test.tsx @@ -0,0 +1,205 @@ +import { CommonActions, StackActions } from "@react-navigation/native"; +import { testSaga } from "redux-saga-test-plan"; +import NavigationService from "../../../../navigation/NavigationService"; +import ROUTES from "../../../../navigation/routes"; +import { + checkNotificationPermissions, + requestNotificationPermissions +} from "../../utils"; +import { notificationsInfoScreenConsent } from "../../store/actions/notifications"; +import { checkNotificationsPreferencesSaga } from "../checkNotificationsPreferencesSaga"; +import { InitializedProfile } from "../../../../../definitions/backend/InitializedProfile"; +import { ServicesPreferencesModeEnum } from "../../../../../definitions/backend/ServicesPreferencesMode"; +import { profileUpsert } from "../../../../store/actions/profile"; +import { PushNotificationsContentTypeEnum } from "../../../../../definitions/backend/PushNotificationsContentType"; +import { ReminderStatusEnum } from "../../../../../definitions/backend/ReminderStatus"; + +const userProfile = { + service_preferences_settings: { + mode: ServicesPreferencesModeEnum.LEGACY + } +} as InitializedProfile; + +describe("checkNotificationsPreferencesSaga", () => { + it("upon saga startup, it should ask for push notifications permission", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions); + }); + + it("if the push notifications permission was given, the saga will terminate ", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions) + .next(true) + .call(NavigationService.dispatchNavigationAction, StackActions.popToTop()) + .next() + .isDone(); + }); + + it("if the push notifications permission was not given and is first onboarding, the saga will request push notification permissions", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions) + .next(false) + .call(requestNotificationPermissions); + }); + + it("if the saga asks for push permissions and the user give them, the saga will terminate", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions) + .next(false) + .call(requestNotificationPermissions) + .next(true) + .call(NavigationService.dispatchNavigationAction, StackActions.popToTop()) + .next() + .isDone(); + }); + + it("if the saga asks for push permissions and the user does not give them, the saga navigates to the Info Screen and waits for the notificationsInfoScreenConsent action", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions) + .next(false) + .call(requestNotificationPermissions) + .next(false) + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT + }) + ) + .next() + .take(notificationsInfoScreenConsent); + }); + + it("if the saga is waiting for the notificationsInfoScreenConsent action and the latter is received, the saga terminates", () => { + testSaga(checkNotificationsPreferencesSaga, userProfile) + .next() + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding: true } + }) + ) + .next() + .take(profileUpsert.success) + .next({ + payload: { + newValue: { + push_notifications_content_type: + PushNotificationsContentTypeEnum.FULL, + reminder_status: ReminderStatusEnum.ENABLED + } + } + }) + .call(checkNotificationPermissions) + .next(false) + .call(requestNotificationPermissions) + .next(false) + .call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT + }) + ) + .next() + .take(notificationsInfoScreenConsent) + .next() + .call(NavigationService.dispatchNavigationAction, StackActions.popToTop()) + .next() + .isDone(); + }); +}); diff --git a/ts/features/pushNotifications/sagas/checkNotificationsPermissionsSaga.ts b/ts/features/pushNotifications/sagas/checkNotificationsPermissionsSaga.ts deleted file mode 100644 index e94f4337c74..00000000000 --- a/ts/features/pushNotifications/sagas/checkNotificationsPermissionsSaga.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CommonActions, StackActions } from "@react-navigation/native"; -import { call, take } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import NavigationService from "../../../navigation/NavigationService"; -import ROUTES from "../../../navigation/routes"; -import { SagaCallReturnType } from "../../../types/utils"; -import { - checkNotificationPermissions, - requestNotificationPermissions -} from "../utils"; -import { notificationsInfoScreenConsent } from "../store/actions/notifications"; - -export function* checkNotificationsPermissionsSaga() { - const authorizationStatus: SagaCallReturnType< - typeof checkNotificationPermissions - > = yield* call(checkNotificationPermissions); - - if (!authorizationStatus) { - const permissionStatus: SagaCallReturnType< - typeof requestNotificationPermissions - > = yield* call(requestNotificationPermissions); - - if (permissionStatus) { - return; - } - - yield* call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT - }) - ); - - yield* take>( - notificationsInfoScreenConsent - ); - - yield* call( - NavigationService.dispatchNavigationAction, - StackActions.popToTop() - ); - } -} diff --git a/ts/features/pushNotifications/sagas/checkNotificationsPreferencesSaga.ts b/ts/features/pushNotifications/sagas/checkNotificationsPreferencesSaga.ts index dd19ba224f8..76704efcf5c 100644 --- a/ts/features/pushNotifications/sagas/checkNotificationsPreferencesSaga.ts +++ b/ts/features/pushNotifications/sagas/checkNotificationsPreferencesSaga.ts @@ -2,26 +2,24 @@ import { CommonActions, StackActions } from "@react-navigation/native"; import { call, take } from "typed-redux-saga/macro"; import { ActionType } from "typesafe-actions"; import { InitializedProfile } from "../../../../definitions/backend/InitializedProfile"; -import { remindersOptInEnabled } from "../../../config"; import NavigationService from "../../../navigation/NavigationService"; import ROUTES from "../../../navigation/routes"; import { profileUpsert } from "../../../store/actions/profile"; import { isProfileFirstOnBoarding } from "../../../store/reducers/profile"; -import { requestNotificationPermissions } from "../utils"; +import { + checkNotificationPermissions, + requestNotificationPermissions +} from "../utils"; import { trackNotificationsOptInPreviewStatus, trackNotificationsOptInReminderStatus } from "../analytics"; -import { checkNotificationsPermissionsSaga } from "./checkNotificationsPermissionsSaga"; +import { SagaCallReturnType } from "../../../types/utils"; +import { notificationsInfoScreenConsent } from "../store/actions/notifications"; export function* checkNotificationsPreferencesSaga( userProfile: InitializedProfile ) { - if (!remindersOptInEnabled) { - // the feature flag is disabled - return; - } - const isFirstOnboarding = isProfileFirstOnBoarding(userProfile); // Check if the user has already set a preference for push notification opt-in @@ -41,13 +39,12 @@ export function* checkNotificationsPreferencesSaga( } // show the opt-in screen - yield* call(() => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, - params: { isFirstOnboarding } - }) - ) + yield* call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + params: { isFirstOnboarding } + }) ); // wait for the notifications preferences to be set @@ -65,11 +62,38 @@ export function* checkNotificationsPreferencesSaga( } } + // check if the user has given system notification permissions + const authorizationStatus: SagaCallReturnType< + typeof checkNotificationPermissions + > = yield* call(checkNotificationPermissions); + + if (!authorizationStatus) { + const permissionStatus: SagaCallReturnType< + typeof requestNotificationPermissions + > = yield* call(requestNotificationPermissions); + + if (permissionStatus) { + yield* call( + NavigationService.dispatchNavigationAction, + StackActions.popToTop() + ); + return; + } + + yield* call( + NavigationService.dispatchNavigationAction, + CommonActions.navigate(ROUTES.ONBOARDING, { + screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT + }) + ); + + yield* take>( + notificationsInfoScreenConsent + ); + } + yield* call( NavigationService.dispatchNavigationAction, StackActions.popToTop() ); - - // check if the user has given system notification permissions - yield* call(checkNotificationsPermissionsSaga); } diff --git a/ts/features/pushNotifications/screens/OnboardingNotificationsInfoScreenConsent.tsx b/ts/features/pushNotifications/screens/OnboardingNotificationsInfoScreenConsent.tsx index 0bae4d568be..8201ae2404b 100644 --- a/ts/features/pushNotifications/screens/OnboardingNotificationsInfoScreenConsent.tsx +++ b/ts/features/pushNotifications/screens/OnboardingNotificationsInfoScreenConsent.tsx @@ -1,119 +1,140 @@ +import React, { useCallback, useEffect, useMemo } from "react"; +import { AppState, FlatList, View, Platform, StyleSheet } from "react-native"; import { + Body, Divider, + FooterWithButtons, + H2, + H6, + IOStyles, IOVisualCostants, + IconButton, ListItemInfo, VSpacer } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import React, { useEffect } from "react"; -import { - AppState, - FlatList, - ListRenderItemInfo, - Platform, - SafeAreaView, - View -} from "react-native"; -import { useSelector } from "react-redux"; -import { FooterStackButton } from "../../../components/buttons/FooterStackButtons"; -import { Body } from "../../../components/core/typography/Body"; -import { H1 } from "../../../components/core/typography/H1"; -import { H2 } from "../../../components/core/typography/H2"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; +import { useNavigation } from "@react-navigation/native"; import I18n from "../../../i18n"; +import { openAppSettings } from "../../../utils/appSettings"; +import { useIODispatch, useIOSelector } from "../../../store/hooks"; +import { checkNotificationPermissions } from "../utils"; +import { + pushNotificationPreviewEnabledSelector, + pushNotificationRemindersEnabledSelector +} from "../../../store/reducers/profile"; import { notificationsInfoScreenConsent } from "../store/actions/notifications"; -import { useIODispatch } from "../../../store/hooks"; -import { profilePreferencesSelector } from "../../../store/reducers/profile"; import { trackNotificationsOptInOpenSettings, trackNotificationsOptInReminderOnPermissionsOff, trackNotificationsOptInSkipSystemPermissions } from "../analytics"; -import { openAppSettings } from "../../../utils/appSettings"; -import { checkNotificationPermissions } from "../utils"; -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "onboarding.infoConsent.contextualHelpTitle", - body: "onboarding.infoConsent.contextualHelpContent" -}; - -const instructions = Platform.select>({ - ios: [ - { - icon: "systemSettingsiOS", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.ios.step1"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.ios.step1" - ) - }, - { - icon: "systemNotificationsInstructions", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.ios.step2"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.ios.step2" - ) - }, - { - icon: "systemToggleInstructions", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.ios.step3"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.ios.step3" - ) - } - ], - android: [ - { - icon: "systemSettingsAndroid", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.android.step1"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.android.step1" - ) - }, - { - icon: "systemAppsAndroid", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.android.step2"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.android.step2" - ) - }, - { - icon: "productIOAppBlueBg", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.android.step3"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.android.step3" - ) - }, - { - icon: "systemNotificationsInstructions", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.android.step4"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.android.step4" - ) - }, - { - icon: "systemToggleInstructions", - label: I18n.t("onboarding.infoConsent.instructions.label"), - value: I18n.t("onboarding.infoConsent.instructions.android.step5"), - accessibilityLabel: I18n.t( - "onboarding.infoConsent.instructions.android.step5" - ) - } - ] +const styles = StyleSheet.create({ + footer: { paddingBottom: IOStyles.footer.paddingBottom }, + header: { + alignSelf: "flex-end", + flexDirection: "row", + paddingBottom: 18, + paddingRight: IOVisualCostants.appMarginDefault, + paddingTop: 24 + }, + listContainer: { + marginHorizontal: IOVisualCostants.appMarginDefault + } }); export const OnboardingNotificationsInfoScreenConsent = () => { + const navigation = useNavigation(); const dispatch = useIODispatch(); - const optInPreferencesPot = useSelector(profilePreferencesSelector); + const remindersEnabled = useIOSelector( + pushNotificationRemindersEnabledSelector + ); + const previewEnabled = useIOSelector(pushNotificationPreviewEnabledSelector); + + const instructions = useMemo( + () => + Platform.select>({ + ios: [ + { + icon: "systemSettingsiOS", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.ios.step1"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.ios.step1" + ) + }, + { + icon: "systemNotificationsInstructions", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.ios.step2"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.ios.step2" + ) + }, + { + icon: "systemToggleInstructions", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.ios.step3"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.ios.step3" + ) + } + ], + android: [ + { + icon: "systemSettingsAndroid", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.android.step1"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.android.step1" + ) + }, + { + icon: "systemAppsAndroid", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.android.step2"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.android.step2" + ) + }, + { + icon: "productIOAppBlueBg", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.android.step3"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.android.step3" + ) + }, + { + icon: "systemNotificationsInstructions", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.android.step4"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.android.step4" + ) + }, + { + icon: "systemToggleInstructions", + label: I18n.t("onboarding.infoConsent.instructions.label"), + value: I18n.t("onboarding.infoConsent.instructions.android.step5"), + accessibilityLabel: I18n.t( + "onboarding.infoConsent.instructions.android.step5" + ) + } + ] + }), + [] + ); + + const closeModalAndScreen = useCallback(() => { + // Dismiss the modal (the check on `canGoBack` avoids + // a logged error when running tests, since in that + // case there is no screen below on the navigation stack) + if (navigation.canGoBack()) { + navigation.goBack(); + } + + dispatch(notificationsInfoScreenConsent()); + }, [dispatch, navigation]); useEffect(() => { const subscription = AppState.addEventListener( @@ -123,7 +144,7 @@ export const OnboardingNotificationsInfoScreenConsent = () => { const authorizationStatus = await checkNotificationPermissions(); if (authorizationStatus) { - dispatch(notificationsInfoScreenConsent()); + closeModalAndScreen(); } } } @@ -132,83 +153,71 @@ export const OnboardingNotificationsInfoScreenConsent = () => { return () => { subscription.remove(); }; - }, [dispatch]); + }, [closeModalAndScreen]); - const goNext = () => { + const goNext = useCallback(() => { // When this code executes, we know for sure that system notifications permissions are disabled, // otherwise the component would either have been skipped by the saga or it would have automatically // handled the given permission using the AppState listener (registered on the useEffect) trackNotificationsOptInSkipSystemPermissions(); - if (pot.isSome(optInPreferencesPot)) { - const optInPreferences = optInPreferencesPot.value; - if (optInPreferences.preview || optInPreferences.reminder) { - trackNotificationsOptInReminderOnPermissionsOff(); - } + if (remindersEnabled || previewEnabled) { + trackNotificationsOptInReminderOnPermissionsOff(); } - dispatch(notificationsInfoScreenConsent()); - }; + closeModalAndScreen(); + }, [closeModalAndScreen, previewEnabled, remindersEnabled]); - const openSettings = () => { + const openSettings = useCallback(() => { trackNotificationsOptInOpenSettings(); openAppSettings(); - }; + }, []); - const headerComponent = ( + const ListHeader = ( +

{I18n.t("onboarding.infoConsent.title")}

-

- {I18n.t("onboarding.infoConsent.title")} -

{I18n.t("onboarding.infoConsent.subTitle")} - -

+ +
{I18n.t("onboarding.infoConsent.instructions.title")} -
+

+
); - const renderItem = ({ item, index }: ListRenderItemInfo) => ( - - ); - return ( - } - > - - } - ListHeaderComponent={headerComponent} + <> + + - + ( + + )} + contentContainerStyle={styles.listContainer} + ItemSeparatorComponent={() => } + ListHeaderComponent={ListHeader} + /> + + - - +
+ ); }; diff --git a/ts/features/pushNotifications/screens/OnboardingNotificationsPreferencesScreen.tsx b/ts/features/pushNotifications/screens/OnboardingNotificationsPreferencesScreen.tsx index 6d76f99fe68..692ab9a00c6 100644 --- a/ts/features/pushNotifications/screens/OnboardingNotificationsPreferencesScreen.tsx +++ b/ts/features/pushNotifications/screens/OnboardingNotificationsPreferencesScreen.tsx @@ -1,67 +1,44 @@ +import React, { useEffect, useState } from "react"; +import { ScrollView, StyleSheet, View } from "react-native"; import { + VSpacer, ContentWrapper, - Divider, - FooterWithButtons, - IOColors, - IOToast, - IOVisualCostants, - NativeSwitch, - VSpacer + IOStyles, + ButtonSolid, + useIOToast, + H1, + Body } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as B from "fp-ts/lib/boolean"; -import { pipe } from "fp-ts/lib/function"; -import React, { memo, useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native"; -import { useSelector, useStore } from "react-redux"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { PushNotificationsContentTypeEnum } from "../../../../definitions/backend/PushNotificationsContentType"; import { ReminderStatusEnum } from "../../../../definitions/backend/ReminderStatus"; -import { PreferencesListItem } from "../../../components/PreferencesListItem"; -import { InfoBox } from "../../../components/box/InfoBox"; -import { IOBadge } from "../../../components/core/IOBadge"; -import { Body } from "../../../components/core/typography/Body"; -import { H1 } from "../../../components/core/typography/H1"; -import { H5 } from "../../../components/core/typography/H5"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; import I18n from "../../../i18n"; import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; import { OnboardingParamsList } from "../../../navigation/params/OnboardingParamsList"; import { profileUpsert } from "../../../store/actions/profile"; -import { useIODispatch } from "../../../store/hooks"; -import { profilePreferencesSelector } from "../../../store/reducers/profile"; -import customVariables from "../../../theme/variables"; -import { getFlowType } from "../../../utils/analytics"; +import { useIODispatch, useIOSelector, useIOStore } from "../../../store/hooks"; +import { + profileHasErrorSelector, + profileIsUpdatingSelector +} from "../../../store/reducers/profile"; import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; -import { usePreviewMoreInfo } from "../hooks/usePreviewMoreInfo"; +import { getFlowType } from "../../../utils/analytics"; import { trackNotificationPreferenceConfiguration, trackNotificationScreen, trackNotificationsPreferencesPreviewStatus, trackNotificationsPreferencesReminderStatus } from "../../../screens/profile/analytics"; -import { NotificationsPreferencesPreview } from "../components/NotificationsPreferencesPreview"; +import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; +import { useHardwareBackButton } from "../../../hooks/useHardwareBackButton"; +import { ProfileNotificationSettings } from "../components/ProfileNotificationsSettings"; const styles = StyleSheet.create({ - containerActions: { - backgroundColor: IOColors.white, - borderTopLeftRadius: 16, - borderTopRightRadius: 16, - paddingHorizontal: IOVisualCostants.appMarginDefault, - paddingBottom: customVariables.contentPadding - }, - containerActionsBlueBg: { - paddingTop: customVariables.contentPadding + scrollViewContainer: { + flexGrow: 1 } }); -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "onboarding.notifications.contextualHelpTitle", - body: "onboarding.notifications.contextualHelpContent" -}; - export type OnboardingNotificationsPreferencesScreenNavigationParams = { isFirstOnboarding: boolean; }; @@ -71,62 +48,16 @@ type Props = IOStackNavigationRouteProps< "ONBOARDING_NOTIFICATIONS_PREFERENCES" >; -const CustomGoBack = memo( - ({ isFirstOnboarding }: { isFirstOnboarding: boolean }) => - pipe( - isFirstOnboarding, - B.fold( - () => ( - - ), - () => null - ) - ) -); - -const Header = memo(({ isFirstOnboarding }: { isFirstOnboarding: boolean }) => { - const { title, subtitle } = pipe( - isFirstOnboarding, - B.fold( - () => ({ - title: I18n.t("profile.preferences.notifications.titleExistingUser"), - subtitle: I18n.t( - "profile.preferences.notifications.subtitleExistingUser" - ) - }), - () => ({ - title: I18n.t("profile.preferences.notifications.title"), - subtitle: I18n.t("profile.preferences.notifications.subtitle") - }) - ) - ); - - return ( - -

{title}

- - {subtitle} - - -
- ); -}); - export const OnboardingNotificationsPreferencesScreen = (props: Props) => { const dispatch = useIODispatch(); + const safeAreaInsets = useSafeAreaInsets(); + const toast = useIOToast(); const [previewEnabled, setPreviewEnabled] = useState(true); const [remindersEnabled, setRemindersEnabled] = useState(true); - const preferences = useSelector(profilePreferencesSelector); - const { present, bottomSheet } = usePreviewMoreInfo(); - - const isError = pot.isError(preferences); - const isUpdating = pot.isUpdating(preferences); + const isError = useIOSelector(profileHasErrorSelector); + const isUpdating = useIOSelector(profileIsUpdatingSelector); const { isFirstOnboarding } = props.route.params; @@ -150,11 +81,11 @@ export const OnboardingNotificationsPreferencesScreen = (props: Props) => { useEffect(() => { if (isError && !isUpdating) { - IOToast.error(I18n.t("profile.preferences.notifications.error")); + toast.error(I18n.t("profile.preferences.notifications.error")); } - }, [isError, isUpdating]); + }, [isError, isUpdating, toast]); - const store = useStore(); + const store = useIOStore(); const upsertPreferences = () => { void trackNotificationPreferenceConfiguration( @@ -175,114 +106,49 @@ export const OnboardingNotificationsPreferencesScreen = (props: Props) => { ); }; + useHardwareBackButton(() => true); + + useHeaderSecondLevel({ + canGoBack: false, + supportRequest: true, + title: "" + }); + return ( - } - headerTitle={ - isFirstOnboarding - ? I18n.t("onboarding.notifications.headerTitle") - : undefined - } - contextualHelpMarkdown={contextualHelpMarkdown} - primary={!isFirstOnboarding} - > - - -
- + + +

{I18n.t("profile.preferences.notifications.title")}

+ + {I18n.t("profile.preferences.notifications.subtitle")} + - - {isFirstOnboarding && } - - } - /> - - - } - /> - - - -
- {I18n.t( - "profile.main.privacy.shareData.screen.profileSettings" - )} -
-
-
- {/* This extra View has been added to avoid displaying the IOColors.blue - background when the ScrollView bounces */} - -
- - {bottomSheet} - - + + - + ]} + > + + + ); }; diff --git a/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsInfoScreenConsent.test.tsx b/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsInfoScreenConsent.test.tsx index dc8eb0e3777..3f9c381e7b9 100644 --- a/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsInfoScreenConsent.test.tsx +++ b/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsInfoScreenConsent.test.tsx @@ -1,14 +1,14 @@ -import configureMockStore from "redux-mock-store"; +import { createStore } from "redux"; import { AppState } from "react-native"; import { fireEvent, waitFor } from "@testing-library/react-native"; import ROUTES from "../../../../navigation/routes"; import { applicationChangeState } from "../../../../store/actions/application"; import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; import { OnboardingNotificationsInfoScreenConsent } from "../OnboardingNotificationsInfoScreenConsent"; import * as notificationsActions from "../../store/actions/notifications"; import * as notification from "../../utils"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; const checkNotificationPermissions = jest.spyOn( notification, @@ -27,10 +27,9 @@ describe("OnboardingNotificationsInfoScreenConsent", () => { }); it("Click on the button continue check that the NOTIFICATIONS_INFO_SCREEN_CONSENT action is triggered", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const { component } = renderComponentMockStore(globalState); + const screen = renderScreen(); - const continueButton = component.queryByTestId("continue-btn"); + const continueButton = screen.queryByTestId("continue-btn"); expect(continueButton).not.toBeNull(); if (continueButton) { @@ -43,11 +42,10 @@ describe("OnboardingNotificationsInfoScreenConsent", () => { checkNotificationPermissions.mockImplementation(() => Promise.resolve(true) ); - const globalState = appReducer(undefined, applicationChangeState("active")); const appStateSpy = jest.spyOn(AppState, "addEventListener"); - const { component } = renderComponentMockStore(globalState); - expect(component).not.toBeNull(); + const screen = renderScreen(); + expect(screen).not.toBeNull(); appStateSpy.mock.calls[0][1]("active"); @@ -61,11 +59,10 @@ describe("OnboardingNotificationsInfoScreenConsent", () => { checkNotificationPermissions.mockImplementation(() => Promise.resolve(false) ); - const globalState = appReducer(undefined, applicationChangeState("active")); const appStateSpy = jest.spyOn(AppState, "addEventListener"); - const { component } = renderComponentMockStore(globalState); - expect(component).not.toBeNull(); + const screen = renderScreen(); + expect(screen).not.toBeNull(); appStateSpy.mock.calls[0][1]("active"); @@ -76,11 +73,10 @@ describe("OnboardingNotificationsInfoScreenConsent", () => { }); it("If AppState is not active doesn't trigger NOTIFICATIONS_INFO_SCREEN_CONSENT action", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); const appStateSpy = jest.spyOn(AppState, "addEventListener"); - const { component } = renderComponentMockStore(globalState); - expect(component).not.toBeNull(); + const screen = renderScreen(); + expect(screen).not.toBeNull(); appStateSpy.mock.calls[0][1]("background"); expect(checkNotificationPermissions).not.toBeCalled(); @@ -98,21 +94,25 @@ describe("OnboardingNotificationsInfoScreenConsent", () => { expect(checkNotificationPermissions).not.toBeCalled(); expect(notificationsInfoScreenConsentSpy).not.toBeCalled(); }); + + it("should match snapshot", () => { + const screen = renderScreen(); + expect(screen.toJSON()).toMatchSnapshot(); + }); }); -const renderComponentMockStore = (state: GlobalState) => { - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...state - } as GlobalState); - - return { - component: renderScreenWithNavigationStoreContext( - OnboardingNotificationsInfoScreenConsent, - ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT, - {}, - store - ), +const renderScreen = () => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const store = createStore(appReducer, dsEnabledState as any); + + return renderScreenWithNavigationStoreContext( + OnboardingNotificationsInfoScreenConsent, + ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT, + {}, store - }; + ); }; diff --git a/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsPreferencesScreen.test.tsx b/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsPreferencesScreen.test.tsx index b7952ab7293..e361e26b2c2 100644 --- a/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsPreferencesScreen.test.tsx +++ b/ts/features/pushNotifications/screens/__tests__/OnboardingNotificationsPreferencesScreen.test.tsx @@ -1,122 +1,113 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import _ from "lodash"; -import configureMockStore from "redux-mock-store"; +import { act, fireEvent } from "@testing-library/react-native"; +import { createStore } from "redux"; import I18n from "../../../../i18n"; import ROUTES from "../../../../navigation/routes"; import { applicationChangeState } from "../../../../store/actions/application"; import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; - import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import mockedProfile from "../../../../__mocks__/initializedProfile"; import { OnboardingNotificationsPreferencesScreen } from "../OnboardingNotificationsPreferencesScreen"; +import { preferencesDesignSystemSetEnabled } from "../../../../store/actions/persistedPreferences"; +import { GlobalState } from "../../../../store/reducers/types"; describe("OnboardingNotificationsPreferencesScreen", () => { - describe("given an onboarded user", () => { - it("then the title should match the 'profile.preferences.notifications.titleExistingUser' key and the subtitle should match 'profile.preferences.notifications.subtitleExistingUser'", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore(globalState, false); - expect(screen).not.toBeNull(); - - const headerH1Title = screen.component.queryByText( - I18n.t("profile.preferences.notifications.titleExistingUser") - ); - expect(headerH1Title).not.toBeNull(); - - const bodySubtitle = screen.component.queryByText( - I18n.t("profile.preferences.notifications.subtitleExistingUser") - ); - expect(bodySubtitle).not.toBeNull(); - }); + it("given an user that is doing the onboarding for the first time then the title should match the 'profile.preferences.notifications.title' key and the subtitle should match 'profile.preferences.notifications.subtitle'", () => { + const screen = renderScreen(true); + expect(screen).not.toBeNull(); + + const headerH1Title = screen.queryByText( + I18n.t("profile.preferences.notifications.title") + ); + expect(headerH1Title).not.toBeNull(); + + const bodySubtitle = screen.queryByText( + I18n.t("profile.preferences.notifications.subtitle") + ); + expect(bodySubtitle).not.toBeNull(); }); - describe("given an user that is doing the onboarding for the first time", () => { - it("then the title should match the 'profile.preferences.notifications.title' key and the subtitle should match 'profile.preferences.notifications.subtitle'", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore(globalState, true); - expect(screen).not.toBeNull(); - - const headerH1Title = screen.component.queryByText( - I18n.t("profile.preferences.notifications.title") - ); - expect(headerH1Title).not.toBeNull(); - - const bodySubtitle = screen.component.queryByText( - I18n.t("profile.preferences.notifications.subtitle") - ); - expect(bodySubtitle).not.toBeNull(); + it("given an onboarded user and an undefined 'reminder_status' then the reminders switch should be on", () => { + const screen = renderScreen(false); + expect(screen).not.toBeNull(); + + const toggle = screen.getByTestId("remindersPreferenceSwitch"); + expect(toggle.props.value).toBeTruthy(); + }); + + it("given an onboarded user and an undefined 'push_notifications_content_type' then the previews switch should be on", () => { + const screen = renderScreen(false); + expect(screen).not.toBeNull(); + + const toggle = screen.getByTestId("previewsPreferenceSwitch"); + expect(toggle.props.value).toBeTruthy(); + }); + + it("should match snapshot when updating the profile", () => { + const screen = renderScreen(true, true); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot when not updating the profile", () => { + const screen = renderScreen(false); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot when not updating the profile, disabled previews switch", async () => { + const screen = renderScreen(false); + + const previewSwitch = screen.getByTestId("previewsPreferenceSwitch"); + await act(() => { + fireEvent(previewSwitch, "onValueChange", false); }); + + expect(screen.toJSON()).toMatchSnapshot(); }); + it("should match snapshot when not updating the profile, disabled reminder switch", async () => { + const screen = renderScreen(false); - describe("given an onboarded user and an undefined 'reminder_status'", () => { - it("then the reminders switch should be on", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore( - { - ...globalState, - profile: pot.some({ - ..._.omit(mockedProfile, "reminder_status") - }) - }, - false - ); - expect(screen).not.toBeNull(); - - const toggle = screen.component.getByTestId("remindersPreferenceSwitch"); - expect(toggle.props.value).toBeTruthy(); + const reminderSwitch = screen.getByTestId("remindersPreferenceSwitch"); + await act(() => { + fireEvent(reminderSwitch, "onValueChange", false); }); + + expect(screen.toJSON()).toMatchSnapshot(); }); - describe("given an onboarded user and an undefined 'push_notifications_content_type'", () => { - it("then the previews switch should be on", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore( - { - ...globalState, - profile: pot.some({ - ..._.omit(mockedProfile, "push_notifications_content_type") - }) - }, - false - ); - expect(screen).not.toBeNull(); - - const toggle = screen.component.getByTestId("previewsPreferenceSwitch"); - expect(toggle.props.value).toBeTruthy(); + it("should match snapshot when not updating the profile, disabled previews and reminder switches", async () => { + const screen = renderScreen(false); + + const previewSwitch = screen.getByTestId("previewsPreferenceSwitch"); + + const reminderSwitch = screen.getByTestId("remindersPreferenceSwitch"); + await act(() => { + fireEvent(previewSwitch, "onValueChange", false); + fireEvent(reminderSwitch, "onValueChange", false); }); + + expect(screen.toJSON()).toMatchSnapshot(); }); }); -const renderComponentMockStore = ( - state: GlobalState, - isFirstOnboarding: boolean +const renderScreen = ( + isFirstOnboarding: boolean, + isUpdatingProfile: boolean = false ) => { - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...state - } as GlobalState); - - return { - component: renderScreenWithNavigationStoreContext( - OnboardingNotificationsPreferencesScreen, - ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, - { - isFirstOnboarding - }, - store - ), + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const finalState = { + ...dsEnabledState, + profile: isUpdatingProfile ? pot.noneUpdating({}) : pot.some({}) + } as GlobalState; + const store = createStore(appReducer, finalState as any); + + return renderScreenWithNavigationStoreContext( + OnboardingNotificationsPreferencesScreen, + ROUTES.ONBOARDING_NOTIFICATIONS_PREFERENCES, + { + isFirstOnboarding + }, store - }; + ); }; diff --git a/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsInfoScreenConsent.test.tsx.snap b/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsInfoScreenConsent.test.tsx.snap new file mode 100644 index 00000000000..80caaba8af0 --- /dev/null +++ b/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsInfoScreenConsent.test.tsx.snap @@ -0,0 +1,1489 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OnboardingNotificationsInfoScreenConsent should match snapshot 1`] = ` + + + + + + + + + + + + + + + ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enable push notifications not to miss important messages! + + + + To do so, you'll have to edit the preferences in your device settings. + + + + Here's how: + + + + } + contentContainerStyle={ + Object { + "marginHorizontal": 24, + } + } + data={ + Array [ + Object { + "accessibilityLabel": "Go to \\"Settings\\"", + "icon": "systemSettingsiOS", + "label": "Step", + "value": "Go to \\"Settings\\"", + }, + Object { + "accessibilityLabel": "Select \\"Notifications\\"", + "icon": "systemNotificationsInstructions", + "label": "Step", + "value": "Select \\"Notifications\\"", + }, + Object { + "accessibilityLabel": "Enable \\"Allow Notifications\\"", + "icon": "systemToggleInstructions", + "label": "Step", + "value": "Enable \\"Allow Notifications\\"", + }, + ] + } + getItem={[Function]} + getItemCount={[Function]} + keyExtractor={[Function]} + onContentSizeChange={[Function]} + onLayout={[Function]} + onMomentumScrollBegin={[Function]} + onMomentumScrollEnd={[Function]} + onScroll={[Function]} + onScrollBeginDrag={[Function]} + onScrollEndDrag={[Function]} + removeClippedSubviews={false} + renderItem={[Function]} + scrollEventThrottle={50} + stickyHeaderIndices={Array []} + viewabilityConfigCallbackPairs={Array []} + > + + + + + Enable push notifications not to miss important messages! + + + + To do so, you'll have to edit the preferences in your device settings. + + + + Here's how: + + + + + + + + + + + + + + + + + + + + + + + + + + + Step 1 + + + Go to "Settings" + + + + + + + + + + + + + + + + + + + + + + + Step 2 + + + Select "Notifications" + + + + + + + + + + + + + + + + + + + + + Step 3 + + + Enable "Allow Notifications" + + + + + + + + + + + + + + + + + + Go to Settings + + + + + + + + + + + + + + + + + + + + +`; diff --git a/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsPreferencesScreen.test.tsx.snap b/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsPreferencesScreen.test.tsx.snap new file mode 100644 index 00000000000..aeca4d74333 --- /dev/null +++ b/ts/features/pushNotifications/screens/__tests__/__snapshots__/OnboardingNotificationsPreferencesScreen.test.tsx.snap @@ -0,0 +1,8476 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OnboardingNotificationsPreferencesScreen should match snapshot when not updating the profile 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + Continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`OnboardingNotificationsPreferencesScreen should match snapshot when not updating the profile, disabled previews and reminder switches 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + Continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`OnboardingNotificationsPreferencesScreen should match snapshot when not updating the profile, disabled previews switch 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + Continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`OnboardingNotificationsPreferencesScreen should match snapshot when not updating the profile, disabled reminder switch 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + Continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`OnboardingNotificationsPreferencesScreen should match snapshot when updating the profile 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + + + + + + + + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + + + + + + + + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + You can change this choice anytime in Profile > Preferences + + + + + + + + + + + + + + + + + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/ts/features/pushNotifications/utils/configurePushNotification.ts b/ts/features/pushNotifications/utils/configurePushNotification.ts index 08d03b30b30..4fc89303be9 100644 --- a/ts/features/pushNotifications/utils/configurePushNotification.ts +++ b/ts/features/pushNotifications/utils/configurePushNotification.ts @@ -12,11 +12,7 @@ import { Platform } from "react-native"; import PushNotification from "react-native-push-notification"; import * as pot from "@pagopa/ts-commons/lib/pot"; -import { - maximumItemsFromAPI, - pageSize, - remindersOptInEnabled -} from "../../../config"; +import { maximumItemsFromAPI, pageSize } from "../../../config"; import { loadPreviousPageMessages, reloadAllMessages @@ -153,7 +149,7 @@ function configurePushNotifications() { // Only for iOS, we need to customize push notification prompt. // We delay the push notification promt until opt-in screen // during onboarding where permission is clearly required - requestPermissions: !remindersOptInEnabled || Platform.OS !== "ios" + requestPermissions: Platform.OS !== "ios" }); } diff --git a/ts/navigation/OnboardingNavigator.tsx b/ts/navigation/OnboardingNavigator.tsx index 7ee96ffc2fa..69696235cd2 100644 --- a/ts/navigation/OnboardingNavigator.tsx +++ b/ts/navigation/OnboardingNavigator.tsx @@ -81,15 +81,21 @@ const OnboardingNavigator = () => ( component={OnboardingCompletedScreen} /> - + + + ); diff --git a/ts/navigation/ProfileNavigator.tsx b/ts/navigation/ProfileNavigator.tsx index 8c707c06109..559ce16a34c 100644 --- a/ts/navigation/ProfileNavigator.tsx +++ b/ts/navigation/ProfileNavigator.tsx @@ -1,7 +1,6 @@ import { createStackNavigator } from "@react-navigation/stack"; import * as React from "react"; import LogoutScreen from "../components/screens/LogoutScreen"; -import { remindersOptInEnabled } from "../config"; import { DesignSystemNavigator } from "../features/design-system/navigation/navigator"; import LollipopPlayground from "../features/lollipop/playgrounds/LollipopPlayground"; import CalendarsPreferencesScreen from "../screens/profile/CalendarsPreferencesScreen"; @@ -165,12 +164,10 @@ const ProfileStackNavigator = () => ( name={ROUTES.PROFILE_REMOVE_ACCOUNT_SUCCESS} component={RemoveAccountSuccess} /> - {remindersOptInEnabled && ( - - )} + ); diff --git a/ts/sagas/startup/__tests__/checkNotificationsPermissionsSaga.test.tsx b/ts/sagas/startup/__tests__/checkNotificationsPermissionsSaga.test.tsx deleted file mode 100644 index 00ef80cf310..00000000000 --- a/ts/sagas/startup/__tests__/checkNotificationsPermissionsSaga.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { CommonActions, StackActions } from "@react-navigation/native"; -import { testSaga } from "redux-saga-test-plan"; -import NavigationService from "../../../navigation/NavigationService"; -import ROUTES from "../../../navigation/routes"; -import { - checkNotificationPermissions, - requestNotificationPermissions -} from "../../../features/pushNotifications/utils"; -import { checkNotificationsPermissionsSaga } from "../../../features/pushNotifications/sagas/checkNotificationsPermissionsSaga"; -import { notificationsInfoScreenConsent } from "../../../features/pushNotifications/store/actions/notifications"; - -describe("checkNotificationsPermissionsSaga", () => { - it("upon saga startup, it should ask for push notifications permission", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions); - }); - - it("if the push notifications permission was given, the saga will terminate ", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(true) - .isDone(); - }); - - it("if the push notifications permission was not given and is first onboarding, the saga will request push notification permissions", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions); - }); - - it("if the saga asks for push permissions and the user give them, the saga will terminate", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(true) - .isDone(); - }); - - it("if the saga asks for push permissions and the user does not give them, the saga navigates to the Info Screen and waits for the notificationsInfoScreenConsent action", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(false) - .call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT - }) - ) - .next() - .take(notificationsInfoScreenConsent); - }); - - it("if the saga is waiting for the notificationsInfoScreenConsent action and the latter is received, the saga terminates", () => { - testSaga(checkNotificationsPermissionsSaga) - .next() - .call(checkNotificationPermissions) - .next(false) - .call(requestNotificationPermissions) - .next(false) - .call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate(ROUTES.ONBOARDING, { - screen: ROUTES.ONBOARDING_NOTIFICATIONS_INFO_SCREEN_CONSENT - }) - ) - .next() - .take(notificationsInfoScreenConsent) - .next() - .call(NavigationService.dispatchNavigationAction, StackActions.popToTop()) - .next() - .isDone(); - }); -}); diff --git a/ts/screens/profile/NotificationsPreferencesScreen.tsx b/ts/screens/profile/NotificationsPreferencesScreen.tsx index 598bf7fef88..00d27e7eda7 100644 --- a/ts/screens/profile/NotificationsPreferencesScreen.tsx +++ b/ts/screens/profile/NotificationsPreferencesScreen.tsx @@ -1,140 +1,122 @@ -import { ContentWrapper, IOToast } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import React, { useEffect, useState } from "react"; -import { useSelector } from "react-redux"; +import React, { useCallback, useEffect, useState } from "react"; +import { ContentWrapper, useIOToast } from "@pagopa/io-app-design-system"; import { PushNotificationsContentTypeEnum } from "../../../definitions/backend/PushNotificationsContentType"; import { ReminderStatusEnum } from "../../../definitions/backend/ReminderStatus"; -import ItemSeparatorComponent from "../../components/ItemSeparatorComponent"; -import { PreferencesListItem } from "../../components/PreferencesListItem"; -import { RemoteSwitch } from "../../components/core/selection/RemoteSwitch"; -import { ContextualHelpPropsMarkdown } from "../../components/screens/BaseScreenComponent"; import { RNavScreenWithLargeHeader } from "../../components/ui/RNavScreenWithLargeHeader"; import I18n from "../../i18n"; import { profileUpsert } from "../../store/actions/profile"; -import { useIODispatch } from "../../store/hooks"; -import { profilePreferencesSelector } from "../../store/reducers/profile"; +import { useIODispatch, useIOSelector } from "../../store/hooks"; +import { + profileHasErrorSelector, + profileIsUpdatingSelector, + pushNotificationPreviewEnabledSelector, + pushNotificationRemindersEnabledSelector +} from "../../store/reducers/profile"; import { getFlowType } from "../../utils/analytics"; import { useOnFirstRender } from "../../utils/hooks/useOnFirstRender"; -import { usePreviewMoreInfo } from "../../features/pushNotifications/hooks/usePreviewMoreInfo"; +import { ProfileNotificationSettings } from "../../features/pushNotifications/components/ProfileNotificationsSettings"; import { trackNotificationScreen, trackNotificationsPreferencesPreviewStatus, trackNotificationsPreferencesReminderStatus } from "./analytics"; -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "profile.preferences.notifications.contextualHelpTitle", - body: "profile.preferences.notifications.contextualHelpContent" -}; - export const NotificationsPreferencesScreen = () => { const dispatch = useIODispatch(); - const [isUpserting, setIsUpserting] = useState(false); - const preferences = useSelector(profilePreferencesSelector); + const toast = useIOToast(); + const [isUpsertingPreview, setIsUpsertingPreview] = useState(false); + const [isUpsertingReminders, setIsUpsertingReminders] = useState(false); - const reminder = pot.map(preferences, p => p.reminder); - const preview = pot.map(preferences, p => p.preview); - const isError = pot.isError(preferences); - const isUpdating = pot.isUpdating(preferences); - - const { present, bottomSheet } = usePreviewMoreInfo(); + const profileHasError = useIOSelector(profileHasErrorSelector); + const profileIsUpdating = useIOSelector(profileIsUpdatingSelector); + const previewSwitchIsOn = useIOSelector( + pushNotificationPreviewEnabledSelector + ); + const remindersSwitchIsOn = useIOSelector( + pushNotificationRemindersEnabledSelector + ); useOnFirstRender(() => { trackNotificationScreen(getFlowType(false, false)); }); useEffect(() => { - if (isError && isUpserting) { - IOToast.error(I18n.t("profile.preferences.notifications.error")); + if (profileHasError && (isUpsertingPreview || isUpsertingReminders)) { + toast.error(I18n.t("profile.preferences.notifications.error")); } - if (!isUpdating) { - setIsUpserting(false); + if (!profileIsUpdating) { + if (isUpsertingPreview) { + setIsUpsertingPreview(false); + } + if (isUpsertingReminders) { + setIsUpsertingReminders(false); + } } - }, [isError, isUpdating, isUpserting]); + }, [ + isUpsertingPreview, + isUpsertingReminders, + profileHasError, + profileIsUpdating, + toast + ]); - const togglePreference = (type: string, value: T) => { - setIsUpserting(true); - dispatch(profileUpsert.request({ [type]: value })); - }; + const onPreviewValueChanged = useCallback( + (isPreviewEnabled: boolean) => { + trackNotificationsPreferencesPreviewStatus( + isPreviewEnabled, + getFlowType(false, false) + ); + setIsUpsertingPreview(true); + dispatch( + profileUpsert.request({ + push_notifications_content_type: isPreviewEnabled + ? PushNotificationsContentTypeEnum.FULL + : PushNotificationsContentTypeEnum.ANONYMOUS + }) + ); + }, + [dispatch] + ); + + const onReminderValueChanged = useCallback( + (isReminderEnabled: boolean) => { + trackNotificationsPreferencesReminderStatus( + isReminderEnabled, + getFlowType(false, false) + ); + setIsUpsertingReminders(true); + dispatch( + profileUpsert.request({ + reminder_status: isReminderEnabled + ? ReminderStatusEnum.ENABLED + : ReminderStatusEnum.DISABLED + }) + ); + }, + [dispatch] + ); return ( - { - trackNotificationsPreferencesPreviewStatus( - value, - getFlowType(false, false) - ); - togglePreference( - "push_notifications_content_type", - value - ? PushNotificationsContentTypeEnum.FULL - : PushNotificationsContentTypeEnum.ANONYMOUS - ); - }} - testID="previewPreferenceSwitch" - /> - } - /> - - { - trackNotificationsPreferencesReminderStatus( - value, - getFlowType(false, false) - ); - togglePreference( - "reminder_status", - value - ? ReminderStatusEnum.ENABLED - : ReminderStatusEnum.DISABLED - ); - }} - testID="remindersPreferenceSwitch" - /> - } + - - {bottomSheet} ); }; diff --git a/ts/screens/profile/PreferencesScreen.tsx b/ts/screens/profile/PreferencesScreen.tsx index f00725e221d..cbcc9310c15 100644 --- a/ts/screens/profile/PreferencesScreen.tsx +++ b/ts/screens/profile/PreferencesScreen.tsx @@ -21,7 +21,6 @@ import { LightModalContextInterface } from "../../components/ui/LightModal"; import { RNavScreenWithLargeHeader } from "../../components/ui/RNavScreenWithLargeHeader"; -import { remindersOptInEnabled } from "../../config"; import I18n from "../../i18n"; import { AppParamsList, @@ -178,7 +177,6 @@ class PreferencesScreen extends React.Component { const preferencesNavListItems: ReadonlyArray = [ { // Notifications - condition: remindersOptInEnabled, value: I18n.t("profile.preferences.list.notifications.title"), description: I18n.t("profile.preferences.list.notifications.subtitle"), onPress: () => { diff --git a/ts/screens/profile/__test__/NotificationsPreferencesScreen.test.tsx b/ts/screens/profile/__test__/NotificationsPreferencesScreen.test.tsx index e7e20a576c0..e0ec7dcf761 100644 --- a/ts/screens/profile/__test__/NotificationsPreferencesScreen.test.tsx +++ b/ts/screens/profile/__test__/NotificationsPreferencesScreen.test.tsx @@ -1,151 +1,212 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import _ from "lodash"; -import configureMockStore from "redux-mock-store"; +import { createStore } from "redux"; import { PushNotificationsContentTypeEnum } from "../../../../definitions/backend/PushNotificationsContentType"; import { ReminderStatusEnum } from "../../../../definitions/backend/ReminderStatus"; import ROUTES from "../../../navigation/routes"; import { applicationChangeState } from "../../../store/actions/application"; import { appReducer } from "../../../store/reducers"; import { GlobalState } from "../../../store/reducers/types"; - import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import mockedProfile from "../../../__mocks__/initializedProfile"; import { NotificationsPreferencesScreen } from "../NotificationsPreferencesScreen"; +import { preferencesDesignSystemSetEnabled } from "../../../store/actions/persistedPreferences"; +import { InitializedProfile } from "../../../../definitions/backend/InitializedProfile"; describe("NotificationsPreferencesScreen", () => { - describe("given an undefined 'reminder_status'", () => { - it("then the switch should be off", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ..._.omit(mockedProfile, "reminder_status") - }) - }); - expect(screen).not.toBeNull(); + it("given an undefined 'reminder_status' then the switch should be off", () => { + const screen = renderScreen(); + expect(screen).not.toBeNull(); - const toggle = screen.component.getByTestId("remindersPreferenceSwitch"); - expect(toggle.props.value).toBeFalsy(); - }); + const toggle = screen.getByTestId("remindersPreferenceSwitch"); + expect(toggle.props.value).toBeFalsy(); }); - describe("given an ENABLED 'reminder_status'", () => { - it("then the switch should be on", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ...mockedProfile, - reminder_status: ReminderStatusEnum.ENABLED - }) - }); - expect(screen).not.toBeNull(); + it("given an ENABLED 'reminder_status'then the switch should be on", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.ENABLED); + expect(screen).not.toBeNull(); - const toggle = screen.component.getByTestId("remindersPreferenceSwitch"); - expect(toggle.props.value).toBeTruthy(); - }); + const toggle = screen.getByTestId("remindersPreferenceSwitch"); + expect(toggle.props.value).toBeTruthy(); }); - describe("given a DISABLED 'reminder_status'", () => { - it("then the switch should be off", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ...mockedProfile, - reminder_status: ReminderStatusEnum.DISABLED - }) - }); - expect(screen).not.toBeNull(); + it("given a DISABLED 'reminder_status' then the switch should be off", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.DISABLED); + expect(screen).not.toBeNull(); - const toggle = screen.component.getByTestId("remindersPreferenceSwitch"); - expect(toggle.props.value).toBeFalsy(); - }); + const toggle = screen.getByTestId("remindersPreferenceSwitch"); + expect(toggle.props.value).toBeFalsy(); }); - describe("given an undefined 'push_notifications_content_type'", () => { - it("then the switch should be off", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ..._.omit(mockedProfile, "push_notifications_content_type") - }) - }); - expect(screen).not.toBeNull(); + it("given an undefined 'push_notifications_content_type' then the switch should be off", () => { + const screen = renderScreen(); + expect(screen).not.toBeNull(); - const toggle = screen.component.getByTestId("previewPreferenceSwitch"); - expect(toggle.props.value).toBeFalsy(); - }); + const toggle = screen.getByTestId("previewsPreferenceSwitch"); + expect(toggle.props.value).toBeFalsy(); }); - describe("given a FULL 'push_notifications_content_type'", () => { - it("then the switch should be on", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ...mockedProfile, - push_notifications_content_type: PushNotificationsContentTypeEnum.FULL - }) - }); - expect(screen).not.toBeNull(); + it("given a FULL 'push_notifications_content_type' then the switch should be on", () => { + const screen = renderScreen(PushNotificationsContentTypeEnum.FULL); + expect(screen).not.toBeNull(); - const toggle = screen.component.getByTestId("previewPreferenceSwitch"); - expect(toggle.props.value).toBeTruthy(); - }); + const toggle = screen.getByTestId("previewsPreferenceSwitch"); + expect(toggle.props.value).toBeTruthy(); }); - describe("given an ANONYMOUS 'push_notifications_content_type'", () => { - it("then the switch should be off", () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const screen = renderComponentMockStore({ - ...globalState, - profile: pot.some({ - ...mockedProfile, - push_notifications_content_type: - PushNotificationsContentTypeEnum.ANONYMOUS - }) - }); - expect(screen).not.toBeNull(); + it("given an ANONYMOUS 'push_notifications_content_type' then the switch should be off", () => { + const screen = renderScreen(PushNotificationsContentTypeEnum.ANONYMOUS); + expect(screen).not.toBeNull(); + + const toggle = screen.getByTestId("previewsPreferenceSwitch"); + expect(toggle.props.value).toBeFalsy(); + }); - const toggle = screen.component.getByTestId("previewPreferenceSwitch"); - expect(toggle.props.value).toBeFalsy(); - }); + it("should match snapshot, undefined preview, undefined reminder, not updating", () => { + const screen = renderScreen(undefined, undefined, false); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, undefined preview, undefined reminder, updating", () => { + const screen = renderScreen(undefined, undefined, true); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, undefined preview, disabled reminder, not updating", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.DISABLED, false); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, undefined preview, disabled reminder, updating", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.DISABLED, true); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, undefined preview, enabled reminder, not updating", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.ENABLED, false); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, undefined preview, enabled reminder, updating", () => { + const screen = renderScreen(undefined, ReminderStatusEnum.ENABLED, true); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, undefined reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + undefined, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, undefined reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + undefined, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, disabled reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + ReminderStatusEnum.DISABLED, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, disabled reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + ReminderStatusEnum.DISABLED, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, enabled reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + ReminderStatusEnum.ENABLED, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, disabled preview, enabled reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.ANONYMOUS, + ReminderStatusEnum.ENABLED, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, undefined reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + undefined, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, undefined reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + undefined, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, disabled reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + ReminderStatusEnum.DISABLED, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, disabled reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + ReminderStatusEnum.DISABLED, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, enabled reminder, not updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + ReminderStatusEnum.ENABLED, + false + ); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot, enabled preview, enabled reminder, updating", () => { + const screen = renderScreen( + PushNotificationsContentTypeEnum.FULL, + ReminderStatusEnum.ENABLED, + true + ); + expect(screen.toJSON()).toMatchSnapshot(); }); }); -const renderComponentMockStore = (state: GlobalState) => { - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...state - } as GlobalState); +const renderScreen = ( + previewValue?: PushNotificationsContentTypeEnum, + reminderValue?: ReminderStatusEnum, + isUpdatingProfile: boolean = false +) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const dsEnabledState = appReducer( + globalState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const profile = { + push_notifications_content_type: previewValue, + reminder_status: reminderValue + } as InitializedProfile; + const finalState = { + ...dsEnabledState, + profile: isUpdatingProfile ? pot.noneUpdating(profile) : pot.some(profile) + } as GlobalState; + const store = createStore(appReducer, finalState as any); - return { - component: renderScreenWithNavigationStoreContext( - NotificationsPreferencesScreen, - ROUTES.PROFILE_PREFERENCES_NOTIFICATIONS, - {}, - store - ), + return renderScreenWithNavigationStoreContext( + NotificationsPreferencesScreen, + ROUTES.PROFILE_PREFERENCES_NOTIFICATIONS, + {}, store - }; + ); }; diff --git a/ts/screens/profile/__test__/__snapshots__/NotificationsPreferencesScreen.test.tsx.snap b/ts/screens/profile/__test__/__snapshots__/NotificationsPreferencesScreen.test.tsx.snap new file mode 100644 index 00000000000..254d7838e79 --- /dev/null +++ b/ts/screens/profile/__test__/__snapshots__/NotificationsPreferencesScreen.test.tsx.snap @@ -0,0 +1,24715 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, disabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, disabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, enabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, enabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, undefined reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, disabled preview, undefined reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, disabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, disabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, enabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, enabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a notice due soon + + + Log in to pay the notice issued by ACI + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, undefined reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, enabled preview, undefined reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + Comune di Ipazia + + + Summer camp registration is now open + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, disabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, disabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, enabled reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, enabled reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a notice due tomorrow + + + Log in to pay for it + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, undefined reminder, updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`NotificationsPreferencesScreen should match snapshot, undefined preview, undefined reminder, not updating 1`] = ` + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + Choose how to receive messages and reminders. + + + + + + + + + + + + + + + + + + + + + You have a new message + + + Open the app to read the content + + + + + + + + + + Show a preview + + + + + + + + + + Include the sender and subject of the message in the push notification + + + + More info + + + + + + + + Allow reminders + + + + + + + + + + Upon request of the sender, receive push notifications close to deadlines or when you have unread messages + + + + + + + + If this option is enabled, push notifications show the sender and subject of messages, even when the screen is locked. This information is processed by your OS manager and by third-party apps that may be running. + + + + + + + + + + + + + + + + + + + + + + + + Customize push notifications + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/ts/store/reducers/__tests__/profile.test.ts b/ts/store/reducers/__tests__/profile.test.ts index 2c26f7336df..883e8081298 100644 --- a/ts/store/reducers/__tests__/profile.test.ts +++ b/ts/store/reducers/__tests__/profile.test.ts @@ -7,10 +7,17 @@ import { isProfileEmailValidatedSelector, isProfileFirstOnBoarding, profileEmailSelector, - ProfileState + profileHasErrorSelector, + profileIsUpdatingSelector, + ProfileState, + pushNotificationPreviewEnabledSelector, + pushNotificationRemindersEnabledSelector } from "../profile"; import { ServicesPreferencesModeEnum } from "../../../../definitions/backend/ServicesPreferencesMode"; import { GlobalState } from "../types"; +import { ReminderStatusEnum } from "../../../../definitions/backend/ReminderStatus"; +import { ProfileError } from "../profileErrorType"; +import { PushNotificationsContentTypeEnum } from "../../../../definitions/backend/PushNotificationsContentType"; describe("email profile selector", () => { const potProfile: ProfileState = pot.some(mockedProfile); @@ -288,3 +295,436 @@ describe("isProfileEmailValidatedSelector", () => { expect(isProfileEmailValidated).toBe(true); }); }); + +describe("pushNotificationRemindersEnabledSelector", () => { + it("should return false for a pot.none profile", () => { + const globalState = { + profile: pot.none + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.noneLoading profile", () => { + const globalState = { + profile: pot.noneLoading + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.noneUpdating profile", () => { + const globalState = { + profile: pot.noneUpdating({ + reminder_status: ReminderStatusEnum.ENABLED + }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.noneError profile", () => { + const globalState = { + profile: pot.noneError(new ProfileError("test error")) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.some profile with undefined reminder_status", () => { + const globalState = { + profile: pot.some({ reminder_status: undefined }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.some profile with DISABLED reminder_status", () => { + const globalState = { + profile: pot.some({ reminder_status: ReminderStatusEnum.DISABLED }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return true for a pot.some profile with ENABLED reminder_status", () => { + const globalState = { + profile: pot.some({ reminder_status: ReminderStatusEnum.ENABLED }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(true); + }); + it("should return false for a pot.someLoading profile with undefined reminder_status", () => { + const globalState = { + profile: pot.someLoading({ reminder_status: undefined }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.someLoading profile with DISABLED reminder_status", () => { + const globalState = { + profile: pot.someLoading({ reminder_status: ReminderStatusEnum.DISABLED }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return true for a pot.someLoading profile with ENABLED reminder_status", () => { + const globalState = { + profile: pot.someLoading({ reminder_status: ReminderStatusEnum.ENABLED }) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(true); + }); + it("should return false for a pot.someUpdating profile with undefined reminder_status", () => { + const globalState = { + profile: pot.someUpdating( + { reminder_status: undefined }, + { reminder_status: ReminderStatusEnum.ENABLED } + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.someUpdating profile with DISABLED reminder_status", () => { + const globalState = { + profile: pot.someUpdating( + { reminder_status: ReminderStatusEnum.DISABLED }, + { reminder_status: ReminderStatusEnum.ENABLED } + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return true for a pot.someUpdating profile with ENABLED reminder_status", () => { + const globalState = { + profile: pot.someUpdating( + { reminder_status: ReminderStatusEnum.ENABLED }, + { reminder_status: ReminderStatusEnum.DISABLED } + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(true); + }); + it("should return false for a pot.someError profile with undefined reminder_status", () => { + const globalState = { + profile: pot.someError( + { reminder_status: undefined }, + new ProfileError("test error") + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return false for a pot.someError profile with DISABLED reminder_status", () => { + const globalState = { + profile: pot.someError( + { reminder_status: ReminderStatusEnum.DISABLED }, + new ProfileError("test error") + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(false); + }); + it("should return true for a pot.someError profile with ENABLED reminder_status", () => { + const globalState = { + profile: pot.someError( + { reminder_status: ReminderStatusEnum.ENABLED }, + new ProfileError("test error") + ) + } as GlobalState; + const remindersEnabled = + pushNotificationRemindersEnabledSelector(globalState); + expect(remindersEnabled).toBe(true); + }); +}); + +describe("pushNotificationPreviewEnabledSelector", () => { + it("should return false for a pot.none profile", () => { + const globalState = { + profile: pot.none + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.noneLoading profile", () => { + const globalState = { + profile: pot.noneLoading + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.noneUpdating profile", () => { + const globalState = { + profile: pot.noneUpdating({ + reminder_status: ReminderStatusEnum.ENABLED + }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.noneError profile", () => { + const globalState = { + profile: pot.noneError(new ProfileError("test error")) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.some profile with undefined push_notifications_content_type", () => { + const globalState = { + profile: pot.some({ push_notifications_content_type: undefined }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.some profile with ANONYMOUS push_notifications_content_type", () => { + const globalState = { + profile: pot.some({ + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return true for a pot.some profile with FULL push_notifications_content_type", () => { + const globalState = { + profile: pot.some({ + push_notifications_content_type: PushNotificationsContentTypeEnum.FULL + }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(true); + }); + it("should return false for a pot.someLoading profile with undefined push_notifications_content_type", () => { + const globalState = { + profile: pot.someLoading({ push_notifications_content_type: undefined }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.someLoading profile with ANONYMOUS push_notifications_content_type", () => { + const globalState = { + profile: pot.someLoading({ + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return true for a pot.someLoading profile with FULL push_notifications_content_type", () => { + const globalState = { + profile: pot.someLoading({ + push_notifications_content_type: PushNotificationsContentTypeEnum.FULL + }) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(true); + }); + it("should return false for a pot.someUpdating profile with undefined push_notifications_content_type", () => { + const globalState = { + profile: pot.someUpdating( + { push_notifications_content_type: undefined }, + { + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + } + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.someUpdating profile with ANONYMOUS push_notifications_content_type", () => { + const globalState = { + profile: pot.someUpdating( + { + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + }, + { + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + } + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return true for a pot.someUpdating profile with FULL push_notifications_content_type", () => { + const globalState = { + profile: pot.someUpdating( + { + push_notifications_content_type: PushNotificationsContentTypeEnum.FULL + }, + { + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + } + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(true); + }); + it("should return false for a pot.someError profile with undefined push_notifications_content_type", () => { + const globalState = { + profile: pot.someError( + { push_notifications_content_type: undefined }, + new ProfileError("test error") + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return false for a pot.someError profile with ANONYMOUS push_notifications_content_type", () => { + const globalState = { + profile: pot.someError( + { + push_notifications_content_type: + PushNotificationsContentTypeEnum.ANONYMOUS + }, + new ProfileError("test error") + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(false); + }); + it("should return true for a pot.someError profile with FULL push_notifications_content_type", () => { + const globalState = { + profile: pot.someError( + { + push_notifications_content_type: PushNotificationsContentTypeEnum.FULL + }, + new ProfileError("test error") + ) + } as GlobalState; + const previewEnabled = pushNotificationPreviewEnabledSelector(globalState); + expect(previewEnabled).toBe(true); + }); +}); + +describe("profileHasErrorSelector", () => { + it("should return false for a pot.none profile", () => { + const globalState = { + profile: pot.none + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return false for a pot.noneLoading profile", () => { + const globalState = { + profile: pot.noneLoading + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return false for a pot.noneUpdating profile", () => { + const globalState = { + profile: pot.noneUpdating({}) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return true for a pot.error profile", () => { + const globalState = { + profile: pot.noneError(new ProfileError("test error")) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(true); + }); + it("should return false for a pot.some profile", () => { + const globalState = { + profile: pot.some({}) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return false for a pot.someLoading profile", () => { + const globalState = { + profile: pot.someLoading({}) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return false for a pot.someUpdating profile", () => { + const globalState = { + profile: pot.someUpdating({}, {}) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(false); + }); + it("should return true for a pot.none profile", () => { + const globalState = { + profile: pot.someError({}, new ProfileError("test error")) + } as GlobalState; + const hasError = profileHasErrorSelector(globalState); + expect(hasError).toBe(true); + }); +}); + +describe("profileIsUpdatingSelector", () => { + it("should return false for a pot.none profile", () => { + const globalState = { + profile: pot.none + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); + it("should return false for a pot.noneLoading profile", () => { + const globalState = { + profile: pot.noneLoading + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); + it("should return true for a pot.noneUpdating profile", () => { + const globalState = { + profile: pot.noneUpdating({}) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(true); + }); + it("should return false for a pot.error profile", () => { + const globalState = { + profile: pot.noneError(new ProfileError("test error")) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); + it("should return false for a pot.some profile", () => { + const globalState = { + profile: pot.some({}) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); + it("should return false for a pot.someLoading profile", () => { + const globalState = { + profile: pot.someLoading({}) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); + it("should return true for a pot.someUpdating profile", () => { + const globalState = { + profile: pot.someUpdating({}, {}) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(true); + }); + it("should return false for a pot.none profile", () => { + const globalState = { + profile: pot.someError({}, new ProfileError("test error")) + } as GlobalState; + const isUpdating = profileIsUpdatingSelector(globalState); + expect(isUpdating).toBe(false); + }); +}); diff --git a/ts/store/reducers/profile.ts b/ts/store/reducers/profile.ts index c113f7c627d..7372bbcfd89 100644 --- a/ts/store/reducers/profile.ts +++ b/ts/store/reducers/profile.ts @@ -185,19 +185,32 @@ export const isEmailValidatedSelector = createSelector( isEmailValidated => isEmailValidated ); -// return preferences -export const profilePreferencesSelector = createSelector( - profileSelector, - ( - profile: ProfileState - ): pot.Pot<{ reminder: boolean; preview: boolean }, Error> => - pot.map(profile, p => ({ - reminder: p.reminder_status === ReminderStatusEnum.ENABLED, - preview: - p.push_notifications_content_type === - PushNotificationsContentTypeEnum.FULL - })) -); +export const profileHasErrorSelector = (state: GlobalState) => + pipe(state.profile, pot.isError); + +export const profileIsUpdatingSelector = (state: GlobalState) => + pipe(state.profile, pot.isUpdating); + +export const pushNotificationRemindersEnabledSelector = (state: GlobalState) => + pipe( + state.profile, + pot.toOption, + O.chainNullableK(profile => profile.reminder_status), + O.map(reminderStatus => reminderStatus === ReminderStatusEnum.ENABLED), + O.getOrElse(() => false) + ); + +export const pushNotificationPreviewEnabledSelector = (state: GlobalState) => + pipe( + state.profile, + pot.toOption, + O.chainNullableK(profile => profile.push_notifications_content_type), + O.map( + pushNotificationContentType => + pushNotificationContentType === PushNotificationsContentTypeEnum.FULL + ), + O.getOrElse(() => false) + ); // return the profile notification settings actual state or undefined if ProfileState pot is in an Error state export const profileNotificationSettingsSelector = createSelector(