From 63b725b8c9c58c82c8e790cdacd8d93240f152f8 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 20 Oct 2025 12:33:54 +0200 Subject: [PATCH 1/7] UI tests for ContactMethodsPage --- tests/ui/ContactMethodsPageTest.tsx | 135 ++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 tests/ui/ContactMethodsPageTest.tsx diff --git a/tests/ui/ContactMethodsPageTest.tsx b/tests/ui/ContactMethodsPageTest.tsx new file mode 100644 index 000000000000..09b0847c7185 --- /dev/null +++ b/tests/ui/ContactMethodsPageTest.tsx @@ -0,0 +1,135 @@ +import { render, screen, waitFor } from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import type { ValueOf } from 'type-fest'; +import ComposeProviders from '@components/ComposeProviders'; +import ContactMethodsPage from '@pages/settings/Profile/Contacts/ContactMethodsPage'; +import DelegateNoAccessModalProvider from '@src/components/DelegateNoAccessModalProvider'; +import LockedAccountModalProvider from '@src/components/LockedAccountModalProvider'; +import type CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + + +// Mock navigation used by the page +jest.mock('@libs/Navigation/Navigation', () => ({ + navigate: jest.fn(), + goBack: jest.fn(), + getActiveRoute: jest.fn(() => ''), +})); + +// Replace MenuItem with a simple test double that exposes props in the tree +jest.mock('@components/MenuItem', () => { + const ReactMock = require('react') as typeof React; + const {Text} = require('react-native') as {Text: React.ComponentType<{testID: string; children?: React.ReactNode}>}; + return ({title, brickRoadIndicator}: {title: string; brickRoadIndicator?: ValueOf}) => + ReactMock.createElement(Text, {testID: `menu-${String(title)}`}, `${brickRoadIndicator ?? 'none'}-brickRoadIndicator`); +}); + +describe('ContactMethodsPage', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(() => { + return Onyx.clear(); + }); + + function renderPage() { + return render( + + {/* @ts-expect-error - route typing is not necessary for this test */} + + , + ); + } + + it('sets error indicator when login has error fields', async () => { + // Given a login list entry with errorFields set + const defaultEmail = 'default@example.com'; + const otherEmail = 'other@example.com'; + Onyx.merge(ONYXKEYS.SESSION, {email: defaultEmail}); + Onyx.merge(ONYXKEYS.LOGIN_LIST, { + [defaultEmail]: { + partnerUserID: defaultEmail, + validatedDate: '2024-01-01', + }, + [otherEmail]: { + partnerUserID: otherEmail, + validatedDate: '', + errorFields: { + error: {field: 'dummy'}, + }, + }, + }); + await waitForBatchedUpdates(); + + renderPage(); + + let node = screen.getByTestId(`menu-${defaultEmail}`); + // ContactMethodsPage doesn't set any BR for validated logins + expect(node).toHaveTextContent('none-brickRoadIndicator'); + + node = screen.getByTestId(`menu-${otherEmail}`); + // ContactMethodsPage sets brickRoadIndicator to 'error' when any errorFields are present + expect(node).toHaveTextContent('error-brickRoadIndicator'); + + // Verify that RBR dissapears + Onyx.merge(ONYXKEYS.LOGIN_LIST, { + [otherEmail]: { + partnerUserID: otherEmail, + validatedDate: '2024-02-02', + errorFields: null, + }, + }); + + await waitFor(() => { + node = screen.getByTestId(`menu-${otherEmail}`); + // ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins + expect(node).toHaveTextContent('none-brickRoadIndicator'); + }); + }); + + it('sets info indicator when login is unvalidated and not default', async () => { + // Given two logins: default (session email) validated, and another unvalidated + const defaultEmail = 'default@example.com'; + const otherEmail = 'other@example.com'; + Onyx.merge(ONYXKEYS.SESSION, {email: defaultEmail}); + Onyx.merge(ONYXKEYS.LOGIN_LIST, { + [defaultEmail]: { + partnerUserID: defaultEmail, + validatedDate: '2024-01-01', + }, + [otherEmail]: { + partnerUserID: otherEmail, + validatedDate: '', + }, + }); + await waitForBatchedUpdates(); + + renderPage(); + let node = screen.getByTestId(`menu-${defaultEmail}`); + // ContactMethodsPage doesn't set any BR for validated logins + expect(node).toHaveTextContent('none-brickRoadIndicator'); + + node = screen.getByTestId(`menu-${otherEmail}`); + // ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins + expect(node).toHaveTextContent('info-brickRoadIndicator'); + + // Verify that GBR dissapears + Onyx.merge(ONYXKEYS.LOGIN_LIST, { + [otherEmail]: { + partnerUserID: otherEmail, + validatedDate: '2024-02-02', + }, + }); + + await waitFor(() => { + node = screen.getByTestId(`menu-${otherEmail}`); + // ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins + expect(node).toHaveTextContent('none-brickRoadIndicator'); + }); + }); +}); \ No newline at end of file From 6f8d2fc3c718f976ef9a312764980be03416813f Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 20 Oct 2025 12:59:09 +0200 Subject: [PATCH 2/7] UI tests for ProfilePage --- src/pages/settings/Profile/ProfilePage.tsx | 2 + tests/ui/ProfilePageTest.tsx | 135 +++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 tests/ui/ProfilePageTest.tsx diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 3d94b9befcb4..141663426ff5 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -79,6 +79,7 @@ function ProfilePage() { title: formatPhoneNumber(currentUserPersonalDetails?.login ?? ''), pageRoute: ROUTES.SETTINGS_CONTACT_METHODS.route, brickRoadIndicator: contactMethodBrickRoadIndicator, + testID: 'contact-method-menu-item', }, { description: translate('statusPage.status'), @@ -244,6 +245,7 @@ function ProfilePage() { wrapperStyle={styles.sectionMenuItemTopDescription} onPress={() => Navigation.navigate(detail.pageRoute)} brickRoadIndicator={detail.brickRoadIndicator} + pressableTestID={detail?.testID} /> ))}