diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts deleted file mode 100644 index 44a82253b7c0..000000000000 --- a/src/hooks/usePaginatedReportActions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {useMemo} from 'react'; -import {useOnyx} from 'react-native-onyx'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Get the longest continuous chunk of reportActions including the linked reportAction. If not linking to a specific action, returns the continuous chunk of newest reportActions. - */ -function usePaginatedReportActions(reportID?: string, reportActionID?: string) { - // Use `||` instead of `??` to handle empty string. - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const reportIDWithDefault = reportID || '-1'; - const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { - canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), - }); - - const reportActions = useMemo(() => { - if (!sortedAllReportActions.length) { - return []; - } - return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionID); - }, [reportActionID, sortedAllReportActions]); - - const linkedAction = useMemo(() => sortedAllReportActions.find((obj) => String(obj.reportActionID) === String(reportActionID)), [reportActionID, sortedAllReportActions]); - - return { - reportActions, - linkedAction, - }; -} - -export default usePaginatedReportActions; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index b1ef9fbfdd97..dcbad36d1eda 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -22,7 +22,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types'; @@ -80,10 +79,21 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const {translate} = useLocalize(); const {isOffline} = useNetwork(); const styles = useThemeStyles(); + // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || '-1'}`); - const {reportActions} = usePaginatedReportActions(report.reportID); + const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID ?? '-1'}`, { + canEvict: false, + selector: (allReportActions: OnyxEntry<OnyxTypes.ReportActions>) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), + }); + + const reportActions = useMemo(() => { + if (!sortedAllReportActions.length) { + return []; + } + return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions); + }, [sortedAllReportActions]); const transactionThreadReportID = useMemo( () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 3f37663928ff..d8d98fe32d96 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -26,11 +26,11 @@ import useIsReportOpenInRHP from '@hooks/useIsReportOpenInRHP'; import useLastAccessedReportID from '@hooks/useLastAccessedReportID'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {getCurrentUserAccountID} from '@libs/actions/Report'; import Timing from '@libs/actions/Timing'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; @@ -68,6 +68,9 @@ type ReportScreenOnyxProps = { /** The policies which the user has access to */ policies: OnyxCollection<OnyxTypes.Policy>; + /** An array containing all report actions related to this report, sorted based on a date criterion */ + sortedAllReportActions: OnyxTypes.ReportAction[]; + /** Additional report details */ reportNameValuePairs: OnyxEntry<OnyxTypes.ReportNameValuePairs>; @@ -116,6 +119,7 @@ function ReportScreen({ betas = [], route, reportNameValuePairs, + sortedAllReportActions, reportMetadata = { isLoadingInitialReportActions: true, isLoadingOlderReportActions: false, @@ -279,14 +283,12 @@ function ReportScreen({ const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [isLinkingToMessage, setIsLinkingToMessage] = useState(!!reportActionIDFromRoute); - - const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: (value) => value?.accountID}); - const {reportActions, linkedAction} = usePaginatedReportActions(report.reportID, reportActionIDFromRoute); - const isLinkedActionDeleted = useMemo(() => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID), [linkedAction]); - const isLinkedActionInaccessibleWhisper = useMemo( - () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), - [currentUserAccountID, linkedAction], - ); + const reportActions = useMemo(() => { + if (!sortedAllReportActions.length) { + return []; + } + return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionIDFromRoute); + }, [reportActionIDFromRoute, sortedAllReportActions]); // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. // If we have cached reportActions, they will be shown immediately. @@ -311,6 +313,10 @@ function ReportScreen({ const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isEmptyChat = useMemo(() => ReportUtils.isEmptyReport(report), [report]); const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; + const isLinkedMessageAvailable = useMemo( + (): boolean => sortedAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)) > -1, + [sortedAllReportActions, reportActionIDFromRoute], + ); // If there's a non-404 error for the report we should show it instead of blocking the screen const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); @@ -402,12 +408,12 @@ function ReportScreen({ const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isReportOpenInRHP) || PersonalDetailsUtils.isPersonalDetailsEmpty()); const shouldShowSkeleton = - !linkedAction && + !isLinkedMessageAvailable && (isLinkingToMessage || !isCurrentReportLoadedFromOnyx || (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) || isLoading || - (!!reportActionIDFromRoute && !!reportMetadata?.isLoadingInitialReportActions)); + (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions)); const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading; const currentReportIDFormRoute = route.params?.reportID; @@ -682,16 +688,28 @@ function ReportScreen({ fetchReport(); }, [fetchReport]); + const {isLinkedReportActionDeleted, isInaccessibleWhisper} = useMemo(() => { + const currentUserAccountID = getCurrentUserAccountID(); + if (!reportActionIDFromRoute || !sortedAllReportActions) { + return {isLinkedReportActionDeleted: false, isInaccessibleWhisper: false}; + } + const action = sortedAllReportActions.find((item) => item.reportActionID === reportActionIDFromRoute); + return { + isLinkedReportActionDeleted: action && !ReportActionsUtils.shouldReportActionBeVisible(action, action.reportActionID), + isInaccessibleWhisper: action && ReportActionsUtils.isWhisperAction(action) && !(action?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), + }; + }, [reportActionIDFromRoute, sortedAllReportActions]); + // If user redirects to an inaccessible whisper via a deeplink, on a report they have access to, // then we set reportActionID as empty string, so we display them the report and not the "Not found page". useEffect(() => { - if (!isLinkedActionInaccessibleWhisper) { + if (!isInaccessibleWhisper) { return; } Navigation.isNavigationReady().then(() => { Navigation.setParams({reportActionID: ''}); }); - }, [isLinkedActionInaccessibleWhisper]); + }, [isInaccessibleWhisper]); useEffect(() => { if (!!report.lastReadTime || !ReportUtils.isTaskReport(report)) { @@ -701,7 +719,7 @@ function ReportScreen({ Report.readNewestAction(report.reportID); }, [report]); - if ((!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) { + if ((!isInaccessibleWhisper && isLinkedReportActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) { return ( <BlockingView icon={Illustrations.ToddBehindCloud} @@ -815,6 +833,11 @@ export default withCurrentReportID( isSidebarLoaded: { key: ONYXKEYS.IS_SIDEBAR_LOADED, }, + sortedAllReportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`, + canEvict: false, + selector: (allReportActions: OnyxEntry<OnyxTypes.ReportActions>) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), + }, reportNameValuePairs: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getReportID(route)}`, allowStaleData: true, @@ -843,6 +866,7 @@ export default withCurrentReportID( ReportScreen, (prevProps, nextProps) => prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && + lodashIsEqual(prevProps.sortedAllReportActions, nextProps.sortedAllReportActions) && lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) && lodashIsEqual(prevProps.betas, nextProps.betas) && lodashIsEqual(prevProps.policies, nextProps.policies) && diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index f84c75823753..b5990ee5d002 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -200,104 +200,81 @@ let reportAction9CreatedDate: string; /** * Sets up a test with a logged in user that has one unread chat from another user. Returns the <App/> test instance. */ -async function signInAndGetAppWithUnreadChat() { +function signInAndGetAppWithUnreadChat(): Promise<void> { // Render the App and sign in as a test user. render(<App />); - await waitForBatchedUpdatesWithAct(); - 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'); - }); - await waitForBatchedUpdatesWithAct(); - - User.subscribeToUserEvents(); - await waitForBatchedUpdates(); - - const TEN_MINUTES_AGO = subMinutes(new Date(), 10); - reportAction3CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 30), CONST.DATE.FNS_DB_FORMAT_STRING); - reportAction9CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 90), CONST.DATE.FNS_DB_FORMAT_STRING); - - // Simulate setting an unread report and personal details - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { - reportID: REPORT_ID, - reportName: CONST.REPORT.DEFAULT_REPORT_NAME, - lastReadTime: reportAction3CreatedDate, - lastVisibleActionCreated: reportAction9CreatedDate, - lastMessageText: 'Test', - participants: {[USER_B_ACCOUNT_ID]: {hidden: false}}, - lastActorAccountID: USER_B_ACCOUNT_ID, - type: CONST.REPORT.TYPE.CHAT, - }); - const createdReportActionID = NumberUtils.rand64().toString(); - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, { - [createdReportActionID]: { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - automatic: false, - created: format(TEN_MINUTES_AGO, CONST.DATE.FNS_DB_FORMAT_STRING), - reportActionID: createdReportActionID, - message: [ - { - style: 'strong', - text: '__FAKE__', - type: 'TEXT', - }, - { - style: 'normal', - text: 'created this report', - type: 'TEXT', + 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 () => { + const TEN_MINUTES_AGO = subMinutes(new Date(), 10); + reportAction3CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 30), CONST.DATE.FNS_DB_FORMAT_STRING); + reportAction9CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 90), CONST.DATE.FNS_DB_FORMAT_STRING); + + // Simulate setting an unread report and personal details + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { + reportID: REPORT_ID, + reportName: CONST.REPORT.DEFAULT_REPORT_NAME, + lastReadTime: reportAction3CreatedDate, + lastVisibleActionCreated: reportAction9CreatedDate, + lastMessageText: 'Test', + participants: {[USER_B_ACCOUNT_ID]: {hidden: false}}, + lastActorAccountID: USER_B_ACCOUNT_ID, + type: CONST.REPORT.TYPE.CHAT, + }); + const createdReportActionID = NumberUtils.rand64().toString(); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, { + [createdReportActionID]: { + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + automatic: false, + created: format(TEN_MINUTES_AGO, CONST.DATE.FNS_DB_FORMAT_STRING), + reportActionID: createdReportActionID, + message: [ + { + style: 'strong', + text: '__FAKE__', + type: 'TEXT', + }, + { + style: 'normal', + text: 'created this report', + type: 'TEXT', + }, + ], }, - ], - }, - 1: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 10), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '1', createdReportActionID), - 2: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 20), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '2', '1'), - 3: TestHelper.buildTestReportComment(reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3', '2'), - 4: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 40), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '4', '3'), - 5: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 50), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '5', '4'), - 6: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 60), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '6', '5'), - 7: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 70), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '7', '6'), - 8: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 80), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '8', '7'), - 9: TestHelper.buildTestReportComment(reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9', '8'), - }); - await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { - [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'), - }); - - // We manually setting the sidebar as loaded since the onLayout event does not fire in tests - AppActions.setSidebarLoaded(); - - await waitForBatchedUpdatesWithAct(); -} - -let lastComment = 'Current User Comment 1'; -async function addComment() { - const num = Number.parseInt(lastComment.slice(-1), 10); - lastComment = `${lastComment.slice(0, -1)}${num + 1}`; - const comment = lastComment; - const reportActionsBefore = (await TestHelper.onyxGet(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`)) as Record<string, ReportAction>; - Report.addComment(REPORT_ID, comment); - const reportActionsAfter = (await TestHelper.onyxGet(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`)) as Record<string, ReportAction>; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const newReportActionID = Object.keys(reportActionsAfter).find((reportActionID) => !reportActionsBefore[reportActionID])!; - await act(() => - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, { - [newReportActionID]: { - previousReportActionID: '9', - }, - }), - ); - await waitForBatchedUpdatesWithAct(); - - // Verify the comment is visible (it will appear twice, once in the LHN and once on the report screen) - expect(screen.getAllByText(comment)[0]).toBeOnTheScreen(); + 1: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 10), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '1', createdReportActionID), + 2: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 20), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '2', '1'), + 3: TestHelper.buildTestReportComment(reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3', '2'), + 4: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 40), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '4', '3'), + 5: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 50), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '5', '4'), + 6: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 60), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '6', '5'), + 7: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 70), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '7', '6'), + 8: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 80), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '8', '7'), + 9: TestHelper.buildTestReportComment(reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9', '8'), + }); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'), + }); + + // We manually setting the sidebar as loaded since the onLayout event does not fire in tests + AppActions.setSidebarLoaded(); + return waitForBatchedUpdatesWithAct(); + }); } -const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); - describe('Unread Indicators', () => { afterEach(() => { jest.clearAllMocks(); @@ -342,6 +319,7 @@ describe('Unread Indicators', () => { expect(reportComments).toHaveLength(9); // Since the last read timestamp is the timestamp of action 3 we should have an unread indicator above the next "unread" action which will // have actionID of 4 + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; @@ -357,6 +335,7 @@ describe('Unread Indicators', () => { .then(async () => { await act(() => transitionEndCB?.()); // Verify the unread indicator is present + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); }) @@ -379,6 +358,7 @@ describe('Unread Indicators', () => { }) .then(() => { // Verify the unread indicator is not present + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(0); // Tap on the chat again @@ -386,6 +366,7 @@ describe('Unread Indicators', () => { }) .then(() => { // Verify the unread indicator is not present + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(0); expect(areYouOnChatListScreen()).toBe(false); @@ -495,6 +476,7 @@ describe('Unread Indicators', () => { }) .then(() => { // Verify the indicator appears above the last action + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; @@ -529,6 +511,7 @@ describe('Unread Indicators', () => { return navigateToSidebarOption(0); }) .then(() => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(0); @@ -537,23 +520,30 @@ describe('Unread Indicators', () => { return waitFor(() => expect(isNewMessagesBadgeVisible()).toBe(false)); })); - it('Keep showing the new line indicator when a new message is created by the current user', async () => { - await signInAndGetAppWithUnreadChat(); - - // Verify we are on the LHN and that the chat shows as unread in the LHN - expect(areYouOnChatListScreen()).toBe(true); + it('Keep showing the new line indicator when a new message is created by the current user', () => + signInAndGetAppWithUnreadChat() + .then(() => { + // Verify we are on the LHN and that the chat shows as unread in the LHN + expect(areYouOnChatListScreen()).toBe(true); - // Navigate to the report and verify the indicator is present - await navigateToSidebarOption(0); - await act(() => transitionEndCB?.()); - let unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); - expect(unreadIndicator).toHaveLength(1); + // Navigate to the report and verify the indicator is present + return navigateToSidebarOption(0); + }) + .then(async () => { + await act(() => transitionEndCB?.()); + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); + const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); + expect(unreadIndicator).toHaveLength(1); - // Leave a comment as the current user and verify the indicator is not removed - await addComment(); - unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); - expect(unreadIndicator).toHaveLength(1); - }); + // Leave a comment as the current user and verify the indicator is removed + Report.addComment(REPORT_ID, 'Current User Comment 1'); + return waitForBatchedUpdates(); + }) + .then(() => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); + const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); + expect(unreadIndicator).toHaveLength(1); + })); xit('Keeps the new line indicator when the user moves the App to the background', () => signInAndGetAppWithUnreadChat() @@ -565,6 +555,7 @@ describe('Unread Indicators', () => { return navigateToSidebarOption(0); }) .then(() => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); @@ -573,6 +564,7 @@ describe('Unread Indicators', () => { }) .then(() => navigateToSidebarOption(0)) .then(() => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(0); @@ -581,6 +573,7 @@ describe('Unread Indicators', () => { return waitForBatchedUpdates(); }) .then(() => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); let unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); @@ -604,10 +597,11 @@ describe('Unread Indicators', () => { signInAndGetAppWithUnreadChat() // Navigate to the chat and simulate leaving a comment from the current user .then(() => navigateToSidebarOption(0)) - .then(() => + .then(() => { // Leave a comment as the current user - addComment(), - ) + Report.addComment(REPORT_ID, 'Current User Comment 1'); + return waitForBatchedUpdates(); + }) .then(() => { // Simulate the response from the server so that the comment can be deleted in this test lastReportAction = reportActions ? CollectionUtils.lastItem(reportActions) : undefined; @@ -625,7 +619,7 @@ describe('Unread Indicators', () => { expect(alternateText).toHaveLength(1); // This message is visible on the sidebar and the report screen, so there are two occurrences. - expect(screen.getAllByText(lastComment)[0]).toBeOnTheScreen(); + expect(screen.getAllByText('Current User Comment 1')[0]).toBeOnTheScreen(); if (lastReportAction) { Report.deleteReportComment(REPORT_ID, lastReportAction); diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index dffb2b4e312a..9ca0969abc6a 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,6 +1,5 @@ import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; -import type {ConnectOptions, OnyxKey} from 'react-native-onyx'; import CONST from '@src/CONST'; import * as Session from '@src/libs/actions/Session'; import HttpUtils from '@src/libs/HttpUtils'; @@ -248,33 +247,5 @@ const createAddListenerMock = () => { return {triggerTransitionEnd, addListener}; }; -/** - * Get an Onyx value. Only for use in tests for now. - */ -async function onyxGet(key: OnyxKey): Promise<Parameters<Required<ConnectOptions<typeof key>>['callback']>[0]> { - return new Promise((resolve) => { - // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs - // @ts-expect-error This does not need more strict type checking as it's only for tests - const connectionID = Onyx.connect({ - key, - callback: (value) => { - Onyx.disconnect(connectionID); - resolve(value); - }, - waitForCollectionCallback: true, - }); - }); -} - export type {MockFetch, FormData}; -export { - assertFormDataMatchesObject, - buildPersonalDetails, - buildTestReportComment, - createAddListenerMock, - getGlobalFetchMock, - setPersonalDetails, - signInWithTestUser, - signOutTestUser, - onyxGet, -}; +export {assertFormDataMatchesObject, buildPersonalDetails, buildTestReportComment, createAddListenerMock, getGlobalFetchMock, setPersonalDetails, signInWithTestUser, signOutTestUser};