diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 4967083833bf..37b9184a3250 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -701,6 +701,7 @@ function ReportScreen({ {shouldShowReportActionList && ( { + (reportAction: OnyxTypes.ReportAction, index: number, lastReadTime?: string, shouldCheckWithCurrentUnreadMarker?: boolean): boolean => { + // if lastReadTime is null, use the lastReadTimeRef.current + const baseLastReadTime = lastReadTime ?? lastReadTimeRef.current; let shouldDisplay = false; - if (!currentUnreadMarker) { + if (!currentUnreadMarker || !shouldCheckWithCurrentUnreadMarker) { const nextMessage = sortedVisibleReportActions[index + 1]; - const isCurrentMessageUnread = isMessageUnread(reportAction, lastReadTimeRef.current); - shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, lastReadTimeRef.current)) && !ReportActionsUtils.shouldHideNewMarker(reportAction); + const isCurrentMessageUnread = isMessageUnread(reportAction, baseLastReadTime); + shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, baseLastReadTime)) && !ReportActionsUtils.shouldHideNewMarker(reportAction); if (shouldDisplay && !messageManuallyMarkedUnread) { const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < (userActiveSince.current ?? '') : true; // Prevent displaying a new marker line when report action is of type "REPORT_PREVIEW" and last actor is the current user @@ -487,21 +489,37 @@ function ReportActionsList({ return ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report); }, [parentReportAction, report, sortedVisibleReportActions]); + // storing the last read time used to render the unread marker + const markerLastReadTimeRef = useRef(); + + useEffect(() => { + if (currentUnreadMarker) { + return; + } + markerLastReadTimeRef.current = undefined; + }, [currentUnreadMarker]); + const calculateUnreadMarker = useCallback(() => { // Iterate through the report actions and set appropriate unread marker. // This is to avoid a warning of: // Cannot update a component (ReportActionsList) while rendering a different component (CellRenderer). let markerFound = false; sortedVisibleReportActions.forEach((reportAction, index) => { - if (!shouldDisplayNewMarker(reportAction, index)) { + if (!shouldDisplayNewMarker(reportAction, index, markerLastReadTimeRef.current, false)) { return; } markerFound = true; - if (!currentUnreadMarker && currentUnreadMarker !== reportAction.reportActionID) { + if (!currentUnreadMarker || currentUnreadMarker !== reportAction.reportActionID) { cacheUnreadMarkers.set(report.reportID, reportAction.reportActionID); setCurrentUnreadMarker(reportAction.reportActionID); } }); + + // if marker can be found, set the markerLastReadTimeRef to the last read time if necessary + if (markerFound && !markerLastReadTimeRef.current) { + markerLastReadTimeRef.current = lastReadTimeRef.current; + } + if (!markerFound && !linkedReportActionID) { setCurrentUnreadMarker(null); } @@ -570,7 +588,7 @@ function ReportActionsList({ displayAsGroup={ReportActionsUtils.isConsecutiveActionMadeByPreviousActor(sortedVisibleReportActions, index)} mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} - shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} + shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index, markerLastReadTimeRef.current, true)} shouldDisplayReplyDivider={sortedVisibleReportActions.length > 1} isFirstVisibleReportAction={firstVisibleReportActionID === reportAction.reportActionID} shouldUseThreadDividerLine={shouldUseThreadDividerLine} diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 7b820281a5a6..24433d36f0c3 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -463,6 +463,52 @@ describe('Unread Indicators', () => { expect(displayNameTexts[1]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); expect(screen.getByText('B User')).toBeOnTheScreen(); })); + it('Delete a chat message and verify the unread indicator is moved', async () => { + const getUnreadIndicator = () => { + const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); + return screen.queryAllByLabelText(newMessageLineIndicatorHintText); + }; + + return signInAndGetAppWithUnreadChat() + .then(() => navigateToSidebarOption(0)) + .then(async () => act(() => transitionEndCB?.())) + .then(async () => { + const reportActionsViewWrapper = await screen.findByTestId('report-actions-view-wrapper'); + if (reportActionsViewWrapper) { + fireEvent(reportActionsViewWrapper, 'onLayout', {nativeEvent: {layout: {x: 0, y: 0, width: 100, height: 100}}}); + } + return waitForBatchedUpdates(); + }) + .then(() => { + // Verify the new line indicator is present, and it's before the action with ID 4 + const unreadIndicator = getUnreadIndicator(); + expect(unreadIndicator).toHaveLength(1); + const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; + expect(reportActionID).toBe('4'); + + // simulate delete comment event from Pusher + PusherHelper.emitOnyxUpdate([ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + value: { + '4': { + message: [], + }, + }, + }, + ]); + return waitForBatchedUpdates(); + }) + .then(() => + // Verify the new line indicator is now before the action with ID 5 + waitFor(() => { + const unreadIndicator = getUnreadIndicator(); + const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; + expect(reportActionID).toBe('5'); + }), + ); + }); xit('Manually marking a chat message as unread shows the new line indicator and updates the LHN', () => signInAndGetAppWithUnreadChat()