diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index c99dd892e1b0..fdb173f69117 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -11,7 +11,6 @@ import {getEmptyObject} from '@src/types/utils/EmptyObject'; import mapOnyxCollectionItems from '@src/utils/mapOnyxCollectionItems'; import useCurrentReportID from './useCurrentReportID'; import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; -import useDeepCompareRef from './useDeepCompareRef'; import useDiffPrevious from './useDiffPrevious'; import useLocalize from './useLocalize'; import useOnyx from './useOnyx'; @@ -177,14 +176,12 @@ function SidebarOrderedReportsContextProvider({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [getUpdatedReports, chatReports, derivedCurrentReportID, priorityMode, betas, policies, transactionViolations, reportNameValuePairs, reportAttributes, reportsDraftsUpdates]); - const deepComparedReportsToDisplayInLHN = useDeepCompareRef(reportsToDisplayInLHN); - useEffect(() => { setCurrentReportsToDisplay(reportsToDisplayInLHN); }, [reportsToDisplayInLHN]); const getOrderedReportIDs = useCallback( - () => SidebarUtils.sortReportsToDisplayInLHN(deepComparedReportsToDisplayInLHN ?? {}, priorityMode, localeCompare, reportNameValuePairs, reportAttributes, drafts), + () => SidebarUtils.sortReportsToDisplayInLHN(reportsToDisplayInLHN, priorityMode, localeCompare, reportNameValuePairs, reportAttributes, drafts), // Rule disabled intentionally - reports should be sorted only when the reportsToDisplayInLHN changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps [reportsToDisplayInLHN, localeCompare], diff --git a/tests/unit/useSidebarOrderedReportsTest.tsx b/tests/unit/useSidebarOrderedReportsTest.tsx deleted file mode 100644 index 9b0aff784821..000000000000 --- a/tests/unit/useSidebarOrderedReportsTest.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import {renderHook} from '@testing-library/react-native'; -import React from 'react'; -import type {OnyxMultiSetInput} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import OnyxListItemProvider from '@components/OnyxListItemProvider'; -import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID'; -import {SidebarOrderedReportsContextProvider, useSidebarOrderedReports} from '@hooks/useSidebarOrderedReports'; -import SidebarUtils from '@libs/SidebarUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; -import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; - -// Mock dependencies -jest.mock('@libs/SidebarUtils', () => ({ - sortReportsToDisplayInLHN: jest.fn(), - getReportsToDisplayInLHN: jest.fn(), - updateReportsToDisplayInLHN: jest.fn(), -})); -jest.mock('@libs/Navigation/Navigation', () => ({ - getTopmostReportId: jest.fn(), -})); -jest.mock('@libs/ReportUtils', () => ({ - parseReportRouteParams: jest.fn(() => ({reportID: undefined})), - getReportIDFromLink: jest.fn(() => ''), -})); - -const mockSidebarUtils = SidebarUtils as jest.Mocked; - -describe('useSidebarOrderedReports', () => { - beforeAll(async () => { - Onyx.init({keys: ONYXKEYS}); - // Set up basic session data - await Onyx.set(ONYXKEYS.SESSION, { - accountID: 12345, - email: 'test@example.com', - authTokenType: CONST.AUTH_TOKEN_TYPES.ANONYMOUS, - }); - return waitForBatchedUpdates(); - }); - - beforeEach(async () => { - jest.clearAllMocks(); - Onyx.clear(); - - // Set up basic session data for each test - await Onyx.set(ONYXKEYS.SESSION, { - accountID: 12345, - email: 'test@example.com', - authTokenType: CONST.AUTH_TOKEN_TYPES.ANONYMOUS, - }); - - // Set up required Onyx data that the hook depends on - await Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, - [ONYXKEYS.COLLECTION.REPORT]: {}, - [ONYXKEYS.COLLECTION.POLICY]: {}, - [ONYXKEYS.COLLECTION.TRANSACTION]: {}, - [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: {}, - [ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS]: {}, - [ONYXKEYS.NVP_DRAFT_REPORT_COMMENTS]: {}, - [ONYXKEYS.BETAS]: [], - [ONYXKEYS.DERIVED.REPORT_ATTRIBUTES]: {reports: {}}, - } as unknown as OnyxMultiSetInput); - - // Default mock implementations - mockSidebarUtils.getReportsToDisplayInLHN.mockImplementation(() => ({})); - mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation((prev) => prev); - mockSidebarUtils.sortReportsToDisplayInLHN.mockReturnValue([]); - - return waitForBatchedUpdates(); - }); - - afterAll(async () => { - Onyx.clear(); - await waitForBatchedUpdates(); - }); - - const createMockReports = (reports: Record>) => { - const mockReports: Record = {}; - Object.entries(reports).forEach(([key, report]) => { - const reportId = key.replace('report', ''); - mockReports[reportId] = { - reportID: reportId, - reportName: `Report ${reportId}`, - lastVisibleActionCreated: '2024-01-01 10:00:00', - type: CONST.REPORT.TYPE.CHAT, - ...report, - } as Report; - }); - return mockReports; - }; - - let currentReportIDForTestsValue: string | undefined; - - function TestWrapper({children}: {children: React.ReactNode}) { - return ( - - - {children} - - - ); - } - - it('should prevent unnecessary re-renders when reports have same content but different references', () => { - // Given reports with same content but different object references - const reportsContent = { - report1: {reportName: 'Chat 1', lastVisibleActionCreated: '2024-01-01 10:00:00'}, - report2: {reportName: 'Chat 2', lastVisibleActionCreated: '2024-01-01 11:00:00'}, - }; - - // When the initial reports are set - const initialReports = createMockReports(reportsContent); - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(initialReports); - mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation((prev) => ({...prev})); - currentReportIDForTestsValue = '1'; - - // When the hook is rendered - const {rerender} = renderHook(() => useSidebarOrderedReports(), { - wrapper: TestWrapper, - }); - - // Then the mock calls are cleared - mockSidebarUtils.sortReportsToDisplayInLHN.mockClear(); - - // When the reports are updated - const newReportsWithSameContent = createMockReports(reportsContent); - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(newReportsWithSameContent); - - rerender({}); - - // Then sortReportsToDisplayInLHN should not be called again since deep comparison shows no change - expect(mockSidebarUtils.sortReportsToDisplayInLHN).not.toHaveBeenCalled(); - }); - - it('should trigger re-render when reports content actually changes', async () => { - // Given the initial reports are set - const initialReports = createMockReports({ - report1: {reportName: 'Chat 1'}, - report2: {reportName: 'Chat 2'}, - }); - - // When the reports are updated - const updatedReports = createMockReports({ - report1: {reportName: 'Chat 1 Updated'}, // Content changed - report2: {reportName: 'Chat 2'}, - report3: {reportName: 'Chat 3'}, // New report added - }); - - // Then the initial reports are set - await Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT}1`]: initialReports['1'], - [`${ONYXKEYS.COLLECTION.REPORT}2`]: initialReports['2'], - } as unknown as OnyxMultiSetInput); - - // When the mock is updated - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(initialReports); - - // When the hook is rendered - const {rerender} = renderHook(() => useSidebarOrderedReports(), { - wrapper: TestWrapper, - }); - - await waitForBatchedUpdates(); - - // Then the mock calls are cleared - mockSidebarUtils.sortReportsToDisplayInLHN.mockClear(); - - // When the mock is updated - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(updatedReports); - - // When the priority mode is changed - await Onyx.set(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.GSD); - - rerender({}); - - await waitForBatchedUpdates(); - - // Then sortReportsToDisplayInLHN should be called with the updated reports - expect(mockSidebarUtils.sortReportsToDisplayInLHN).toHaveBeenCalledWith( - updatedReports, - expect.any(String), // priorityMode - expect.any(Function), // localeCompare - expect.any(Object), // reportNameValuePairs - expect.any(Object), // reportAttributes - expect.any(Object), // drafts - ); - }); - - it('should handle empty reports correctly with deep comparison', async () => { - // Given the initial reports are set - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue({}); - - // When the hook is rendered - const {rerender} = renderHook(() => useSidebarOrderedReports(), { - wrapper: TestWrapper, - }); - - await waitForBatchedUpdates(); - - // Then the mock calls are cleared - mockSidebarUtils.sortReportsToDisplayInLHN.mockClear(); - - // When the mock is updated - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue({}); - - rerender({}); - - await waitForBatchedUpdates(); - - // Then sortReportsToDisplayInLHN should not be called again since reports are empty - expect(mockSidebarUtils.sortReportsToDisplayInLHN).not.toHaveBeenCalled(); - }); - - it('should maintain referential stability across multiple renders with same content', () => { - // Given the initial reports are set - const reportsContent = { - report1: {reportName: 'Stable Chat'}, - }; - - // When the initial reports are set - const initialReports = createMockReports(reportsContent); - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(initialReports); - mockSidebarUtils.sortReportsToDisplayInLHN.mockReturnValue(['1']); - currentReportIDForTestsValue = '1'; - - const {rerender} = renderHook(() => useSidebarOrderedReports(), { - wrapper: TestWrapper, - }); - - // When the mock is updated - const newReportsWithSameContent = createMockReports(reportsContent); - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(newReportsWithSameContent); - - rerender({}); - currentReportIDForTestsValue = '2'; - - // When the mock is updated - const thirdReportsWithSameContent = createMockReports(reportsContent); - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(thirdReportsWithSameContent); - - rerender({}); - currentReportIDForTestsValue = '3'; - - // Then sortReportsToDisplayInLHN should be called only once (initial render) - expect(mockSidebarUtils.sortReportsToDisplayInLHN).toHaveBeenCalledTimes(1); - }); - - it('should handle priority mode changes correctly with deep comparison', async () => { - // Given the initial reports are set - const reports = createMockReports({ - report1: {reportName: 'Chat A'}, - report2: {reportName: 'Chat B'}, - }); - - mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(reports); - currentReportIDForTestsValue = '1'; - - // When the hook is rendered - const {rerender} = renderHook(() => useSidebarOrderedReports(), { - wrapper: TestWrapper, - }); - - await waitForBatchedUpdates(); - - // Then the mock calls are cleared - mockSidebarUtils.sortReportsToDisplayInLHN.mockClear(); - currentReportIDForTestsValue = '2'; - - // When the priority mode is changed - await Onyx.set(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.GSD); - - rerender({}); - - await waitForBatchedUpdates(); - - // Then sortReportsToDisplayInLHN should be called when priority mode changes - expect(mockSidebarUtils.sortReportsToDisplayInLHN).toHaveBeenCalled(); - }); -});