From 9f57e200b87294a39867472146753acea9b77f2f Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:10:10 +0530 Subject: [PATCH 01/13] Add tests for group chat name --- __mocks__/@react-native-reanimated/index.ts | 10 + src/pages/InviteReportParticipantsPage.tsx | 17 +- tests/ui/GroupChatNameTests.tsx | 393 ++++++++++++++++++++ tests/ui/UnreadIndicatorsTest.tsx | 8 +- tests/unit/ReportUtilsTest.ts | 94 +++++ tests/utils/LHNTestUtils.tsx | 20 +- tests/utils/TestHelper.ts | 59 +++ 7 files changed, 584 insertions(+), 17 deletions(-) create mode 100644 __mocks__/@react-native-reanimated/index.ts create mode 100644 tests/ui/GroupChatNameTests.tsx diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts new file mode 100644 index 000000000000..28efba1dde69 --- /dev/null +++ b/__mocks__/@react-native-reanimated/index.ts @@ -0,0 +1,10 @@ +// __mocks__/react-native-reanimated/index.js +const actualAnimated = jest.requireActual('react-native-reanimated/mock'); + +const mock = { + ...actualAnimated, + createAnimatedPropAdapter: jest.fn(), + useReducedMotion: jest.fn(), +}; + +export default mock; diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index a4d5c5518ba2..4db57f5f2f01 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -11,6 +11,7 @@ import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem import type {Section} from '@components/SelectionList/types'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; +import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -44,7 +45,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen const styles = useThemeStyles(); const {translate} = useLocalize(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [invitePersonalDetails, setInvitePersonalDetails] = useState([]); const [recentReports, setRecentReports] = useState([]); @@ -57,7 +58,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen ); useEffect(() => { - const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], searchTerm, excludedUsers, false, options.reports, true); + const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], debouncedSearchTerm, excludedUsers, false, options.reports, true); // Update selectedOptions with the latest personalDetails information const detailsMap: Record = {}; @@ -77,7 +78,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen setRecentReports(inviteOptions.recentReports); setSelectedOptions(newSelectedOptions); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change - }, [personalDetails, betas, searchTerm, excludedUsers, options]); + }, [personalDetails, betas, debouncedSearchTerm, excludedUsers, options]); const sections = useMemo(() => { const sectionsArr: Sections = []; @@ -88,11 +89,11 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen // Filter all options that is a part of the search term or in the personal details let filterSelectedOptions = selectedOptions; - if (searchTerm !== '') { + if (debouncedSearchTerm !== '') { filterSelectedOptions = selectedOptions.filter((option) => { const accountID = option?.accountID; const isOptionInPersonalDetails = invitePersonalDetails.some((personalDetail) => accountID && personalDetail?.accountID === accountID); - const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); + const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm); const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); return isPartOfSearchTerm || isOptionInPersonalDetails; }); @@ -130,7 +131,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen } return sectionsArr; - }, [invitePersonalDetails, searchTerm, selectedOptions, translate, userToInvite, areOptionsInitialized, recentReports]); + }, [invitePersonalDetails, debouncedSearchTerm, selectedOptions, translate, userToInvite, areOptionsInitialized, recentReports]); const toggleOption = useCallback( (option: OptionsListUtils.MemberForList) => { @@ -171,7 +172,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen }, [selectedOptions, backRoute, reportID, validate]); const headerMessage = useMemo(() => { - const searchValue = searchTerm.trim().toLowerCase(); + const searchValue = debouncedSearchTerm.trim().toLowerCase(); const expensifyEmails = CONST.EXPENSIFY_EMAILS as string[]; if (!userToInvite && expensifyEmails.includes(searchValue)) { return translate('messages.errorMessageInvalidEmail'); @@ -187,7 +188,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen return translate('messages.userIsAlreadyMember', {login: searchValue, name: reportName ?? ''}); } return OptionsListUtils.getHeaderMessage(invitePersonalDetails.length !== 0, !!userToInvite, searchValue); - }, [searchTerm, userToInvite, excludedUsers, invitePersonalDetails, translate, reportName]); + }, [debouncedSearchTerm, userToInvite, excludedUsers, invitePersonalDetails, translate, reportName]); const footerContent = useMemo( () => ( diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx new file mode 100644 index 000000000000..e0ac71120768 --- /dev/null +++ b/tests/ui/GroupChatNameTests.tsx @@ -0,0 +1,393 @@ +/* eslint-disable testing-library/no-node-access */ +import type * as NativeNavigation from '@react-navigation/native'; +import {act, render, screen, waitFor} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import * as Localize from '@libs/Localize'; +import * as AppActions from '@userActions/App'; +import * as User from '@userActions/User'; +import App from '@src/App'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Participant} from '@src/types/onyx/Report'; +import PusherHelper from '../utils/PusherHelper'; +import * as TestHelper from '../utils/TestHelper'; +import {navigateToSidebarOption} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +// We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App +jest.setTimeout(50000); + +jest.mock('../../src/components/ConfirmedRoute.tsx'); + +// Needed for: https://stackoverflow.com/questions/76903168/mocking-libraries-in-jest +jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ + /* eslint-disable-next-line @typescript-eslint/naming-convention */ + __esModule: true, + default: { + ignoreLogs: jest.fn(), + ignoreAllLogs: jest.fn(), + }, +})); + +/** + * We need to keep track of the transitionEnd callback so we can trigger it in our tests + */ +let transitionEndCB: () => void; + +type ListenerMock = { + triggerTransitionEnd: () => void; + addListener: jest.Mock; +}; + +/** + * This is a helper function to create a mock for the addListener function of the react-navigation library. + * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate + * the transitionEnd event that is triggered when the screen transition animation is completed. + * + * This can't be moved to a utils file because Jest wants any external function to stay in the scope. + * Details: https://github.com/jestjs/jest/issues/2567 + */ +const createAddListenerMock = (): ListenerMock => { + const transitionEndListeners: Array<() => void> = []; + const triggerTransitionEnd = () => { + transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); + }; + + const addListener: jest.Mock = jest.fn().mockImplementation((listener, callback: () => void) => { + if (listener === 'transitionEnd') { + transitionEndListeners.push(callback); + } + return () => { + transitionEndListeners.filter((cb) => cb !== callback); + }; + }); + + return {triggerTransitionEnd, addListener}; +}; + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + const {triggerTransitionEnd, addListener} = createAddListenerMock(); + transitionEndCB = triggerTransitionEnd; + + const useNavigation = () => + ({ + navigate: jest.fn(), + ...actualNav.useNavigation, + getState: () => ({ + routes: [], + }), + addListener, + } as typeof NativeNavigation.useNavigation); + + return { + ...actualNav, + useNavigation, + getState: () => ({ + routes: [], + }), + } as typeof NativeNavigation; +}); + +beforeAll(() => { + TestHelper.beforeAllSetupUITests(); +}); + +const REPORT_ID = '1'; +const USER_A_ACCOUNT_ID = 1; +const USER_A_EMAIL = 'user_a@test.com'; +const USER_B_ACCOUNT_ID = 2; +const USER_B_EMAIL = 'user_b@test.com'; +const USER_C_ACCOUNT_ID = 3; +const USER_C_EMAIL = 'user_c@test.com'; +const USER_D_ACCOUNT_ID = 4; +const USER_D_EMAIL = 'user_d@test.com'; +const USER_E_ACCOUNT_ID = 5; +const USER_E_EMAIL = 'user_e@test.com'; +const USER_F_ACCOUNT_ID = 6; +const USER_F_EMAIL = 'user_f@test.com'; +const USER_G_ACCOUNT_ID = 7; +const USER_G_EMAIL = 'user_g@test.com'; +const USER_H_ACCOUNT_ID = 8; +const USER_H_EMAIL = 'user_h@test.com'; + +/** + * Sets up a test with a logged in user. Returns the test instance. + */ +function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Promise { + // Render the App and sign in as a test user. + render(); + + const participants: Record = {}; + participantAccountIDs?.forEach((id) => { + participants[id] = { + hidden: false, + role: id === 1 ? CONST.REPORT.ROLE.ADMIN : CONST.REPORT.ROLE.MEMBER, + } as Participant; + }); + + return waitForBatchedUpdatesWithAct() + .then(async () => { + await waitForBatchedUpdatesWithAct(); + const hintText = Localize.translateLocal('loginForm.loginForm'); + const loginForm = screen.queryAllByLabelText(hintText); + expect(loginForm).toHaveLength(1); + + await act(async () => { + await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); + }); + return waitForBatchedUpdatesWithAct(); + }) + .then(() => { + User.subscribeToUserEvents(); + return waitForBatchedUpdates(); + }) + .then(async () => { + // Simulate setting an unread report and personal details + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { + reportID: REPORT_ID, + reportName, + lastMessageText: 'Test', + participants, + lastActorAccountID: USER_B_ACCOUNT_ID, + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + }); + + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [USER_A_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_A_EMAIL, USER_A_ACCOUNT_ID, 'A'), + [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'), + [USER_C_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'), + [USER_D_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_D_EMAIL, USER_D_ACCOUNT_ID, 'D'), + [USER_E_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_E_EMAIL, USER_E_ACCOUNT_ID, 'E'), + [USER_F_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_F_EMAIL, USER_F_ACCOUNT_ID, 'F'), + [USER_G_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_G_EMAIL, USER_G_ACCOUNT_ID, 'G'), + [USER_H_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_H_EMAIL, USER_H_ACCOUNT_ID, 'H'), + }); + + // We manually setting the sidebar as loaded since the onLayout event does not fire in tests + AppActions.setSidebarLoaded(); + return waitForBatchedUpdatesWithAct(); + }); +} + +/** + * Tests for checking the group chat names at places like LHN, chat header, details page etc. + * Note that limit of 5 names is only for the header. + */ +describe('Tests for group chat name', () => { + beforeEach(() => { + jest.clearAllMocks(); + Onyx.clear(); + + // Unsubscribe to pusher channels + PusherHelper.teardown(); + }); + + const participantAccountIDs4 = [USER_A_ACCOUNT_ID, USER_B_ACCOUNT_ID, USER_C_ACCOUNT_ID, USER_D_ACCOUNT_ID]; + const participantAccountIDs8 = [...participantAccountIDs4, USER_E_ACCOUNT_ID, USER_F_ACCOUNT_ID, USER_G_ACCOUNT_ID, USER_H_ACCOUNT_ID]; + + it('Should show correctly in LHN', () => + signInAndGetApp('A, B, C, D', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); + })); + + it('Should show correctly in LHN when report name is not present', () => + signInAndGetApp('', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); + })); + + it('Should show all 8 names in LHN when 8 participants are present', () => + signInAndGetApp('', participantAccountIDs8).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H')); + })); + + it('Check if group name shows fine for report header', () => + signInAndGetApp('', participantAccountIDs4) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'A, B, C, D'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show only 5 names when there are 8 participants in the report header', () => + signInAndGetApp('', participantAccountIDs8) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'A, B, C, D, E'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show exact name in header when report name is available with 4 participants', () => + signInAndGetApp('Test chat', participantAccountIDs4) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('Test chat'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'Test chat'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show exact name in header when report name is available with 8 participants', () => + signInAndGetApp("Let's talk", participantAccountIDs8) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe("Let's talk"); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = "Let's talk"; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show last message preview in LHN', () => + signInAndGetApp('A, B, C, D', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const lastChatHintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview'); + const lastChatText = screen.queryByLabelText(lastChatHintText); + + return waitFor(() => expect(lastChatText?.props?.children).toBe('B: Test')); + })); + + it('Should sort the names before displaying', () => + signInAndGetApp('', [USER_E_ACCOUNT_ID, ...participantAccountIDs4]).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E')); + })); +}); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 1d31a707d81d..1a0bd3aeb279 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -25,6 +25,7 @@ import type {ReportAction, ReportActions} from '@src/types/onyx'; import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; +import {navigateToSidebarOption} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -82,13 +83,6 @@ function navigateToSidebar(): Promise { return waitForBatchedUpdates(); } -async function navigateToSidebarOption(index: number): Promise { - const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - const optionRows = screen.queryAllByAccessibilityHint(hintText); - fireEvent(optionRows[index], 'press'); - await waitForBatchedUpdatesWithAct(); -} - function areYouOnChatListScreen(): boolean { const hintText = Localize.translateLocal('sidebarScreen.listOfChats'); const sidebarLinks = screen.queryAllByLabelText(hintText); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 598c0e3bcbd6..29502ba6177e 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -10,6 +10,7 @@ import type {PersonalDetailsList, Policy, Report, ReportAction} from '@src/types import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import * as NumberUtils from '../../src/libs/NumberUtils'; import * as LHNTestUtils from '../utils/LHNTestUtils'; +import {fakePersonalDetails} from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // Be sure to include the mocked permissions library or else the beta tests won't work @@ -991,4 +992,97 @@ describe('ReportUtils', () => { expect(report).toEqual(undefined); }); }); + + describe('getGroupChatName tests', () => { + afterEach(() => Onyx.clear()); + + describe('When participantAccountIDs is passed to getGroupChatName', () => { + it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4])).toEqual('Four, One, Three, Two'); + }); + + it('Should show all participants name if count <= 5 and shouldApplyLimit is true', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('Four, One, Three, Two'); + }); + + it('Should show 5 participants name if count > 5 and shouldApplyLimit is true', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], true)).toEqual('Five, Four, One, Three, Two'); + }); + + it('Should show all participants name if count > 5 and shouldApplyLimit is false', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + }); + + it('Should use correct display name for participants', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); + }); + }); + + describe('When participantAccountIDs is not passed to getGroupChatName and report ID is passed', () => { + it('Should show report name if count <= 5 and shouldApplyLimit is false', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4], 0, false, [1]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count <= 5 and shouldApplyLimit is true', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4], 0, false, [1]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, true, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count > 5 and shouldApplyLimit is true', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, true, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count > 5 and shouldApplyLimit is false', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual("Let's talk"); + }); + + it('Should show participant names if report name is not available', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: '', + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + }); + }); + }); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 7197529cd43c..1d8a13ead7d6 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -112,6 +112,13 @@ const fakePersonalDetails: PersonalDetailsList = { avatar: 'none', firstName: 'Nine', }, + 10: { + accountID: 10, + login: 'email10@test.com', + displayName: 'Email Ten', + avatar: 'none', + firstName: 'Ten', + }, }; let lastFakeReportID = 0; @@ -120,16 +127,25 @@ let lastFakeReportActionID = 0; /** * @param millisecondsInThePast the number of milliseconds in the past for the last message timestamp (to order reports by most recent messages) */ -function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0, isUnread = false): Report { +function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0, isUnread = false, adminIDs: number[] = []): Report { const lastVisibleActionCreated = DateUtils.getDBTime(Date.now() - millisecondsInThePast); + const participants = ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs); + + adminIDs.forEach((id) => { + participants[id] = { + hidden: false, + role: CONST.REPORT.ROLE.ADMIN, + }; + }); + return { type: CONST.REPORT.TYPE.CHAT, reportID: `${++lastFakeReportID}`, reportName: 'Report', lastVisibleActionCreated, lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastVisibleActionCreated, 1) : lastVisibleActionCreated, - participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), + participants, }; } diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 84ea2b2aafbe..6f97c23a5b29 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,3 +1,4 @@ +import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; @@ -13,6 +14,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import appSetup from '@src/setup'; import type {Response as OnyxResponse, PersonalDetails, Report} from '@src/types/onyx'; import waitForBatchedUpdates from './waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from './waitForBatchedUpdatesWithAct'; +import * as Localize from '@libs/Localize'; type MockFetch = jest.MockedFn & { pause: () => void; @@ -305,6 +308,59 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { ).toEqual(expect.objectContaining(obj)); } +/** + * This is a helper function to create a mock for the addListener function of the react-navigation library. + * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate + * the transitionEnd event that is triggered when the screen transition animation is completed. + * + * @returns An object with two functions: triggerTransitionEnd and addListener + */ +const createAddListenerMock = () => { + const transitionEndListeners: Listener[] = []; + const triggerTransitionEnd = () => { + transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); + }; + + const addListener = jest.fn().mockImplementation((listener, callback: Listener) => { + if (listener === 'transitionEnd') { + transitionEndListeners.push(callback); + } + return () => { + transitionEndListeners.filter((cb) => cb !== callback); + }; + }); + + return {triggerTransitionEnd, addListener}; +}; + +async function navigateToSidebarOption(index: number): Promise { + const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(hintText); + fireEvent(optionRows[index], 'press'); + await waitForBatchedUpdatesWithAct(); +} + +function beforeAllSetupUITests(shouldConnectToPusher = false) { + // In this test, we are generically mocking the responses of all API requests by mocking fetch() and having it + // return 200. In other tests, we might mock HttpUtils.xhr() with a more specific mock data response (which means + // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling + // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to + // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. + global.fetch = getGlobalFetchMock(); + + Linking.setInitialURL('https://new.expensify.com/'); + appSetup(); + + if (shouldConnectToPusher) { + PusherConnectionManager.init(); + Pusher.init({ + appKey: CONFIG.PUSHER.APP_KEY, + cluster: CONFIG.PUSHER.CLUSTER, + authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, + }); + } +} + export type {MockFetch, FormData}; export { assertFormDataMatchesObject, @@ -318,4 +374,7 @@ export { expectAPICommandToHaveBeenCalled, expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, + createAddListenerMock, + navigateToSidebarOption, + beforeAllSetupUITests, }; From 7b5298a9e61ceeefdb87d3398902a6038602bffa Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 2 Aug 2024 02:34:04 +0530 Subject: [PATCH 02/13] Update --- __mocks__/@react-native-reanimated/index.ts | 3 ++- tests/ui/GroupChatNameTests.tsx | 4 ++++ tests/utils/TestHelper.ts | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts index 28efba1dde69..df9cc4ecef8d 100644 --- a/__mocks__/@react-native-reanimated/index.ts +++ b/__mocks__/@react-native-reanimated/index.ts @@ -1,4 +1,5 @@ -// __mocks__/react-native-reanimated/index.js +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + const actualAnimated = jest.requireActual('react-native-reanimated/mock'); const mock = { diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index e0ac71120768..7eb70b412f70 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -1,4 +1,8 @@ /* eslint-disable testing-library/no-node-access */ + +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type * as NativeNavigation from '@react-navigation/native'; import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 6f97c23a5b29..b10f165a96be 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,8 +1,10 @@ import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; +import type {Listener} from 'onfido-sdk-ui/types/shared/EventEmitter'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import type {ApiCommand, ApiRequestCommandParameters} from '@libs/API/types'; +import * as Localize from '@libs/Localize'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import CONFIG from '@src/CONFIG'; @@ -15,7 +17,6 @@ import appSetup from '@src/setup'; import type {Response as OnyxResponse, PersonalDetails, Report} from '@src/types/onyx'; import waitForBatchedUpdates from './waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from './waitForBatchedUpdatesWithAct'; -import * as Localize from '@libs/Localize'; type MockFetch = jest.MockedFn & { pause: () => void; @@ -318,6 +319,7 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { const createAddListenerMock = () => { const transitionEndListeners: Listener[] = []; const triggerTransitionEnd = () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); }; From a428070ae660713f0a7bfab57631b20a4c9b00fe Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:38:39 +0530 Subject: [PATCH 03/13] Update --- tests/ui/GroupChatNameTests.tsx | 50 +-------------------------------- tests/utils/TestHelper.ts | 1 - 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 7eb70b412f70..fa84ee1e12d5 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -45,55 +45,7 @@ type ListenerMock = { addListener: jest.Mock; }; -/** - * This is a helper function to create a mock for the addListener function of the react-navigation library. - * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate - * the transitionEnd event that is triggered when the screen transition animation is completed. - * - * This can't be moved to a utils file because Jest wants any external function to stay in the scope. - * Details: https://github.com/jestjs/jest/issues/2567 - */ -const createAddListenerMock = (): ListenerMock => { - const transitionEndListeners: Array<() => void> = []; - const triggerTransitionEnd = () => { - transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); - }; - - const addListener: jest.Mock = jest.fn().mockImplementation((listener, callback: () => void) => { - if (listener === 'transitionEnd') { - transitionEndListeners.push(callback); - } - return () => { - transitionEndListeners.filter((cb) => cb !== callback); - }; - }); - - return {triggerTransitionEnd, addListener}; -}; - -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - const {triggerTransitionEnd, addListener} = createAddListenerMock(); - transitionEndCB = triggerTransitionEnd; - - const useNavigation = () => - ({ - navigate: jest.fn(), - ...actualNav.useNavigation, - getState: () => ({ - routes: [], - }), - addListener, - } as typeof NativeNavigation.useNavigation); - - return { - ...actualNav, - useNavigation, - getState: () => ({ - routes: [], - }), - } as typeof NativeNavigation; -}); +jest.mock('@react-navigation/native'); beforeAll(() => { TestHelper.beforeAllSetupUITests(); diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index b10f165a96be..f80ce747837c 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,6 +1,5 @@ import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; -import type {Listener} from 'onfido-sdk-ui/types/shared/EventEmitter'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import type {ApiCommand, ApiRequestCommandParameters} from '@libs/API/types'; From e8956bcc2b2c5e77054675f39dc8a740a27f3742 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:44:44 +0530 Subject: [PATCH 04/13] Update TestHelper.ts --- tests/utils/TestHelper.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index f80ce747837c..72dbfaafe61f 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -308,32 +308,6 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { ).toEqual(expect.objectContaining(obj)); } -/** - * This is a helper function to create a mock for the addListener function of the react-navigation library. - * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate - * the transitionEnd event that is triggered when the screen transition animation is completed. - * - * @returns An object with two functions: triggerTransitionEnd and addListener - */ -const createAddListenerMock = () => { - const transitionEndListeners: Listener[] = []; - const triggerTransitionEnd = () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return - transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); - }; - - const addListener = jest.fn().mockImplementation((listener, callback: Listener) => { - if (listener === 'transitionEnd') { - transitionEndListeners.push(callback); - } - return () => { - transitionEndListeners.filter((cb) => cb !== callback); - }; - }); - - return {triggerTransitionEnd, addListener}; -}; - async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); const optionRows = screen.queryAllByAccessibilityHint(hintText); @@ -375,7 +349,6 @@ export { expectAPICommandToHaveBeenCalled, expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, - createAddListenerMock, navigateToSidebarOption, beforeAllSetupUITests, }; From 3e2f30e6eacb00fe7d154a859d49574a3f726b09 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:11:44 +0530 Subject: [PATCH 05/13] Update --- tests/unit/ReportUtilsTest.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 334244ec066e..9543e02c3c73 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -996,30 +996,48 @@ describe('ReportUtils', () => { describe('getGroupChatName tests', () => { afterEach(() => Onyx.clear()); + const fourParticipants = [ + {accountID: 1, login: "email1@test.com"}, + {accountID: 2, login: "email2@test.com"}, + {accountID: 3, login: "email3@test.com"}, + {accountID: 4, login: "email4@test.com"}, + ] + + const eightParticipants = [ + {accountID: 1, login: "email1@test.com"}, + {accountID: 2, login: "email2@test.com"}, + {accountID: 3, login: "email3@test.com"}, + {accountID: 4, login: "email4@test.com"}, + {accountID: 5, login: "email5@test.com"}, + {accountID: 6, login: "email6@test.com"}, + {accountID: 7, login: "email7@test.com"}, + {accountID: 8, login: "email8@test.com"}, + ] + describe('When participantAccountIDs is passed to getGroupChatName', () => { it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4])).toEqual('Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(fourParticipants)).toEqual('Four, One, Three, Two'); }); it('Should show all participants name if count <= 5 and shouldApplyLimit is true', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(fourParticipants)).toEqual('Four, One, Three, Two'); }); it('Should show 5 participants name if count > 5 and shouldApplyLimit is true', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], true)).toEqual('Five, Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(eightParticipants, true)).toEqual('Five, Four, One, Three, Two'); }); it('Should show all participants name if count > 5 and shouldApplyLimit is false', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + expect(ReportUtils.getGroupChatName(eightParticipants, false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); }); it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); }); }); From 84deb67dd9c5e3003286a160f994e0b7ae40e2c6 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:16:01 +0530 Subject: [PATCH 06/13] Update --- tests/ui/GroupChatNameTests.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index fa84ee1e12d5..25defa2ff283 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -3,7 +3,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import type * as NativeNavigation from '@react-navigation/native'; import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; @@ -40,11 +39,6 @@ jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ */ let transitionEndCB: () => void; -type ListenerMock = { - triggerTransitionEnd: () => void; - addListener: jest.Mock; -}; - jest.mock('@react-navigation/native'); beforeAll(() => { From 221b3ff1399fb6120b2fa8d7fb8016626b3bf8c3 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:31:49 +0530 Subject: [PATCH 07/13] Lint fixes --- tests/unit/ReportUtilsTest.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 9543e02c3c73..bf8f48e988ab 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -997,22 +997,22 @@ describe('ReportUtils', () => { afterEach(() => Onyx.clear()); const fourParticipants = [ - {accountID: 1, login: "email1@test.com"}, - {accountID: 2, login: "email2@test.com"}, - {accountID: 3, login: "email3@test.com"}, - {accountID: 4, login: "email4@test.com"}, - ] + {accountID: 1, login: 'email1@test.com'}, + {accountID: 2, login: 'email2@test.com'}, + {accountID: 3, login: 'email3@test.com'}, + {accountID: 4, login: 'email4@test.com'}, + ]; const eightParticipants = [ - {accountID: 1, login: "email1@test.com"}, - {accountID: 2, login: "email2@test.com"}, - {accountID: 3, login: "email3@test.com"}, - {accountID: 4, login: "email4@test.com"}, - {accountID: 5, login: "email5@test.com"}, - {accountID: 6, login: "email6@test.com"}, - {accountID: 7, login: "email7@test.com"}, - {accountID: 8, login: "email8@test.com"}, - ] + {accountID: 1, login: 'email1@test.com'}, + {accountID: 2, login: 'email2@test.com'}, + {accountID: 3, login: 'email3@test.com'}, + {accountID: 4, login: 'email4@test.com'}, + {accountID: 5, login: 'email5@test.com'}, + {accountID: 6, login: 'email6@test.com'}, + {accountID: 7, login: 'email7@test.com'}, + {accountID: 8, login: 'email8@test.com'}, + ]; describe('When participantAccountIDs is passed to getGroupChatName', () => { it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { From 33d1ba31a3d0f1ec31e749eba325ed03c66918b2 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:59:08 +0530 Subject: [PATCH 08/13] Update --- tests/ui/GroupChatNameTests.tsx | 4 ---- tests/utils/TestHelper.ts | 29 +++++------------------------ 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 25defa2ff283..393cf2c6dad2 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -41,10 +41,6 @@ let transitionEndCB: () => void; jest.mock('@react-navigation/native'); -beforeAll(() => { - TestHelper.beforeAllSetupUITests(); -}); - const REPORT_ID = '1'; const USER_A_ACCOUNT_ID = 1; const USER_A_EMAIL = 'user_a@test.com'; diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 084edc1d1567..394c05fe8b7c 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -313,30 +313,12 @@ function assertFormDataMatchesObject(obj: Report, formData?: FormData) { async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - const optionRows = screen.queryAllByAccessibilityHint(hintText); - fireEvent(optionRows[index], 'press'); - await waitForBatchedUpdatesWithAct(); -} - -function beforeAllSetupUITests(shouldConnectToPusher = false) { - // In this test, we are generically mocking the responses of all API requests by mocking fetch() and having it - // return 200. In other tests, we might mock HttpUtils.xhr() with a more specific mock data response (which means - // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling - // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to - // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. - global.fetch = getGlobalFetchMock(); - - Linking.setInitialURL('https://new.expensify.com/'); - appSetup(); - - if (shouldConnectToPusher) { - PusherConnectionManager.init(); - Pusher.init({ - appKey: CONFIG.PUSHER.APP_KEY, - cluster: CONFIG.PUSHER.CLUSTER, - authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, - }); + const optionRow = screen.queryAllByAccessibilityHint(hintText).at(index); + if (!optionRow) { + return; } + fireEvent(optionRow, 'press'); + await waitForBatchedUpdatesWithAct(); } export type {MockFetch, FormData}; @@ -353,5 +335,4 @@ export { expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, navigateToSidebarOption, - beforeAllSetupUITests, }; From 304f0d2fc357e24e88006c0a35f821dc1f1639c1 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:05:35 +0530 Subject: [PATCH 09/13] Update --- tests/ui/GroupChatNameTests.tsx | 3 +++ tests/utils/LHNTestUtils.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 393cf2c6dad2..3c13605925c9 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -41,6 +41,8 @@ let transitionEndCB: () => void; jest.mock('@react-navigation/native'); +TestHelper.setupApp(); + const REPORT_ID = '1'; const USER_A_ACCOUNT_ID = 1; const USER_A_EMAIL = 'user_a@test.com'; @@ -69,6 +71,7 @@ function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Pro const participants: Record = {}; participantAccountIDs?.forEach((id) => { participants[id] = { + notificationPreference: 'always', hidden: false, role: id === 1 ? CONST.REPORT.ROLE.ADMIN : CONST.REPORT.ROLE.MEMBER, } as Participant; diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 7a9873483069..02223d36f3c6 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -134,7 +134,7 @@ function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0 adminIDs.forEach((id) => { participants[id] = { - hidden: false, + notificationPreference: 'always', role: CONST.REPORT.ROLE.ADMIN, }; }); From e24e1ccd2c10cc2fff1f3addc8240f429d4f8d51 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:50:05 +0530 Subject: [PATCH 10/13] Update --- __mocks__/@react-native-reanimated/index.ts | 11 ----------- tests/ui/GroupChatNameTests.tsx | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 __mocks__/@react-native-reanimated/index.ts diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts deleted file mode 100644 index df9cc4ecef8d..000000000000 --- a/__mocks__/@react-native-reanimated/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - -const actualAnimated = jest.requireActual('react-native-reanimated/mock'); - -const mock = { - ...actualAnimated, - createAnimatedPropAdapter: jest.fn(), - useReducedMotion: jest.fn(), -}; - -export default mock; diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 3c13605925c9..919279ad00e3 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -174,7 +174,7 @@ describe('Tests for group chat name', () => { return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); })); - it('Should show all 8 names in LHN when 8 participants are present', () => + it('Should show limited names in LHN when 8 participants are present', () => signInAndGetApp('', participantAccountIDs8).then(() => { // Verify the sidebar links are rendered const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); @@ -189,7 +189,7 @@ describe('Tests for group chat name', () => { const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H')); + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E')); })); it('Check if group name shows fine for report header', () => From a8b9955c47368b50588b7bbbb27026d71b855e32 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:57:33 +0530 Subject: [PATCH 11/13] Update --- tests/ui/GroupChatNameTests.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 919279ad00e3..fc383efe4e28 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -236,7 +236,7 @@ describe('Tests for group chat name', () => { const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H'); + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E'); return navigateToSidebarOption(0); }) From b64f63db80eb53a9e6dab3c735f7293557daa53b Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:41:41 +0530 Subject: [PATCH 12/13] Update timeout of UnreadIndicatorsTest.tsx --- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index bada60720e36..4cb2b5463fb9 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -30,7 +30,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; // We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App -jest.setTimeout(30000); +jest.setTimeout(40000); jest.mock('@react-navigation/native'); jest.mock('../../src/libs/Notification/LocalNotification'); From bd9f6e8077ce434b1b034c45e195928dc94c81ab Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:42:35 +0530 Subject: [PATCH 13/13] Update timeout of UnreadIndicatorsTest.tsx --- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 4cb2b5463fb9..c201d8cabd74 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -30,7 +30,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; // We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App -jest.setTimeout(40000); +jest.setTimeout(45000); jest.mock('@react-navigation/native'); jest.mock('../../src/libs/Notification/LocalNotification');