diff --git a/src/pages/inbox/report/ReportActionsList/ReportActionsListContent.tsx b/src/pages/inbox/report/ReportActionsList/ReportActionsListContent.tsx index c2ede5315711..49e072508eb7 100644 --- a/src/pages/inbox/report/ReportActionsList/ReportActionsListContent.tsx +++ b/src/pages/inbox/report/ReportActionsList/ReportActionsListContent.tsx @@ -45,6 +45,7 @@ import React, {useState} from 'react'; import type {ReportActionsListProps} from './types'; +import filterHiddenSupportTicketSurveys from './filterHiddenSupportTicketSurveys'; import useReportActionsListData from './useReportActionsListData'; import useReportActionsListRenderItem from './useReportActionsListRenderItem'; import useReportActionsListScroll from './useReportActionsListScroll'; @@ -66,6 +67,7 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps) hasNewerActions, oldestUnreadReportAction, transactionThreadReport, + sortedAllReportActions, sortedReportActions: unfilteredReportActions, sortedVisibleReportActions: unfilteredVisibleReportActions, isConciergeHiddenHistory, @@ -74,8 +76,8 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps) } = useReportActionsListState(); // Remove rows hidden by the renderer before calculating summary counts, headers, and scroll positions. - const sortedReportActions = report?.isWaitingOnBankAccount ? unfilteredReportActions.filter((action) => !shouldHidePayAction(action, true)) : unfilteredReportActions; - const sortedVisibleReportActions = report?.isWaitingOnBankAccount + const paymentFilteredReportActions = report?.isWaitingOnBankAccount ? unfilteredReportActions.filter((action) => !shouldHidePayAction(action, true)) : unfilteredReportActions; + const paymentFilteredVisibleReportActions = report?.isWaitingOnBankAccount ? unfilteredVisibleReportActions.filter((action) => !shouldHidePayAction(action, true)) : unfilteredVisibleReportActions; @@ -95,6 +97,19 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps) useRetireMerchantRuleSuggestionOnLeave(reportID); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`); + // Hidden surveys must not become unread targets that have no rendered row. + const sortedReportActions = filterHiddenSupportTicketSurveys({ + reportActions: paymentFilteredReportActions, + sortedAllReportActions, + report, + reportNameValuePairs, + }); + const sortedVisibleReportActions = filterHiddenSupportTicketSurveys({ + reportActions: paymentFilteredVisibleReportActions, + sortedAllReportActions, + report, + reportNameValuePairs, + }); const isReportArchived = !!isArchivedReport(reportNameValuePairs); const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(report?.policyID)}`); @@ -139,7 +154,6 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps) const reportActionsListData = useReportActionsListData({ sortedVisibleReportActions, - reportNameValuePairs, sessionStartTime, linkedReportActionID, unreadMarkerReportActionID, @@ -149,7 +163,8 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps) const {listID, listProps, shouldBeAlignedToTop, isFloatingMessageCounterVisible, isActionBadgeAboveViewport, scrollToBottomAndMarkReportAsRead, scrollToActionBadgeTarget} = useReportActionsListScroll({ reportID, - sortedVisibleReportActions, + // Hidden surveys still prove that the selected page contains the newest action. + sortedVisibleReportActions: paymentFilteredVisibleReportActions, renderedVisibleReportActions: listData, reportActionIDToDisplayIndex, hasScrolledOverThreshold, diff --git a/src/pages/inbox/report/ReportActionsList/filterHiddenSupportTicketSurveys.ts b/src/pages/inbox/report/ReportActionsList/filterHiddenSupportTicketSurveys.ts new file mode 100644 index 000000000000..3fe2ec02b130 --- /dev/null +++ b/src/pages/inbox/report/ReportActionsList/filterHiddenSupportTicketSurveys.ts @@ -0,0 +1,32 @@ +/** Keeps survey eligibility consistent between unread selection and rendered rows. */ +import {isResolvedSupportTicket, isSupportTicket} from '@libs/ReportUtils'; + +import CONST from '@src/CONST'; +import type {Report, ReportAction, ReportNameValuePairs} from '@src/types/onyx'; + +import type {OnyxEntry} from 'react-native-onyx'; + +type FilterHiddenSupportTicketSurveysParams = { + reportActions: ReportAction[]; + sortedAllReportActions: ReportAction[] | undefined; + report: OnyxEntry; + reportNameValuePairs: OnyxEntry; +}; + +function filterHiddenSupportTicketSurveys({reportActions, sortedAllReportActions, report, reportNameValuePairs}: FilterHiddenSupportTicketSurveysParams): ReportAction[] { + if (!isSupportTicket(report)) { + return reportActions; + } + + const shouldHideAllSurveys = !isResolvedSupportTicket(report) || !!reportNameValuePairs?.reopenedAsReportID; + const latestResolution = shouldHideAllSurveys ? undefined : sortedAllReportActions?.find((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED); + const latestSurvey = latestResolution + ? sortedAllReportActions?.find((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY && action.created >= latestResolution.created) + : undefined; + const shouldHideSurvey = (action: ReportAction) => + action.actionName === CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY && (shouldHideAllSurveys || action.reportActionID !== latestSurvey?.reportActionID); + + return reportActions.some(shouldHideSurvey) ? reportActions.filter((action) => !shouldHideSurvey(action)) : reportActions; +} + +export default filterHiddenSupportTicketSurveys; diff --git a/src/pages/inbox/report/ReportActionsList/types.ts b/src/pages/inbox/report/ReportActionsList/types.ts index 2e9aac5c8da5..c77e6863c03b 100644 --- a/src/pages/inbox/report/ReportActionsList/types.ts +++ b/src/pages/inbox/report/ReportActionsList/types.ts @@ -18,7 +18,6 @@ type ReportActionsListProps = { type UseReportActionsListDataParams = { sortedVisibleReportActions: OnyxTypes.ReportAction[]; - reportNameValuePairs: OnyxEntry; sessionStartTime: string | null; linkedReportActionID: string | undefined; unreadMarkerReportActionID: string | null; @@ -57,8 +56,9 @@ type UseReportActionsListRenderItemParams = Pick< | 'draftReportActionID' | 'isDraftPendingCompletion' > & - Pick & + Pick & Pick & { + reportNameValuePairs: OnyxEntry; isReportArchived: boolean; isOffline: boolean; sortedReportActions: OnyxTypes.ReportAction[]; diff --git a/src/pages/inbox/report/ReportActionsList/useReportActionsListData.ts b/src/pages/inbox/report/ReportActionsList/useReportActionsListData.ts index fbdb294336ac..f7296b1de56e 100644 --- a/src/pages/inbox/report/ReportActionsList/useReportActionsListData.ts +++ b/src/pages/inbox/report/ReportActionsList/useReportActionsListData.ts @@ -1,5 +1,5 @@ import {getReportActionMessage, isTransactionThread} from '@libs/ReportActionsUtils'; -import {isExpenseReport, isIOUReport, isInvoiceReport, isResolvedSupportTicket, isSupportTicket} from '@libs/ReportUtils'; +import {isExpenseReport, isIOUReport, isInvoiceReport} from '@libs/ReportUtils'; import {useConciergeDraft, useConciergeDraftActions} from '@pages/inbox/ConciergeDraftContext'; import {useReportActionsListState} from '@pages/inbox/report/ReportActionsListContext'; @@ -13,8 +13,8 @@ import type {UseReportActionsListDataParams} from './types'; import getReportActionsWithStreamedReply from './getReportActionsWithStreamedReply'; -/** Prepares the rendered report rows, including streamed Concierge drafts, support surveys, and collapsed system messages. */ -function useReportActionsListData({sortedVisibleReportActions, reportNameValuePairs, sessionStartTime, linkedReportActionID, unreadMarkerReportActionID}: UseReportActionsListDataParams) { +/** Prepares the rendered report rows, including streamed Concierge drafts and collapsed system messages. */ +function useReportActionsListData({sortedVisibleReportActions, sessionStartTime, linkedReportActionID, unreadMarkerReportActionID}: UseReportActionsListDataParams) { const {report, sortedAllReportActions, parentReportAction, isConciergeHiddenHistory, showFullHistory} = useReportActionsListState(); const {draftReportAction, isDraftPendingCompletion} = useConciergeDraft(); const {clearDraft, revealDraftFromReportAction} = useConciergeDraftActions(); @@ -26,14 +26,8 @@ function useReportActionsListData({sortedVisibleReportActions, reportNameValuePa ) : undefined; - const shouldHideSupportTicketSurvey = isSupportTicket(report) && (!isResolvedSupportTicket(report) || !!reportNameValuePairs?.reopenedAsReportID); - const latestResolvedSupportTicketAction = sortedAllReportActions?.find((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED); - const latestSupportTicketSurveyAction = latestResolvedSupportTicketAction - ? sortedAllReportActions?.find((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY && action.created >= latestResolvedSupportTicketAction.created) - : undefined; - const shouldHideDraft = !!(showHiddenHistory && sessionStartTime && draftReportAction && draftReportAction.created < sessionStartTime); - const {reportActionsWithStreamedReply, isSyntheticDraftVisible} = getReportActionsWithStreamedReply({ + const {reportActionsWithStreamedReply: renderedVisibleReportActions, isSyntheticDraftVisible} = getReportActionsWithStreamedReply({ sortedVisibleReportActions, draftReportAction, isDraftPendingCompletion, @@ -41,21 +35,6 @@ function useReportActionsListData({sortedVisibleReportActions, reportNameValuePa shouldHideDraft, }); - const shouldFilterSupportTicketSurveys = - isSupportTicket(report) && - reportActionsWithStreamedReply.some( - (action) => - action.actionName === CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY && - (shouldHideSupportTicketSurvey || action.reportActionID !== latestSupportTicketSurveyAction?.reportActionID), - ); - const renderedVisibleReportActions = shouldFilterSupportTicketSurveys - ? reportActionsWithStreamedReply.filter( - (action) => - action.actionName !== CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY || - (!shouldHideSupportTicketSurvey && action.reportActionID === latestSupportTicketSurveyAction?.reportActionID), - ) - : reportActionsWithStreamedReply; - const shouldCollapseSystemMessages = isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report) || isTransactionThread(parentReportAction); const { displayReportActions, diff --git a/tests/ui/ReportActionsListTest.tsx b/tests/ui/ReportActionsListTest.tsx index f98ba0dea090..277834adb4a9 100644 --- a/tests/ui/ReportActionsListTest.tsx +++ b/tests/ui/ReportActionsListTest.tsx @@ -1,22 +1,30 @@ import {act, render, screen, waitFor} from '@testing-library/react-native'; +import {LocaleContextProvider} from '@components/LocaleContextProvider'; + import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useIsReportLoadPending} from '@hooks/useInFlightRequests'; /* eslint-disable @typescript-eslint/no-unsafe-type-assertion */ import type * as InFlightRequests from '@hooks/useInFlightRequests'; import useIsInSidePanel from '@hooks/useIsInSidePanel'; import useNetwork from '@hooks/useNetwork'; +import * as NetworkWithOfflineStatus from '@hooks/useNetworkWithOfflineStatus'; import useOnyx from '@hooks/useOnyx'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useParentReportAction from '@hooks/useParentReportAction'; +import usePrevious from '@hooks/usePrevious'; import type useReportActionsScroll from '@hooks/useReportActionsScroll'; +import * as ReportScrollManager from '@hooks/useReportScrollManager'; import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSidePanelState from '@hooks/useSidePanelState'; import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport'; +import type useUnreadMarker from '@hooks/useUnreadMarker'; import DateUtils from '@libs/DateUtils'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import Visibility from '@libs/Visibility'; import {useActionListRef} from '@pages/inbox/ActionListContext'; import type * as ActionListContextModule from '@pages/inbox/ActionListContext'; @@ -25,6 +33,9 @@ import {useConciergeDraft, useConciergeDraftActions} from '@pages/inbox/Concierg import CollapsedSystemMessages from '@pages/inbox/report/CollapsedSystemMessages'; import ReportActionsList from '@pages/inbox/report/ReportActionsList'; import ReportActionsListItemRenderer from '@pages/inbox/report/ReportActionsListItemRenderer'; +import {getUnreadMarkerReportAction} from '@pages/inbox/report/shouldDisplayNewMarkerOnReportAction'; +import * as ReportActionsNewActionLiveTail from '@pages/inbox/report/useReportActionsNewActionLiveTail'; +import * as ReportUnreadMessageScrollTracking from '@pages/inbox/report/useReportUnreadMessageScrollTracking'; import useScrollToEditingReportAction from '@pages/inbox/report/useScrollToEditingReportAction'; import CONST from '@src/CONST'; @@ -366,6 +377,207 @@ describe('ReportActionsList (body)', () => { await Onyx.clear(); }); + describe('Support survey unread markers', () => { + it.each([ + {hasUnreadMarker: true, hasNewestPage: true}, + {hasUnreadMarker: false, hasNewestPage: true}, + {hasUnreadMarker: true, hasNewestPage: false}, + {hasUnreadMarker: false, hasNewestPage: false}, + ])('handles a hidden newest survey (latest page loaded: $hasNewestPage, unread marker: $hasUnreadMarker)', ({hasUnreadMarker, hasNewestPage}) => { + // Given a cached survey newer than the reply, but not necessarily in the loaded page + const reply = {...mockReportActions[1], created: '2023-01-01 00:04:00.000'}; + const survey = {...reply, reportActionID: 'hidden-survey', actionName: CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY, created: '2023-01-01 00:05:00.000'}; + const supportReport = {...mockReport, type: CONST.REPORT.TYPE.SUPPORT_TICKET, lastVisibleActionCreated: survey.created}; + mockUseNetwork.mockReturnValue({isOffline: false}); + mockUseOnyx.mockImplementation((key, options) => + key === `${ONYXKEYS.COLLECTION.REPORT}${mockReport.reportID}` ? [supportReport, {status: 'loaded'}] : getMockOnyxValue(key, options), + ); + mockUsePaginatedReportActions.mockReturnValue({ + ...defaultPaginatedReportActionsResult, + reportActions: hasNewestPage ? [survey, reply] : [reply], + sortedAllReportActions: [survey, reply], + hasNewerActions: !hasNewestPage, + }); + mockUseUnreadMarker.mockReturnValue({unreadMarkerReportActionID: hasUnreadMarker ? reply.reportActionID : null, unreadMarkerReportActionIndex: hasUnreadMarker ? 0 : -1}); + const markNewestActionAsRead = jest.fn(); + mockUseMarkAsRead.mockReturnValue({markNewestActionAsRead, completeSkippedMarkAsRead: jest.fn()}); + const scrollToBottom = jest.fn(); + const scrollToIndex = jest.fn(); + const openReportAtLiveTail = jest.fn(); + const activeRouteSpy = jest.spyOn(Navigation, 'getReportRHPActiveRoute').mockReturnValue(''); + const navigateSpy = jest.spyOn(Navigation, 'navigate').mockImplementation(() => {}); + const scrollManagerSpy = jest.spyOn(ReportScrollManager, 'default').mockReturnValue(createMock>({scrollToBottom, scrollToIndex})); + const trackingSpy = jest + .spyOn(ReportUnreadMessageScrollTracking, 'default') + .mockReturnValue(createMock>({setIsFloatingMessageCounterVisible: jest.fn()})); + const liveTailSpy = jest + .spyOn(ReportActionsNewActionLiveTail, 'default') + .mockReturnValue(createMock>({openReportAtLiveTail})); + const defaultScrollImplementation = mockUseReportActionsScroll.getMockImplementation(); + mockUseReportActionsScroll.mockImplementation(jest.requireActual<{default: typeof useReportActionsScroll}>('@hooks/useReportActionsScroll').default); + + try { + // When the user clicks the counter or Latest with the actual scroll hook mounted + renderReportActionsList(); + const scrollResult = mockUseReportActionsScroll.mock.results.at(-1)?.value as ReturnType | undefined; + act(() => scrollResult?.scrollToBottomAndMarkReportAsRead()); + + // Then only a loaded latest page is marked read; a cached survey alone cannot prove freshness + expect(getCapturedVisibleActions()).not.toContain(survey); + expect(markNewestActionAsRead).toHaveBeenCalledTimes(hasNewestPage ? 1 : 0); + expect(openReportAtLiveTail).toHaveBeenCalledTimes(!hasNewestPage && !hasUnreadMarker ? 1 : 0); + if (hasUnreadMarker) { + expect(scrollToIndex).toHaveBeenCalledWith(0); + } else { + expect(scrollToBottom).toHaveBeenCalled(); + } + } finally { + scrollManagerSpy.mockRestore(); + trackingSpy.mockRestore(); + liveTailSpy.mockRestore(); + activeRouteSpy.mockRestore(); + navigateSpy.mockRestore(); + mockUseReportActionsScroll.mockReset(); + if (defaultScrollImplementation) { + mockUseReportActionsScroll.mockImplementation(defaultScrollImplementation); + } + } + }); + + it('keeps the offline unread divider above the reply when a newer survey is hidden', () => { + // Given a reply received offline and a newer hidden survey + const supportReport = {...mockReport, type: CONST.REPORT.TYPE.SUPPORT_TICKET, lastReadTime: '2023-01-01 00:02:00.000'}; + const readComment = {...mockReportActions[1], reportActionID: 'read-comment', created: '2023-01-01 00:01:00.000'}; + const offlineReply = {...readComment, reportActionID: 'offline-reply', created: '2023-01-01 00:04:00.000'}; + const survey = {...readComment, reportActionID: 'hidden-survey', actionName: CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY, created: '2023-01-01 00:05:00.000'}; + const actions = [survey, offlineReply, readComment]; + mockUseCurrentUserPersonalDetails.mockReturnValue({...mockUseCurrentUserPersonalDetails(), timezone: {selected: 'Africa/Abidjan', automatic: false}}); + mockUseNetwork.mockReturnValue({isOffline: true}); + const networkSpy = jest.spyOn(NetworkWithOfflineStatus, 'default').mockReturnValue({ + isOffline: true, + lastOfflineAt: {current: new Date('2023-01-01T00:03:00.000Z')}, + lastOnlineAt: {current: new Date('2023-01-01T00:02:00.000Z')}, + }); + mockUseOnyx.mockImplementation((key, options) => { + if (key === `${ONYXKEYS.COLLECTION.REPORT}${mockReport.reportID}`) { + return [options?.selector ? (options.selector as (report: OnyxTypes.Report) => OnyxTypes.Report | string | undefined)(supportReport) : supportReport, {status: 'loaded'}]; + } + return getMockOnyxValue(key, options); + }); + mockUsePaginatedReportActions.mockReturnValue({...defaultPaginatedReportActionsResult, reportActions: actions, sortedAllReportActions: actions}); + mockUseUnreadMarker.mockImplementation(jest.requireActual<{default: typeof useUnreadMarker}>('@hooks/useUnreadMarker').default); + jest.mocked(usePrevious).mockImplementation(jest.requireActual<{default: typeof usePrevious}>('@hooks/usePrevious').default); + const focusSpy = jest.spyOn(Visibility, 'hasFocus').mockReturnValue(false); + + try { + // When the real unread hook selects the offline boundary in the rendered list + render( + + + , + ); + + // Then New belongs to the unread reply, not the older read comment + expect(getCapturedVisibleActions()).not.toContain(survey); + expect(mockUseReportActionsScroll.mock.lastCall?.[0].unreadMarkerReportActionID).toBe(offlineReply.reportActionID); + expect(mockUseReportActionsScroll.mock.lastCall?.[0].unreadMarkerReportActionIndex).toBe(0); + } finally { + focusSpy.mockRestore(); + networkSpy.mockRestore(); + jest.mocked(usePrevious).mockReset(); + } + }); + + it.each(['unresolved', 'replaced', 'resolved'] as const)('places the unread marker on a visible reply when the ticket is %s', (ticketState) => { + // Given an unread survey from a previous resolution before an unread support reply + const supportReport = { + ...mockReport, + type: CONST.REPORT.TYPE.SUPPORT_TICKET, + stateNum: ticketState === 'unresolved' ? CONST.REPORT.STATE_NUM.OPEN : CONST.REPORT.STATE_NUM.APPROVED, + statusNum: ticketState === 'unresolved' ? CONST.REPORT.STATUS_NUM.OPEN : CONST.REPORT.STATUS_NUM.CLOSED, + lastReadTime: '2023-01-01 00:02:00.000', + }; + const readComment = { + ...mockReportActions[1], + reportActionID: 'read-comment', + created: '2023-01-01 00:01:00.000', + }; + const obsoleteSurvey = { + ...readComment, + reportActionID: 'obsolete-survey', + actionName: CONST.REPORT.ACTIONS.TYPE.SUPPORT_SURVEY, + created: '2023-01-01 00:03:00.000', + }; + const unreadReply = { + ...readComment, + reportActionID: 'unread-reply', + created: '2023-01-01 00:04:00.000', + }; + const resolution = { + ...readComment, + reportActionID: 'resolution', + actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, + created: '2023-01-01 00:05:00.000', + }; + const latestSurvey = { + ...obsoleteSurvey, + reportActionID: 'latest-survey', + created: '2023-01-01 00:06:00.000', + }; + const actions = ticketState === 'unresolved' ? [unreadReply, obsoleteSurvey, readComment] : [latestSurvey, resolution, unreadReply, obsoleteSurvey, readComment]; + mockUseNetwork.mockReturnValue({isOffline: false}); + mockUseOnyx.mockImplementation((key, options) => { + if (key === `${ONYXKEYS.COLLECTION.REPORT}${mockReport.reportID}`) { + return [supportReport, {status: 'loaded'}]; + } + if (key === `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${mockReport.reportID}`) { + return [ticketState === 'replaced' ? {reopenedAsReportID: 'replacement-ticket'} : {}, {status: 'loaded'}]; + } + return getMockOnyxValue(key, options); + }); + mockUsePaginatedReportActions.mockReturnValue({ + ...defaultPaginatedReportActionsResult, + reportActions: actions, + sortedAllReportActions: actions, + }); + mockUseUnreadMarker.mockImplementation(({sortedVisibleReportActions}: Parameters[0]) => { + const [unreadMarkerReportActionID, unreadMarkerReportActionIndex] = getUnreadMarkerReportAction({ + visibleReportActions: sortedVisibleReportActions, + earliestReceivedOfflineMessageIndex: undefined, + currentUserAccountID: 100, + prevSortedVisibleReportActionsObjects: {}, + unreadMarkerTime: supportReport.lastReadTime, + isScrolledOverThreshold: false, + isOffline: false, + isReversed: false, + hasWindowFocus: false, + }); + return { + unreadMarkerReportActionID, + unreadMarkerReportActionIndex, + }; + }); + + // When the ticket opens with unread messages + renderReportActionsList(); + + // Then the hidden survey cannot take the divider away from the visible reply + const visibleActions = getCapturedVisibleActions(); + expect(visibleActions).not.toContain(obsoleteSurvey); + expect(visibleActions?.includes(latestSurvey)).toBe(ticketState === 'resolved'); + expect(mockUseReportActionsScroll.mock.lastCall?.[0].unreadMarkerReportActionID).toBe(unreadReply.reportActionID); + expect(mockUseReportActionsScroll.mock.lastCall?.[0].unreadMarkerReportActionIndex).toBe(visibleActions?.indexOf(unreadReply)); + const row = findRenderedElement>( + getCapturedListProps()?.renderItem?.({ + item: unreadReply, + index: visibleActions?.indexOf(unreadReply) ?? -1, + }), + ReportActionsListItemRenderer, + ); + expect(row?.props.shouldDisplayNewMarker).toBe(true); + }); + }); + describe('System message presentation', () => { const systemActions = ( [