From 518087cc0a8560160828c54a6004992739936666 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 10 Jan 2024 13:05:18 +0100 Subject: [PATCH 1/2] Sign in reassure flow --- src/components/MagicCodeInput.js | 5 + src/pages/signin/LoginForm/BaseLoginForm.js | 1 + src/pages/signin/SignInPage.js | 5 +- .../ValidateCodeForm/BaseValidateCodeForm.js | 1 + tests/perf-test/SignInPage.perf-test.js | 151 ++++++++++++++++++ .../collections/getValidCodeCredentials.ts | 11 ++ tests/utils/collections/userAccount.ts | 16 ++ 7 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 tests/perf-test/SignInPage.perf-test.js create mode 100644 tests/utils/collections/getValidCodeCredentials.ts create mode 100644 tests/utils/collections/userAccount.ts diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 55a65237a691..ded514aff946 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -61,6 +61,9 @@ const propTypes = { /** Last pressed digit on BigDigitPad */ lastPressedDigit: PropTypes.string, + + /** TestID for test */ + testID: PropTypes.string, }; const defaultProps = { @@ -77,6 +80,7 @@ const defaultProps = { maxLength: CONST.MAGIC_CODE_LENGTH, isDisableKeyboard: false, lastPressedDigit: '', + testID: '', }; /** @@ -397,6 +401,7 @@ function MagicCodeInput(props) { role={CONST.ACCESSIBILITY_ROLE.TEXT} style={[styles.inputTransparent]} textInputContainerStyles={[styles.borderNone]} + testID={props.testID} /> diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index de2f2900c58d..4a076562161d 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -267,6 +267,7 @@ function LoginForm(props) { textContentType="username" id="username" name="username" + testID="username" onBlur={() => { if (firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) { return; diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 8cb0ef9907af..f88bae02f52d 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -245,7 +245,10 @@ function SignInPageInner({credentials, account, isInModal, activeClients, prefer return ( // Bottom SafeAreaView is removed so that login screen svg displays correctly on mobile. // The SVG should flow under the Home Indicator on iOS. - + {hasError && } diff --git a/tests/perf-test/SignInPage.perf-test.js b/tests/perf-test/SignInPage.perf-test.js new file mode 100644 index 000000000000..267a869ee035 --- /dev/null +++ b/tests/perf-test/SignInPage.perf-test.js @@ -0,0 +1,151 @@ +import {fireEvent, screen} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import {measurePerformance} from 'reassure'; +import ComposeProviders from '../../src/components/ComposeProviders'; +import {LocaleContextProvider} from '../../src/components/LocaleContextProvider'; +import OnyxProvider from '../../src/components/OnyxProvider'; +import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; +import CONST from '../../src/CONST'; +import * as Localize from '../../src/libs/Localize'; +import ONYXKEYS from '../../src/ONYXKEYS'; +import SignInPage from '../../src/pages/signin/SignInPage'; +import getValidCodeCredentials from '../utils/collections/getValidCodeCredentials'; +import userAccount, {getValidAccount} from '../utils/collections/userAccount'; +import PusherHelper from '../utils/PusherHelper'; +import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; + +jest.mock('../../src/libs/Navigation/Navigation', () => { + const actualNav = jest.requireActual('../../src/libs/Navigation/Navigation'); + return { + ...actualNav, + navigationRef: { + addListener: () => jest.fn(), + removeListener: () => jest.fn(), + }, + }; +}); + +const mockedNavigate = jest.fn(); +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useFocusEffect: jest.fn(), + useIsFocused: () => ({ + navigate: mockedNavigate, + }), + useRoute: () => jest.fn(), + useNavigation: () => ({ + navigate: jest.fn(), + addListener: () => jest.fn(), + }), + createNavigationContainerRef: jest.fn(), + }; +}); + +function SignInPageWrapper(args) { + return ( + + + + ); +} + +const runs = CONST.PERFORMANCE_TESTS.RUNS; +const login = 'test@mail.com'; + +describe('SignInPage', () => { + beforeAll(() => + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + registerStorageEventListener: () => {}, + }), + ); + + // Initialize the network key for OfflineWithFeedback + beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + wrapOnyxWithWaitForBatchedUpdates(Onyx); + Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); + }); + + // Clear out Onyx after each test so that each test starts with a clean state + afterEach(() => { + Onyx.clear(); + PusherHelper.teardown(); + }); + + test('[SignInPage] should add username and click continue button', () => { + const addListener = jest.fn(); + const scenario = async () => { + /** + * Checking the SignInPage is mounted + */ + await screen.findByTestId('SignInPage'); + + const usernameInput = screen.getByTestId('username'); + + fireEvent.changeText(usernameInput, login); + + const hintContinueButtonText = Localize.translateLocal('common.continue'); + + const continueButton = await screen.findByText(hintContinueButtonText); + + fireEvent.press(continueButton); + }; + + const navigation = {addListener}; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + [ONYXKEYS.ACCOUNT]: userAccount, + [ONYXKEYS.IS_SIDEBAR_LOADED]: false, + }), + ) + .then(() => measurePerformance(, {scenario, runs})); + }); + + test('[SignInPage] should add magic code and click Sign In button', () => { + const addListener = jest.fn(); + const scenario = async () => { + /** + * Checking the SignInPage is mounted + */ + await screen.findByTestId('SignInPage'); + + const welcomeBackText = Localize.translateLocal('welcomeText.welcomeBack'); + const enterMagicCodeText = Localize.translateLocal('welcomeText.welcomeEnterMagicCode', {login}); + + await screen.findByText(`${welcomeBackText} ${enterMagicCodeText}`); + const magicCodeInput = screen.getByTestId('validateCode'); + + fireEvent.changeText(magicCodeInput, '123456'); + + const signInButtonText = Localize.translateLocal('common.signIn'); + const signInButton = await screen.findByText(signInButtonText); + + fireEvent.press(signInButton); + }; + + const navigation = {addListener}; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + [ONYXKEYS.ACCOUNT]: getValidAccount(login), + [ONYXKEYS.CREDENTIALS]: getValidCodeCredentials(login), + [ONYXKEYS.IS_SIDEBAR_LOADED]: false, + }), + ) + .then(() => measurePerformance(, {scenario, runs})); + }); +}); diff --git a/tests/utils/collections/getValidCodeCredentials.ts b/tests/utils/collections/getValidCodeCredentials.ts new file mode 100644 index 000000000000..5ee856b61160 --- /dev/null +++ b/tests/utils/collections/getValidCodeCredentials.ts @@ -0,0 +1,11 @@ +import {randEmail, randNumber} from '@ngneat/falso'; +import type {Credentials} from '@src/types/onyx'; + +function getValidCodeCredentials(login = randEmail()): Credentials { + return { + login, + validateCode: `${randNumber()}`, + }; +} + +export default getValidCodeCredentials; diff --git a/tests/utils/collections/userAccount.ts b/tests/utils/collections/userAccount.ts new file mode 100644 index 000000000000..c1d05eb17cdf --- /dev/null +++ b/tests/utils/collections/userAccount.ts @@ -0,0 +1,16 @@ +import CONST from '@src/CONST'; +import type {Account} from '@src/types/onyx'; + +function getValidAccount(credentialLogin = ''): Account { + return { + validated: true, + primaryLogin: credentialLogin, + isSAMLRequired: false, + isSAMLEnabled: false, + isLoading: false, + requiresTwoFactorAuth: false, + } as Account; +} + +export default CONST.DEFAULT_ACCOUNT_DATA; +export {getValidAccount}; From a0c1445aa3fee982bcd3ff03f8daf5192443a4f9 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Tue, 23 Jan 2024 12:41:22 +0100 Subject: [PATCH 2/2] Add null support to errors instead of changing the value --- src/CONST.ts | 2 +- src/types/onyx/Account.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index eb2e77404f72..0b10e5767328 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -831,7 +831,7 @@ const CONST = { }, WEEK_STARTS_ON: 1, // Monday DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, - DEFAULT_ACCOUNT_DATA: {errors: undefined, success: '', isLoading: false}, + DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, DEFAULT_CLOSE_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, FORMS: { LOGIN_FORM: 'LoginForm', diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 0ea3e05e8d6a..4e7c5396b649 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -50,7 +50,7 @@ type Account = { /** The active policy ID. Initiating a SmartScan will create an expense on this policy by default. */ activePolicyID?: string; - errors?: OnyxCommon.Errors; + errors?: OnyxCommon.Errors | null; success?: string; codesAreCopied?: boolean; twoFactorAuthStep?: TwoFactorAuthStep;