diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index 5ed706010c..2e3608a206 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -69,7 +69,7 @@ const getStories = () => { "./app/screens/earns-screen/earns-sections.stories.tsx": require("../app/screens/earns-screen/earns-sections.stories.tsx"), "./app/screens/earns-screen/section-completed.stories.tsx": require("../app/screens/earns-screen/section-completed.stories.tsx"), "./app/screens/email-login-screen/email-login-flow.stories.tsx": require("../app/screens/email-login-screen/email-login-flow.stories.tsx"), - "./app/screens/email-login-screen/email-login-validation.stories.tsx": require("../app/screens/email-login-screen/email-login-validation.stories.tsx"), + "./app/screens/email-login-screen/email-login-validate.stories.tsx": require("../app/screens/email-login-screen/email-login-validate.stories.tsx"), "./app/screens/email-registration-screen/email-registration-initiate.stories.tsx": require("../app/screens/email-registration-screen/email-registration-initiate.stories.tsx"), "./app/screens/email-registration-screen/email-registration-validate.stories.tsx": require("../app/screens/email-registration-screen/email-registration-validate.stories.tsx"), "./app/screens/error-screen/error-screen.stories.tsx": require("../app/screens/error-screen/error-screen.stories.tsx"), diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index 8a7ba3c262..b8503a170b 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -429,7 +429,7 @@ const en: BaseTranslation = { logInCreateAccount: "Log in / create account", createAccount: "Create new account", exploreWallet: "Explore wallet", - loginBackInWith: "Login back in with", + logBackInWith: "Log back in with", headline: "Wallet powered by Galoy", startTrialAccount: "Start with a trial account", iUnderstand: "I understand, continue", @@ -796,13 +796,13 @@ const en: BaseTranslation = { header: "To confirm your email address, enter the code we just sent you on {email: string}", success: "Email {email: string} confirmed successfully", }, - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { title: "Login via email", header: "Enter your email address, and we'll send you an access code.", invalidEmail: "Invalid email address. Are you sure you entered the right email?", send: "Send code", }, - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { title: "Code confirmation", header: "If there is an account attached to {email: string}, you should have received 6 digits code to enter below.\n\nIf you are not receiving anything, it's probably either because this is not the right email, the email is in your spam folder.", success: "Email {email: string} confirmed successfully", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 18ee1a8fb4..a0df62cbe6 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -1399,7 +1399,7 @@ type RootTranslation = { /** * L​o​g​i​n​ ​b​a​c​k​ ​i​n​ ​w​i​t​h */ - loginBackInWith: string + logBackInWith: string /** * W​a​l​l​e​t​ ​p​o​w​e​r​e​d​ ​b​y​ ​G​a​l​o​y */ @@ -2684,7 +2684,7 @@ type RootTranslation = { */ success: RequiredParams<'email'> } - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { /** * L​o​g​i​n​ ​v​i​a​ ​e​m​a​i​l */ @@ -2702,7 +2702,7 @@ type RootTranslation = { */ send: string } - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { /** * C​o​d​e​ ​c​o​n​f​i​r​m​a​t​i​o​n */ @@ -4663,7 +4663,7 @@ export type TranslationFunctions = { /** * Login back in with */ - loginBackInWith: () => LocalizedString + logBackInWith: () => LocalizedString /** * Wallet powered by Galoy */ @@ -5901,7 +5901,7 @@ export type TranslationFunctions = { */ success: (arg: { email: string }) => LocalizedString } - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { /** * Login via email */ @@ -5919,7 +5919,7 @@ export type TranslationFunctions = { */ send: () => LocalizedString } - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { /** * Code confirmation */ diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index 834d1aec0e..5d3c4af11f 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -62,8 +62,8 @@ import { EmailRegistrationValidateScreen, } from "@app/screens/email-registration-screen" import { - EmailLoginInputScreen, - EmailLoginValidationScreen, + EmailLoginInitiateScreen, + EmailLoginValidateScreen, } from "@app/screens/email-login-screen" const useStyles = makeStyles(({ colors }) => ({ @@ -337,19 +337,19 @@ export const RootStack = () => { }} /> diff --git a/app/navigation/stack-param-lists.ts b/app/navigation/stack-param-lists.ts index 4492eccf23..3ed6b38653 100644 --- a/app/navigation/stack-param-lists.ts +++ b/app/navigation/stack-param-lists.ts @@ -79,8 +79,8 @@ export type RootStackParamList = { transactionLimitsScreen: undefined emailRegistrationInitiate: undefined emailRegistrationValidate: { email: string; emailRegistrationId: string } - emailLoginInput: undefined - emailLoginValidation: { email: string; emailLoginId: string } + emailLoginInitiate: undefined + emailLoginValidate: { email: string; emailLoginId: string } } export type ContactStackParamList = { diff --git a/app/screens/email-login-screen/email-login-flow.stories.tsx b/app/screens/email-login-screen/email-login-flow.stories.tsx index 3ec4a08c59..39c687163a 100644 --- a/app/screens/email-login-screen/email-login-flow.stories.tsx +++ b/app/screens/email-login-screen/email-login-flow.stories.tsx @@ -7,7 +7,7 @@ import { CaptchaRequestAuthCodeDocument, UserEmailRegistrationInitiateDocument, } from "../../graphql/generated" -import { EmailLoginInputScreen } from "./email-login-input" +import { EmailLoginInitiateScreen } from "./email-login-initiate" const mocks = [ { @@ -79,7 +79,7 @@ const mocks = [ export default { title: "EmailLoginFlow", - component: EmailLoginInputScreen, + component: EmailLoginInitiateScreen, decorators: [ (Story) => ( @@ -89,4 +89,4 @@ export default { ], } -export const Default = () => +export const Default = () => diff --git a/app/screens/email-login-screen/email-login-input.tsx b/app/screens/email-login-screen/email-login-initiate.tsx similarity index 87% rename from app/screens/email-login-screen/email-login-input.tsx rename to app/screens/email-login-screen/email-login-initiate.tsx index 12326616e3..473b8f489e 100644 --- a/app/screens/email-login-screen/email-login-input.tsx +++ b/app/screens/email-login-screen/email-login-initiate.tsx @@ -11,8 +11,8 @@ import { View } from "react-native" import validator from "validator" import { Screen } from "../../components/screen" import { useAppConfig } from "@app/hooks" +import { testProps } from "@app/utils/testProps" -const PLACEHOLDER_EMAIL = "hal@finney.org" const useStyles = makeStyles(({ colors }) => ({ screenStyle: { padding: 20, @@ -47,7 +47,7 @@ const useStyles = makeStyles(({ colors }) => ({ }, })) -export const EmailLoginInputScreen: React.FC = () => { +export const EmailLoginInitiateScreen: React.FC = () => { const styles = useStyles() const { appConfig: { @@ -58,7 +58,7 @@ export const EmailLoginInputScreen: React.FC = () => { const urlEmailCodeRequest = `${authUrl}/auth/email/code` const navigation = - useNavigation>() + useNavigation>() const [emailInput, setEmailInput] = React.useState("") const [errorMessage, setErrorMessage] = React.useState("") @@ -73,7 +73,7 @@ export const EmailLoginInputScreen: React.FC = () => { const submit = async () => { if (!validator.isEmail(emailInput)) { - setErrorMessage(LL.EmailLoginInputScreen.invalidEmail()) + setErrorMessage(LL.EmailLoginInitiateScreen.invalidEmail()) return } @@ -92,7 +92,8 @@ export const EmailLoginInputScreen: React.FC = () => { const emailLoginId = res.data.result if (emailLoginId) { - navigation.navigate("emailLoginValidation", { emailLoginId, email: emailInput }) + console.log({ emailLoginId }) + navigation.navigate("emailLoginValidate", { emailLoginId, email: emailInput }) } else { console.warn("no flow returned") } @@ -124,13 +125,14 @@ export const EmailLoginInputScreen: React.FC = () => { > - {LL.EmailLoginInputScreen.header()} + {LL.EmailLoginInitiateScreen.header()} { ( @@ -26,6 +26,6 @@ export default { ), ], -} as Meta +} as Meta -export const Main = () => +export const Main = () => diff --git a/app/screens/email-login-screen/email-login-validation.tsx b/app/screens/email-login-screen/email-login-validate.tsx similarity index 91% rename from app/screens/email-login-screen/email-login-validation.tsx rename to app/screens/email-login-screen/email-login-validate.tsx index 0a2af6a1de..e10b99c1a9 100644 --- a/app/screens/email-login-screen/email-login-validation.tsx +++ b/app/screens/email-login-screen/email-login-validate.tsx @@ -11,6 +11,7 @@ import { useCallback, useState } from "react" import { ActivityIndicator, View } from "react-native" import { Screen } from "../../components/screen" import analytics from "@react-native-firebase/analytics" +import { testProps } from "@app/utils/testProps" const useStyles = makeStyles(({ colors }) => ({ screenStyle: { @@ -50,17 +51,19 @@ const useStyles = makeStyles(({ colors }) => ({ }, })) -type EmailLoginValidationScreenProps = { - route: RouteProp +type EmailLoginValidateScreenProps = { + route: RouteProp } -export const EmailLoginValidationScreen: React.FC = ({ +const placeholder = "000000" + +export const EmailLoginValidateScreen: React.FC = ({ route, }) => { const styles = useStyles() const { colors } = useTheme() const navigation = - useNavigation>() + useNavigation>() const [errorMessage, setErrorMessage] = React.useState("") @@ -147,11 +150,12 @@ export const EmailLoginValidationScreen: React.FC - {LL.EmailLoginValidationScreen.header({ email })} + {LL.EmailLoginValidateScreen.header({ email })} { const navigation = @@ -76,7 +77,7 @@ export const GetStartedScreen: React.FC = () => { createDeviceAccountEnabled: Boolean(appCheckToken), }) - navigation.navigate("emailLoginInput") + navigation.navigate("emailLoginInitiate") } return ( @@ -110,14 +111,18 @@ export const GetStartedScreen: React.FC = () => { /> )} - {LL.GetStartedScreen.loginBackInWith()} + {LL.GetStartedScreen.logBackInWith()} {LL.common.phone()} {LL.common.or()} - + {LL.common.email()} diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index a88268b293..05b9a24a51 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -1,10 +1,10 @@ +import { sleep } from "../app/utils/sleep" import { i18nObject } from "../app/i18n/i18n-util" import { loadLocale } from "../app/i18n/i18n-util.sync" import { clickBackButton, clickIcon, clickOnSetting, - waitTillOnHomeScreen, waitTillSettingDisplayed, userToken, selector, @@ -15,6 +15,7 @@ import { waitTillButtonDisplayed, getInbox, getFirstEmail, + getSecondEmail, } from "./utils" describe("Login Flow", () => { @@ -124,19 +125,56 @@ describe("Login Flow", () => { await codeInput.waitForDisplayed({ timeout }) await codeInput.click() await codeInput.setValue(code) + + const okButton = await $(selector(LL.common.ok(), "Button")) + await okButton.waitForDisplayed({ timeout }) + await okButton.click() }) - it("clicks OK in alert dialog", async () => { + it("log out", async () => { + await clickOnSetting(LL.AccountScreen.logOutAndDeleteLocalData()) + + await sleep(1000) + + const iUnderstandButton = await $(selector(LL.AccountScreen.IUnderstand(), "Button")) + await iUnderstandButton.waitForDisplayed({ timeout }) + await iUnderstandButton.click() + + await sleep(1000) + const okButton = await $(selector(LL.common.ok(), "Button")) await okButton.waitForDisplayed({ timeout }) await okButton.click() }) - it("navigates back to move home screen", async () => { - await clickBackButton() - await waitTillSettingDisplayed(LL.common.account()) + it("log back in", async () => { + const emailLink = await $(selector("email-button", "Other")) + await emailLink.waitForDisplayed({ timeout }) + await emailLink.click() - await clickBackButton() - await waitTillOnHomeScreen() + const emailInput = await $( + selector(LL.EmailRegistrationInitiateScreen.placeholder(), "Other", "[1]"), + ) + await emailInput.waitForDisplayed({ timeout }) + await emailInput.click() + await emailInput.setValue(email) + await clickButton(LL.EmailRegistrationInitiateScreen.send()) + // i9TEikJakmv4@mailslurp.com + const emailRes = await getSecondEmail(inboxId) + if (!emailRes) throw new Error("No email response") + + const { subject, body } = emailRes + expect(subject).toEqual("your code") + + const regex = /\b\d{6}\b/ + const match = body.match(regex) + if (!match) throw new Error("No code found in email body") + const code = match[0] + + const placeholder = "000000" + const codeInput = await $(selector(placeholder, "Other", "[1]")) + await codeInput.waitForDisplayed({ timeout }) + await codeInput.click() + await codeInput.setValue(code) }) }) diff --git a/e2e/config/wdio.conf.js b/e2e/config/wdio.conf.js index a327a46eb8..8bbfa22325 100644 --- a/e2e/config/wdio.conf.js +++ b/e2e/config/wdio.conf.js @@ -1,10 +1,12 @@ +/* eslint-disable */ + const androidValueForAppiumInspector = { "platformName": "Android", "appium:deviceName": "generic_x86", "appium:app": "./android/app/build/outputs/apk/debug/app-universal-debug.apk", "appium:automationName": "UiAutomator2", "appium:snapshotMaxDepth": 500, - "appium:autoGrantPermissions": true, + "appium:autoGrantPermissions": false } const iOSValueForAppiumInspector = { @@ -13,7 +15,7 @@ const iOSValueForAppiumInspector = { "appium:bundleId": "io.galoy.bitcoinbeach", "appium:automationName": "XCUITest", "appium:snapshotMaxDepth": 500, - "appium:autoAcceptAlerts": true, + "appium:autoAcceptAlerts": false } // value to copy to appium inspector @@ -29,7 +31,7 @@ let capabilities = { "./android/app/build/outputs/apk/debug/app-universal-debug.apk", "appium:automationName": "UiAutomator2", "appium:snapshotMaxDepth": 500, - "appium:autoGrantPermissions": true, + "appium:autoGrantPermissions": false, } if (process.env.E2E_DEVICE === "ios") { @@ -40,7 +42,7 @@ if (process.env.E2E_DEVICE === "ios") { "appium:bundleId": "io.galoy.bitcoinbeach", "appium:automationName": "XCUITest", "appium:snapshotMaxDepth": 500, - "appium:autoAcceptAlerts": true, + "appium:autoAcceptAlerts": false, } } diff --git a/e2e/utils/email.sh b/e2e/utils/email.sh new file mode 100644 index 0000000000..07f90ff3f9 --- /dev/null +++ b/e2e/utils/email.sh @@ -0,0 +1,6 @@ +``` +curl -s -X 'GET' \ + "https://api.mailslurp.com/inboxes/$inboxId/emails" \ + -H 'accept: */*' \ + -H "x-api-key: $MAILSLURP_API_KEY" +``` diff --git a/e2e/utils/email.ts b/e2e/utils/email.ts index f0135b108f..0bdeac7161 100644 --- a/e2e/utils/email.ts +++ b/e2e/utils/email.ts @@ -16,14 +16,14 @@ const headers = { export const getInbox = async () => { const optionsCreateInbox = { method: "POST", - url: `https://api.mailslurp.com/inboxes?expiresIn=60000&useShortAddress=true`, + url: `https://api.mailslurp.com/inboxes?expiresIn=3600000&useShortAddress=true`, headers, } try { const { data } = await axios.request(optionsCreateInbox) const { id, emailAddress } = data - console.log({ id, emailAddress }) + console.log({ inboxId: id, emailAddress }) return { id, emailAddress } } catch (error) { console.error(error)