diff --git a/src/keylessBackup/SignInWithEmail.test.tsx b/src/keylessBackup/SignInWithEmail.test.tsx index c2eef53385c..4a3589de4e8 100644 --- a/src/keylessBackup/SignInWithEmail.test.tsx +++ b/src/keylessBackup/SignInWithEmail.test.tsx @@ -9,9 +9,18 @@ import { KeylessBackupFlow, KeylessBackupOrigin } from 'src/keylessBackup/types' import { noHeader } from 'src/navigator/Headers' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' +import { goToNextOnboardingScreen } from 'src/onboarding/steps' import Logger from 'src/utils/Logger' import MockedNavigator from 'test/MockedNavigator' import { createMockStore } from 'test/utils' +import { mockOnboardingProps } from 'test/values' + +const mockOnboardingPropsSelector = jest.fn(() => mockOnboardingProps) +jest.mock('src/onboarding/steps', () => ({ + goToNextOnboardingScreen: jest.fn(), + getOnboardingStepValues: () => ({ step: 2, totalSteps: 3 }), + onboardingPropsSelector: () => mockOnboardingPropsSelector(), +})) const mockAuthorize = jest.fn() const mockGetCredentials = jest.fn() @@ -247,7 +256,10 @@ describe('SignInWithEmail', () => { expect(getByTestId('KeylessBackupSignInWithEmail/BottomSheet')).toBeTruthy() fireEvent.press(getByText('signInWithEmail.bottomSheet.skip')) - expect(navigate).toHaveBeenCalledWith(Screens.VerificationStartScreen) + expect(goToNextOnboardingScreen).toHaveBeenCalledWith({ + firstScreenInCurrentStep: Screens.SignInWithEmail, + onboardingProps: mockOnboardingProps, + }) expect(AppAnalytics.track).toHaveBeenCalledWith( KeylessBackupEvents.cab_sign_in_with_email_screen_skip, { diff --git a/src/navigator/types.tsx b/src/navigator/types.tsx index e9b6d2aa4a8..0a405b1917b 100644 --- a/src/navigator/types.tsx +++ b/src/navigator/types.tsx @@ -306,7 +306,7 @@ export type StackParamList = { registrationStep?: { step: number; totalSteps: number } e164Number: string countryCallingCode: string - verificationCompletionScreen: keyof StackParamList + hasOnboarded?: boolean } [Screens.OnboardingSuccessScreen]: undefined [Screens.WalletConnectRequest]: diff --git a/src/onboarding/steps.test.ts b/src/onboarding/steps.test.ts index ecd18006814..37a196241d7 100644 --- a/src/onboarding/steps.test.ts +++ b/src/onboarding/steps.test.ts @@ -1,9 +1,14 @@ import { BIOMETRY_TYPE } from 'react-native-keychain' import { initializeAccount } from 'src/account/actions' +import { KeylessBackupFlow } from 'src/keylessBackup/types' import { navigate, navigateClearingStack, popToScreen } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' -import { updateStatsigAndNavigate } from 'src/onboarding/actions' +import { + onboardingCompleted, + updateLastOnboardingScreen, + updateStatsigAndNavigate, +} from 'src/onboarding/actions' import { firstOnboardingScreen, getOnboardingStepValues, @@ -11,7 +16,6 @@ import { } from 'src/onboarding/steps' import { store } from 'src/redux/store' import { mockOnboardingProps } from 'test/values' -import { onboardingCompleted, updateLastOnboardingScreen } from 'src/onboarding/actions' jest.mock('src/redux/store', () => ({ store: { dispatch: jest.fn() } })) jest.mock('src/config', () => ({ @@ -36,7 +40,7 @@ describe('onboarding steps', () => { Screens.VerificationStartScreen, ], name: 'newUserFlowWithEverythingEnabled', - finalScreen: Screens.ChooseYourAdventure, + finalScreen: Screens.OnboardingSuccessScreen, } const newUserFlowWithEverythingDisabled = { @@ -48,7 +52,7 @@ describe('onboarding steps', () => { }, screens: [Screens.PincodeSet, Screens.ProtectWallet], name: 'newUserFlowWithEverythingDisabled', - finalScreen: Screens.ChooseYourAdventure, + finalScreen: Screens.OnboardingSuccessScreen, } const importWalletFlowEverythingEnabled = { @@ -66,7 +70,7 @@ describe('onboarding steps', () => { Screens.VerificationStartScreen, ], name: 'importWalletFlowEverythingEnabled', - finalScreen: Screens.ChooseYourAdventure, + finalScreen: Screens.OnboardingSuccessScreen, } beforeEach(() => { @@ -161,7 +165,25 @@ describe('onboarding steps', () => { ) expect(navigate).toHaveBeenCalledWith(Screens.ImportWallet) }) - it('should navigate to ProtectWallet screen if choseToRestoreAccount is false', () => { + it('should navigate to CAB screen if choseToRestoreAccount is false and cloud backup is on', () => { + goToNextOnboardingScreen({ + firstScreenInCurrentStep: Screens.EnableBiometry, + onboardingProps: { + ...onboardingProps, + choseToRestoreAccount: false, + showCloudAccountBackupSetup: true, + }, + }) + expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) + expect(mockStore.dispatch).toHaveBeenCalledWith( + updateLastOnboardingScreen(Screens.SignInWithEmail) + ) + expect(navigate).toHaveBeenCalledWith(Screens.SignInWithEmail, { + keylessBackupFlow: KeylessBackupFlow.Setup, + origin: 'Onboarding', + }) + }) + it('should navigate to ProtectWallet screen if choseToRestoreAccount is false and cloud backup is off', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.EnableBiometry, onboardingProps: { @@ -175,20 +197,36 @@ describe('onboarding steps', () => { ) expect(navigate).toHaveBeenCalledWith(Screens.ProtectWallet) }) - it('should navigate to the CYA screen', () => { + it('should navigate to Verification screen if choseToRestoreAccount is false, cloud backup is off and protect wallet is off', () => { + goToNextOnboardingScreen({ + firstScreenInCurrentStep: Screens.EnableBiometry, + onboardingProps: { + ...onboardingProps, + choseToRestoreAccount: false, + skipProtectWallet: true, + }, + }) + expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) + expect(mockStore.dispatch).toHaveBeenCalledWith( + updateLastOnboardingScreen(Screens.VerificationStartScreen) + ) + expect(navigate).toHaveBeenCalledWith(Screens.VerificationStartScreen) + }) + it('should navigate to end of onboarding if everything is disabled', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.EnableBiometry, onboardingProps: { ...onboardingProps, skipProtectWallet: true, + skipVerification: true, }, }) expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) }) }) @@ -222,10 +260,31 @@ describe('onboarding steps', () => { expect(navigate).toHaveBeenCalledWith(Screens.ImportWallet) }) - it('should navigate to ProtectWallet', () => { + it('should navigate to CAB screen if choseToRestoreAccount is false and cloud backup is on', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.PincodeSet, - onboardingProps, + onboardingProps: { + ...onboardingProps, + choseToRestoreAccount: false, + showCloudAccountBackupSetup: true, + }, + }) + expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) + expect(mockStore.dispatch).toHaveBeenCalledWith( + updateLastOnboardingScreen(Screens.SignInWithEmail) + ) + expect(navigate).toHaveBeenCalledWith(Screens.SignInWithEmail, { + keylessBackupFlow: KeylessBackupFlow.Setup, + origin: 'Onboarding', + }) + }) + it('should navigate to ProtectWallet screen if choseToRestoreAccount is false and cloud backup is off', () => { + goToNextOnboardingScreen({ + firstScreenInCurrentStep: Screens.PincodeSet, + onboardingProps: { + ...onboardingProps, + choseToRestoreAccount: false, + }, }) expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) expect(mockStore.dispatch).toHaveBeenCalledWith( @@ -233,25 +292,41 @@ describe('onboarding steps', () => { ) expect(navigate).toHaveBeenCalledWith(Screens.ProtectWallet) }) - it('should navigate to the CYA screen', () => { + it('should navigate to Verification screen if choseToRestoreAccount is false, cloud backup is off and protect wallet is off', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.PincodeSet, onboardingProps: { ...onboardingProps, + choseToRestoreAccount: false, skipProtectWallet: true, }, }) expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.VerificationStartScreen) + ) + expect(navigate).toHaveBeenCalledWith(Screens.VerificationStartScreen) + }) + it('should navigate to end of onboarding if everything is disabled', () => { + goToNextOnboardingScreen({ + firstScreenInCurrentStep: Screens.PincodeSet, + onboardingProps: { + ...onboardingProps, + skipProtectWallet: true, + skipVerification: true, + }, + }) + expect(mockStore.dispatch).toHaveBeenCalledWith(initializeAccount()) + expect(mockStore.dispatch).toHaveBeenCalledWith( + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) }) }) describe('Screens.ImportWallet', () => { - it('should navigate to the CYA screen if skipVerification is true', () => { + it('should navigate to end of onboarding if skipVerification is true', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.ImportWallet, onboardingProps: { @@ -260,13 +335,13 @@ describe('onboarding steps', () => { }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) }) - it('should also navigate to the CYA screen if numberAlreadyVerifiedCentrally is true', () => { + it('should also navigate to end of onboarding if numberAlreadyVerifiedCentrally is true', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.ImportWallet, onboardingProps: { @@ -275,10 +350,10 @@ describe('onboarding steps', () => { }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) }) it('should otherwise navigate to VerificationStartScreen', () => { @@ -294,40 +369,40 @@ describe('onboarding steps', () => { ) }) }) - describe('Screens.ImportSelect', () => { - it('should navigate to the CYA screen if skipVerification is true', () => { + describe.each([Screens.ImportSelect, Screens.SignInWithEmail])('Screens.%s', (screen) => { + it('should navigate to end of onboarding if skipVerification is true', () => { goToNextOnboardingScreen({ - firstScreenInCurrentStep: Screens.ImportSelect, + firstScreenInCurrentStep: screen, onboardingProps: { ...onboardingProps, skipVerification: true, }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) }) - it('should also navigate to the CYA screen if numberAlreadyVerifiedCentrally is true', () => { + it('should also navigate to end of onboarding if numberAlreadyVerifiedCentrally is true', () => { goToNextOnboardingScreen({ - firstScreenInCurrentStep: Screens.ImportSelect, + firstScreenInCurrentStep: screen, onboardingProps: { ...onboardingProps, numberAlreadyVerifiedCentrally: true, }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) }) it('should otherwise navigate to LinkPhoneNumber', () => { goToNextOnboardingScreen({ - firstScreenInCurrentStep: Screens.ImportSelect, + firstScreenInCurrentStep: screen, onboardingProps: { ...onboardingProps, }, @@ -340,23 +415,23 @@ describe('onboarding steps', () => { }) describe('Screens.VerificationStartScreen and Screens.LinkPhoneNumber', () => { it.each([Screens.VerificationStartScreen, Screens.LinkPhoneNumber])( - 'From %s should navigate to the Screens.ChooseYourAdventure', + 'From %s should navigate to the end of onboarding', (screen) => { goToNextOnboardingScreen({ firstScreenInCurrentStep: screen, onboardingProps: { ...onboardingProps }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) } ) }) describe('Screens.ProtectWallet', () => { - it('should navigate to the CYA screen if skipVerification is true', () => { + it('should navigate to end of onboarding if skipVerification is true', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.ProtectWallet, onboardingProps: { @@ -365,13 +440,13 @@ describe('onboarding steps', () => { }, }) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateStatsigAndNavigate(Screens.ChooseYourAdventure) + updateStatsigAndNavigate(Screens.OnboardingSuccessScreen) ) expect(mockStore.dispatch).toHaveBeenCalledWith( - updateLastOnboardingScreen(Screens.ChooseYourAdventure) + updateLastOnboardingScreen(Screens.OnboardingSuccessScreen) ) }) - it('should navigate to VerficationStartScreen if skipVerification is false and choseToRestoreAccount is false', () => { + it('should navigate to VerificationStartScreen if skipVerification is false and choseToRestoreAccount is false', () => { goToNextOnboardingScreen({ firstScreenInCurrentStep: Screens.ProtectWallet, onboardingProps: { diff --git a/src/onboarding/steps.ts b/src/onboarding/steps.ts index 7464694131b..9226eea75b4 100644 --- a/src/onboarding/steps.ts +++ b/src/onboarding/steps.ts @@ -6,22 +6,25 @@ import { recoveringFromStoreWipeSelector, } from 'src/account/selectors' import { phoneNumberVerifiedSelector, supportedBiometryTypeSelector } from 'src/app/selectors' +import { ONBOARDING_FEATURES_ENABLED } from 'src/config' import { KeylessBackupFlow, KeylessBackupOrigin } from 'src/keylessBackup/types' import * as NavigationService from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' -import { updateStatsigAndNavigate } from 'src/onboarding/actions' -import { store } from 'src/redux/store' +import { + onboardingCompleted, + updateLastOnboardingScreen, + updateStatsigAndNavigate, +} from 'src/onboarding/actions' import { ToggleableOnboardingFeatures } from 'src/onboarding/types' -import { ONBOARDING_FEATURES_ENABLED } from 'src/config' -import { onboardingCompleted, updateLastOnboardingScreen } from 'src/onboarding/actions' +import { store } from 'src/redux/store' -const END_OF_ONBOARDING_SCREENS = [Screens.TabHome, Screens.ChooseYourAdventure] +const END_OF_ONBOARDING_SCREEN: keyof StackParamList = Screens.OnboardingSuccessScreen interface NavigatorFunctions { navigate: typeof NavigationService.navigate popToScreen: typeof NavigationService.popToScreen - finishOnboarding: (screen: keyof StackParamList) => void + finishOnboarding: () => void navigateClearingStack: typeof NavigationService.navigateClearingStack } @@ -127,7 +130,7 @@ export function getOnboardingStepValues(screen: Screens, onboardingProps: Onboar const nextStepAndCount: typeof NavigationService.navigate = (...args) => { // dummy navigation function to help determine what onboarding step the user is on, without triggering side effects like actually cycling them back through the first few onboarding screens const [nextScreen] = args - if (!END_OF_ONBOARDING_SCREENS.includes(nextScreen)) { + if (nextScreen !== END_OF_ONBOARDING_SCREEN) { totalCounter++ if (currentScreen === screen) { reachedStep = true @@ -139,11 +142,11 @@ export function getOnboardingStepValues(screen: Screens, onboardingProps: Onboar currentScreen = nextScreen } - const finishOnboarding = (nextScreen: Screens) => { - currentScreen = nextScreen + const finishOnboarding = () => { + currentScreen = END_OF_ONBOARDING_SCREEN } - while (!END_OF_ONBOARDING_SCREENS.includes(currentScreen)) { + while (currentScreen !== END_OF_ONBOARDING_SCREEN) { const stepInfo = _getStepInfo({ firstScreenInStep: currentScreen, navigator: { @@ -183,10 +186,10 @@ export function goToNextOnboardingScreen({ navigator: { navigate: NavigationService.navigate, popToScreen: NavigationService.popToScreen, - finishOnboarding: (screen: keyof StackParamList) => { + finishOnboarding: () => { store.dispatch(onboardingCompleted()) - store.dispatch(updateLastOnboardingScreen(screen)) - store.dispatch(updateStatsigAndNavigate(screen)) + store.dispatch(updateLastOnboardingScreen(END_OF_ONBOARDING_SCREEN)) + store.dispatch(updateStatsigAndNavigate(END_OF_ONBOARDING_SCREEN)) }, navigateClearingStack: NavigationService.navigateClearingStack, }, @@ -225,11 +228,40 @@ function _getStepInfo({ firstScreenInStep, navigator, dispatch, props }: GetStep dispatch(updateLastOnboardingScreen(screen)) } - const navigateImportOrImportSelect = () => { - if (props.showCloudAccountBackupRestore) { - wrapNavigate(Screens.ImportSelect) + const navigateToPhoneVerificationOrFinish = ( + screen: + | Screens.LinkPhoneNumber + | Screens.VerificationStartScreen = Screens.VerificationStartScreen + ) => { + if (skipVerification || numberAlreadyVerifiedCentrally) { + finishOnboarding() } else { - wrapNavigate(Screens.ImportWallet) + // DO NOT CLEAR NAVIGATION STACK HERE - breaks restore flow on initial app open in native-stack v6 + wrapNavigate(screen) + } + } + + const biometryNext = () => { + if (choseToRestoreAccount) { + popToScreen(Screens.Welcome) + if (props.showCloudAccountBackupRestore) { + wrapNavigate(Screens.ImportSelect) + } else { + wrapNavigate(Screens.ImportWallet) + } + } else if (showCloudAccountBackupSetup) { + dispatch(initializeAccount()) + wrapNavigate(Screens.SignInWithEmail, { + keylessBackupFlow: KeylessBackupFlow.Setup, + origin: KeylessBackupOrigin.Onboarding, + }) + } else { + dispatch(initializeAccount()) + if (skipProtectWallet) { + navigateToPhoneVerificationOrFinish() + } else { + wrapNavigate(Screens.ProtectWallet) + } } } @@ -239,97 +271,29 @@ function _getStepInfo({ firstScreenInStep, navigator, dispatch, props }: GetStep next: () => { if (supportedBiometryType !== null) { wrapNavigate(Screens.EnableBiometry) - } else if (choseToRestoreAccount) { - popToScreen(Screens.Welcome) - navigateImportOrImportSelect() - } else if (showCloudAccountBackupSetup) { - dispatch(initializeAccount()) - wrapNavigate(Screens.SignInWithEmail, { - keylessBackupFlow: KeylessBackupFlow.Setup, - origin: KeylessBackupOrigin.Onboarding, - }) } else { - dispatch(initializeAccount()) - if (skipProtectWallet) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - wrapNavigate(Screens.ProtectWallet) - } + biometryNext() } }, } case Screens.EnableBiometry: return { - next: () => { - if (choseToRestoreAccount) { - navigateImportOrImportSelect() - } else if (showCloudAccountBackupSetup) { - dispatch(initializeAccount()) - wrapNavigate(Screens.SignInWithEmail, { - keylessBackupFlow: KeylessBackupFlow.Setup, - origin: KeylessBackupOrigin.Onboarding, - }) - } else { - dispatch(initializeAccount()) - if (skipProtectWallet) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - wrapNavigate(Screens.ProtectWallet) - } - } - }, + next: () => biometryNext(), } case Screens.ImportSelect: - return { - next: () => { - if (skipVerification || numberAlreadyVerifiedCentrally) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - // DO NOT CLEAR NAVIGATION STACK HERE - breaks restore flow on initial app open in native-stack v6 - wrapNavigate(Screens.LinkPhoneNumber) - } - }, - } case Screens.SignInWithEmail: return { - next: () => { - if (skipVerification || numberAlreadyVerifiedCentrally) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - // DO NOT CLEAR NAVIGATION STACK HERE - breaks restore flow on initial app open in native-stack v6 - wrapNavigate(Screens.VerificationStartScreen) - } - }, + next: () => navigateToPhoneVerificationOrFinish(Screens.LinkPhoneNumber), } case Screens.ImportWallet: + case Screens.ProtectWallet: return { - next: () => { - if (skipVerification || numberAlreadyVerifiedCentrally) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - // DO NOT CLEAR NAVIGATION STACK HERE - breaks restore flow on initial app open in native-stack v6 - wrapNavigate(Screens.VerificationStartScreen) - } - }, + next: () => navigateToPhoneVerificationOrFinish(), } case Screens.LinkPhoneNumber: case Screens.VerificationStartScreen: return { - next: () => { - // initializeAccount is called in the middle of - // the verification flow, so we don't need to call it here - finishOnboarding(Screens.ChooseYourAdventure) - }, - } - case Screens.ProtectWallet: - return { - next: () => { - if (skipVerification) { - finishOnboarding(Screens.ChooseYourAdventure) - } else { - wrapNavigate(Screens.VerificationStartScreen) - } - }, + next: () => finishOnboarding(), } default: throw new Error( diff --git a/src/onboarding/success/OnboardingSuccessScreen.tsx b/src/onboarding/success/OnboardingSuccessScreen.tsx index c1dbf562629..f9f56a33a29 100644 --- a/src/onboarding/success/OnboardingSuccessScreen.tsx +++ b/src/onboarding/success/OnboardingSuccessScreen.tsx @@ -4,24 +4,15 @@ import { Image, StyleSheet, Text, View } from 'react-native' import { background } from 'src/images/Images' import Logo from 'src/images/Logo' import { nuxNavigationOptionsNoBackButton } from 'src/navigator/Headers' +import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' -import { goToNextOnboardingScreen, onboardingPropsSelector } from 'src/onboarding/steps' -import { useSelector } from 'src/redux/hooks' import colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' function OnboardingSuccessScreen() { - const onboardingProps = useSelector(onboardingPropsSelector) useEffect(() => { - const timeout = setTimeout( - () => - goToNextOnboardingScreen({ - firstScreenInCurrentStep: Screens.VerificationStartScreen, - onboardingProps, - }), - 3000 - ) + const timeout = setTimeout(() => navigate(Screens.ChooseYourAdventure), 3000) return () => clearTimeout(timeout) }, []) diff --git a/src/verify/VerificationCodeInputScreen.test.tsx b/src/verify/VerificationCodeInputScreen.test.tsx index 03c5554bb05..f75c7cd4f1d 100644 --- a/src/verify/VerificationCodeInputScreen.test.tsx +++ b/src/verify/VerificationCodeInputScreen.test.tsx @@ -7,13 +7,21 @@ import SmsRetriever from 'react-native-sms-retriever' import { Provider } from 'react-redux' import { showError } from 'src/alert/actions' import { ErrorMessages } from 'src/app/ErrorMessages' -import { navigate } from 'src/navigator/NavigationService' +import { navigate, popToScreen } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' +import { goToNextOnboardingScreen } from 'src/onboarding/steps' import { sleep } from 'src/utils/sleep' import VerificationCodeInputScreen from 'src/verify/VerificationCodeInputScreen' import networkConfig from 'src/web3/networkConfig' import MockedNavigator from 'test/MockedNavigator' -import { createMockStore } from 'test/utils' +import { createMockStore, getMockStackScreenProps } from 'test/utils' +import { mockOnboardingProps } from 'test/values' + +const mockOnboardingPropsSelector = jest.fn(() => mockOnboardingProps) +jest.mock('src/onboarding/steps', () => ({ + goToNextOnboardingScreen: jest.fn(), + onboardingPropsSelector: () => mockOnboardingPropsSelector(), +})) const mockFetch = fetch as FetchMock @@ -37,15 +45,19 @@ const store = createMockStore({ }, }) -const renderComponent = () => +const renderComponent = ({ + hasOnboarded = false, +}: { + hasOnboarded?: boolean +} = {}) => render( @@ -94,7 +106,7 @@ describe('VerificationCodeInputScreen', () => { ) }) - it('verifies the sms code', async () => { + it('verifies the sms code and navigates to next onboarding screen', async () => { mockFetch.mockResponseOnce(JSON.stringify({ data: { verificationId: 'someId' } }), { status: 200, }) @@ -122,7 +134,99 @@ describe('VerificationCodeInputScreen', () => { await act(() => { jest.runOnlyPendingTimers() }) - expect(navigate).toHaveBeenCalledWith(Screens.OnboardingSuccessScreen) + expect(goToNextOnboardingScreen).toHaveBeenCalledWith({ + firstScreenInCurrentStep: Screens.VerificationStartScreen, + onboardingProps: mockOnboardingProps, + }) + expect(navigate).not.toHaveBeenCalled() + expect(popToScreen).not.toHaveBeenCalled() + }) + + it('verifies the sms code and navigates to home if not in onboarding and no previous routes found', async () => { + mockFetch.mockResponseOnce(JSON.stringify({ data: { verificationId: 'someId' } }), { + status: 200, + }) + mockFetch.mockResponseOnce(JSON.stringify({ message: 'OK' }), { + status: 200, + }) + + const { getByTestId } = renderComponent({ hasOnboarded: true }) + + await act(() => { + fireEvent.changeText(getByTestId('PhoneVerificationCode'), '123456') + }) + + await waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(2)) + expect(mockFetch).toHaveBeenNthCalledWith(2, `${networkConfig.verifySmsCodeUrl}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: `${networkConfig.authHeaderIssuer} 0xabc:someSignedMessage`, + }, + body: '{"phoneNumber":"+31619123456","verificationId":"someId","smsCode":"123456","clientPlatform":"android","clientVersion":"0.0.1"}', + }) + expect(getByTestId('PhoneVerificationCode/CheckIcon')).toBeTruthy() + + await act(() => { + jest.runOnlyPendingTimers() + }) + expect(navigate).toHaveBeenCalledWith(Screens.TabHome) + expect(popToScreen).not.toHaveBeenCalled() + expect(goToNextOnboardingScreen).not.toHaveBeenCalled() + }) + + it('verifies the sms code and navigates to previous route if not in onboarding', async () => { + mockFetch.mockResponseOnce(JSON.stringify({ data: { verificationId: 'someId' } }), { + status: 200, + }) + mockFetch.mockResponseOnce(JSON.stringify({ message: 'OK' }), { + status: 200, + }) + + const screenProps = getMockStackScreenProps(Screens.VerificationCodeInputScreen, { + countryCallingCode: '+31', + e164Number, + hasOnboarded: true, + }) + + jest.mocked(screenProps.navigation).getState = jest.fn( + () => + ({ + routes: [ + { name: Screens.ProfileSubmenu }, + { name: Screens.VerificationStartScreen }, + { name: Screens.VerificationCodeInputScreen }, + ], + }) as any + ) + + const { getByTestId } = render( + + + + ) + + await act(() => { + fireEvent.changeText(getByTestId('PhoneVerificationCode'), '123456') + }) + + await waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(2)) + expect(mockFetch).toHaveBeenNthCalledWith(2, `${networkConfig.verifySmsCodeUrl}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: `${networkConfig.authHeaderIssuer} 0xabc:someSignedMessage`, + }, + body: '{"phoneNumber":"+31619123456","verificationId":"someId","smsCode":"123456","clientPlatform":"android","clientVersion":"0.0.1"}', + }) + expect(getByTestId('PhoneVerificationCode/CheckIcon')).toBeTruthy() + + await act(() => { + jest.runOnlyPendingTimers() + }) + expect(popToScreen).toHaveBeenCalledWith(Screens.ProfileSubmenu) + expect(navigate).not.toHaveBeenCalled() + expect(goToNextOnboardingScreen).not.toHaveBeenCalled() }) it('waits for the verificationId to be captured before verifying sms', async () => { @@ -195,7 +299,10 @@ describe('VerificationCodeInputScreen', () => { await act(() => { jest.runOnlyPendingTimers() }) - expect(navigate).toHaveBeenCalledWith(Screens.OnboardingSuccessScreen) + expect(goToNextOnboardingScreen).toHaveBeenCalledWith({ + firstScreenInCurrentStep: Screens.VerificationStartScreen, + onboardingProps: mockOnboardingProps, + }) }) it('handles when phone number already verified', async () => { @@ -218,7 +325,12 @@ describe('VerificationCodeInputScreen', () => { await act(() => { jest.runOnlyPendingTimers() }) - await waitFor(() => expect(navigate).toHaveBeenCalledWith(Screens.OnboardingSuccessScreen)) + await waitFor(() => + expect(goToNextOnboardingScreen).toHaveBeenCalledWith({ + firstScreenInCurrentStep: Screens.VerificationStartScreen, + onboardingProps: mockOnboardingProps, + }) + ) }) it('shows error in verifying sms code', async () => { diff --git a/src/verify/VerificationCodeInputScreen.tsx b/src/verify/VerificationCodeInputScreen.tsx index 0d3c561c749..4cdf8252f64 100644 --- a/src/verify/VerificationCodeInputScreen.tsx +++ b/src/verify/VerificationCodeInputScreen.tsx @@ -9,10 +9,12 @@ import { PhoneVerificationEvents } from 'src/analytics/Events' import BackButton from 'src/components/BackButton' import InfoBottomSheet from 'src/components/InfoBottomSheet' import { HeaderTitleWithSubtitle } from 'src/navigator/Headers' -import { navigate } from 'src/navigator/NavigationService' +import { navigate, popToScreen } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { TopBarTextButton } from 'src/navigator/TopBarButton' import { StackParamList } from 'src/navigator/types' +import { goToNextOnboardingScreen, onboardingPropsSelector } from 'src/onboarding/steps' +import { useSelector } from 'src/redux/hooks' import colors from 'src/styles/colors' import { useVerifyPhoneNumber } from 'src/verify/hooks' import VerificationCodeInput from 'src/verify/VerificationCodeInput' @@ -21,7 +23,6 @@ function VerificationCodeInputScreen({ route, navigation, }: NativeStackScreenProps) { - const nextScreen = route.params.verificationCompletionScreen const [showHelpDialog, setShowHelpDialog] = useState(false) const { t } = useTranslation() @@ -30,6 +31,7 @@ function VerificationCodeInputScreen({ route.params.e164Number, route.params.countryCallingCode ) + const onboardingProps = useSelector(onboardingPropsSelector) const onResendSms = () => { AppAnalytics.track(PhoneVerificationEvents.phone_verification_resend_message) @@ -83,7 +85,24 @@ function VerificationCodeInputScreen({ setSmsCode={setSmsCode} onResendSms={onResendSms} onSuccess={() => { - navigate(nextScreen) + if (route.params.hasOnboarded) { + const routes = navigation.getState().routes + // if not from onboarding, go back to the screen where phone + // verification was started, which is 2 screens back. If no screen + // is found, go to home screen + const prevRoute = routes[routes.length - 3] + if (prevRoute?.name) { + popToScreen(prevRoute.name) + } else { + navigate(Screens.TabHome) + } + } else { + // if onboarding, continue with the next onboarding screen + goToNextOnboardingScreen({ + firstScreenInCurrentStep: Screens.VerificationStartScreen, + onboardingProps, + }) + } }} containerStyle={{ marginTop: headerHeight }} /> diff --git a/src/verify/VerificationStartScreen.test.tsx b/src/verify/VerificationStartScreen.test.tsx index ccbd30713de..2d7d30625c3 100644 --- a/src/verify/VerificationStartScreen.test.tsx +++ b/src/verify/VerificationStartScreen.test.tsx @@ -166,7 +166,7 @@ describe('VerificationStartScreen', () => { countryCallingCode: '+31', e164Number: '+31619123456', registrationStep: { step: 3, totalSteps: 3 }, - verificationCompletionScreen: 'OnboardingSuccessScreen', + hasOnboarded: false, }) }) @@ -189,7 +189,7 @@ describe('VerificationStartScreen', () => { countryCallingCode: '+31', e164Number: '+31619123456', registrationStep: undefined, - verificationCompletionScreen: 'OnboardingSuccessScreen', + hasOnboarded: false, }) }) @@ -212,7 +212,7 @@ describe('VerificationStartScreen', () => { countryCallingCode: '+31', e164Number: '+31619123456', registrationStep: undefined, - verificationCompletionScreen: 'TabHome', + hasOnboarded: true, }) }) }) diff --git a/src/verify/VerificationStartScreen.tsx b/src/verify/VerificationStartScreen.tsx index 3f494fb80e4..4630a608fc4 100644 --- a/src/verify/VerificationStartScreen.tsx +++ b/src/verify/VerificationStartScreen.tsx @@ -82,20 +82,11 @@ function VerificationStartScreen({ countryCallingCode: country?.countryCallingCode || '', }) - const routes = navigation.getState().routes - const prevRoute = routes[routes.length - 2] // -2 because -1 is the current route - // Usually it makes sense to navigate the user back to where they launched - // the verification flow after they complete it, but during onboarding we - // want to navigate to the next step. - const verificationCompletionScreen = !route.params?.hasOnboarded - ? Screens.OnboardingSuccessScreen - : (prevRoute?.name ?? Screens.TabHome) - navigate(Screens.VerificationCodeInputScreen, { registrationStep: showSteps ? { step, totalSteps } : undefined, e164Number: phoneNumberInfo.e164Number, countryCallingCode: country?.countryCallingCode || '', - verificationCompletionScreen, + hasOnboarded: route.params?.hasOnboarded, }) }