Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/pages/settings/Profile/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -244,6 +245,7 @@ function ProfilePage() {
wrapperStyle={styles.sectionMenuItemTopDescription}
onPress={() => Navigation.navigate(detail.pageRoute)}
brickRoadIndicator={detail.brickRoadIndicator}
pressableTestID={detail?.testID}
/>
))}
<Button
Expand Down
140 changes: 140 additions & 0 deletions tests/ui/ContactMethodsPageTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>}) =>
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(
<ComposeProviders components={[LockedAccountModalProvider, DelegateNoAccessModalProvider]}>
{/* @ts-expect-error - route typing is not necessary for this test */}
<ContactMethodsPage route={{params: {}}} />
</ComposeProviders>,
);
}

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 disappears
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ContactMethodsPage doesn't set any BR for validated logins
// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins
// ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins

expect(node).toHaveTextContent('info-brickRoadIndicator');

// Verify that GBR disappears
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');
});
});
});
138 changes: 138 additions & 0 deletions tests/ui/ProfilePageTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {NavigationContainer} from '@react-navigation/native';
import type * as ReactNavigation from '@react-navigation/native';
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 DelegateNoAccessModalProvider from '@components/DelegateNoAccessModalProvider';
import ProfilePage from '@pages/settings/Profile/ProfilePage';
import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

jest.mock('@libs/Navigation/Navigation', () => ({
navigate: jest.fn(),
goBack: jest.fn(),
getActiveRoute: jest.fn(() => ''),
getShouldPopToSidebar: jest.fn(() => false),
popToSidebar: jest.fn(),
}));

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual<typeof ReactNavigation>('@react-navigation/native');
return {
...actualNav,
useRoute: jest.fn(),
createNavigationContainerRef: () => ({
getState: () => jest.fn(),
}),
usePreventRemove: jest.fn(),
};
});

// Replace MenuItemWithTopDescription with a simple test double that exposes props in the tree
jest.mock('@components/MenuItemWithTopDescription', () => {
const ReactMock = require('react') as typeof React;
const {Text} = require('react-native') as {Text: React.ComponentType<{testID: string; children?: React.ReactNode}>};
return ({pressableTestID, brickRoadIndicator}: {pressableTestID: string; brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>}) =>
ReactMock.createElement(Text, {testID: pressableTestID}, `${brickRoadIndicator ?? 'none'}-brickRoadIndicator`);
});

describe('ProfilePage contact method indicator', () => {
beforeAll(() => {
Onyx.init({
keys: ONYXKEYS,
});
});

beforeEach(() => {
return Onyx.clear();
});

function renderPage() {
return render(
<NavigationContainer>
<ComposeProviders components={[DelegateNoAccessModalProvider]}>
<ProfilePage
// @ts-expect-error - route typing is not necessary for this test
route={{}}
navigation={{}}
/>
</ComposeProviders>
</NavigationContainer>,
);
}

it('shows error when login list has errors', async () => {
const email = 'user@example.com';

// Current user provided by mocked hook uses the same email
Onyx.merge(ONYXKEYS.LOGIN_LIST, {
[email]: {
partnerUserID: email,
validatedDate: '',
errorFields: {anyError: {message: 'oops'}},
},
});
await waitForBatchedUpdates();

renderPage();

// Description for contact method is 'contacts.contactMethod' via mocked translate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave an empty line before each comment like this for better readability

Suggested change
// Description for contact method is 'contacts.contactMethod' via mocked translate
// Description for contact method is 'contacts.contactMethod' via mocked translate

let node = screen.getByText('error-brickRoadIndicator');
expect(node).toBeDefined();

// Verify that RBR disappears
Onyx.merge(ONYXKEYS.LOGIN_LIST, {
[email]: {
partnerUserID: email,
validatedDate: '2024-02-02',
errorFields: null,
},
});

await waitFor(() => {
node = screen.getByTestId('contact-method-menu-item');

// ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins
expect(node).toHaveTextContent('none-brickRoadIndicator');
});
});

it('shows info when there is an unvalidated secondary login', async () => {
const defaultEmail = 'user@example.com';
const otherEmail = 'other@example.com';
Onyx.merge(ONYXKEYS.LOGIN_LIST, {
[defaultEmail]: {
partnerUserID: defaultEmail,
validatedDate: '2024-01-01',
},
[otherEmail]: {
partnerUserID: otherEmail,
validatedDate: '',
},
});
await waitForBatchedUpdates();

renderPage();

let node = screen.getByText('info-brickRoadIndicator');
expect(node).toBeDefined();

// Verify that GBR disappears
Onyx.merge(ONYXKEYS.LOGIN_LIST, {
[otherEmail]: {
partnerUserID: otherEmail,
validatedDate: '2024-02-02',
},
});

await waitFor(() => {
node = screen.getByTestId('contact-method-menu-item');

// ContactMethodsPage sets brickRoadIndicator to 'info' for non-default unvalidated logins
expect(node).toHaveTextContent('none-brickRoadIndicator');
});
});
});
Loading