|
| 1 | +import {act, renderHook} from '@testing-library/react-native'; |
| 2 | +import useOdometerReadingsState from '@pages/iou/request/step/IOURequestStepDistance/hooks/useOdometerReadingsState'; |
| 3 | +import CONST from '@src/CONST'; |
| 4 | +import type * as OnyxTypes from '@src/types/onyx'; |
| 5 | + |
| 6 | +jest.mock('@libs/actions/OdometerTransactionUtils', () => ({ |
| 7 | + isOdometerDraftPendingHydration: jest.fn(() => false), |
| 8 | +})); |
| 9 | + |
| 10 | +const {isOdometerDraftPendingHydration} = jest.requireMock('@libs/actions/OdometerTransactionUtils') as { |
| 11 | + isOdometerDraftPendingHydration: jest.Mock; |
| 12 | +}; |
| 13 | + |
| 14 | +type Params = Parameters<typeof useOdometerReadingsState>[0]; |
| 15 | + |
| 16 | +const buildOdometerTransaction = (overrides: Partial<OnyxTypes.Transaction['comment']> = {}): OnyxTypes.Transaction => |
| 17 | + ({ |
| 18 | + transactionID: 't1', |
| 19 | + iouRequestType: CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER, |
| 20 | + comment: { |
| 21 | + odometerStart: 100, |
| 22 | + odometerEnd: 250, |
| 23 | + ...overrides, |
| 24 | + }, |
| 25 | + }) as unknown as OnyxTypes.Transaction; |
| 26 | + |
| 27 | +const baseParams: Params = { |
| 28 | + currentTransaction: buildOdometerTransaction(), |
| 29 | + isEditing: false, |
| 30 | + selectedTab: CONST.TAB_REQUEST.DISTANCE_ODOMETER, |
| 31 | + isLoadingSelectedTab: false, |
| 32 | + hasVerifiedBlobs: true, |
| 33 | + odometerDraft: undefined, |
| 34 | +}; |
| 35 | + |
| 36 | +describe('useOdometerReadingsState', () => { |
| 37 | + beforeEach(() => { |
| 38 | + isOdometerDraftPendingHydration.mockReturnValue(false); |
| 39 | + }); |
| 40 | + |
| 41 | + it('starts with empty form state, then hydrates startReading/endReading from the transaction', () => { |
| 42 | + const {result} = renderHook(() => useOdometerReadingsState(baseParams)); |
| 43 | + |
| 44 | + // The sync-from-transaction effect runs synchronously after mount. |
| 45 | + expect(result.current.startReading).toBe('100'); |
| 46 | + expect(result.current.endReading).toBe('250'); |
| 47 | + expect(result.current.formError).toBe(''); |
| 48 | + expect(result.current.startReadingRef.current).toBe('100'); |
| 49 | + expect(result.current.endReadingRef.current).toBe('250'); |
| 50 | + }); |
| 51 | + |
| 52 | + it('captures initial baseline refs once blobs are verified and no draft is pending', () => { |
| 53 | + const {result} = renderHook(() => useOdometerReadingsState(baseParams)); |
| 54 | + |
| 55 | + expect(result.current.hasInitializedRefs.current).toBe(true); |
| 56 | + expect(result.current.initialStartReadingRef.current).toBe('100'); |
| 57 | + expect(result.current.initialEndReadingRef.current).toBe('250'); |
| 58 | + }); |
| 59 | + |
| 60 | + it('does not snapshot the baseline while blobs are still being verified', () => { |
| 61 | + const {result} = renderHook(() => useOdometerReadingsState({...baseParams, hasVerifiedBlobs: false})); |
| 62 | + |
| 63 | + expect(result.current.hasInitializedRefs.current).toBe(false); |
| 64 | + expect(result.current.initialStartReadingRef.current).toBe(''); |
| 65 | + }); |
| 66 | + |
| 67 | + it('does not snapshot the baseline while a save-for-later draft is still pending hydration', () => { |
| 68 | + isOdometerDraftPendingHydration.mockReturnValue(true); |
| 69 | + const {result} = renderHook(() => |
| 70 | + useOdometerReadingsState({ |
| 71 | + ...baseParams, |
| 72 | + odometerDraft: {odometerStartReading: 999} as unknown as OnyxTypes.OdometerDraft, |
| 73 | + }), |
| 74 | + ); |
| 75 | + |
| 76 | + expect(result.current.hasInitializedRefs.current).toBe(false); |
| 77 | + }); |
| 78 | + |
| 79 | + it('skips initialization on a non-odometer transaction unless we are editing', () => { |
| 80 | + const transaction = { |
| 81 | + transactionID: 't1', |
| 82 | + iouRequestType: CONST.IOU.REQUEST_TYPE.DISTANCE, |
| 83 | + comment: {odometerStart: 100, odometerEnd: 250}, |
| 84 | + } as unknown as OnyxTypes.Transaction; |
| 85 | + |
| 86 | + const {result} = renderHook(() => useOdometerReadingsState({...baseParams, currentTransaction: transaction, isEditing: false})); |
| 87 | + |
| 88 | + expect(result.current.hasInitializedRefs.current).toBe(false); |
| 89 | + }); |
| 90 | + |
| 91 | + it('resets local state and the initial-refs flag via resetOdometerLocalState', () => { |
| 92 | + const {result} = renderHook(() => useOdometerReadingsState(baseParams)); |
| 93 | + |
| 94 | + expect(result.current.hasInitializedRefs.current).toBe(true); |
| 95 | + |
| 96 | + act(() => { |
| 97 | + result.current.resetOdometerLocalState(); |
| 98 | + }); |
| 99 | + |
| 100 | + expect(result.current.startReading).toBe(''); |
| 101 | + expect(result.current.endReading).toBe(''); |
| 102 | + expect(result.current.startReadingRef.current).toBe(''); |
| 103 | + expect(result.current.endReadingRef.current).toBe(''); |
| 104 | + expect(result.current.initialStartReadingRef.current).toBe(''); |
| 105 | + expect(result.current.initialEndReadingRef.current).toBe(''); |
| 106 | + expect(result.current.hasInitializedRefs.current).toBe(false); |
| 107 | + }); |
| 108 | + |
| 109 | + it('clears form state and bumps inputKey when user switches away from the odometer tab', () => { |
| 110 | + const {result, rerender} = renderHook((params: Params) => useOdometerReadingsState(params), {initialProps: baseParams}); |
| 111 | + |
| 112 | + const initialKey = result.current.inputKey; |
| 113 | + expect(result.current.startReading).toBe('100'); |
| 114 | + |
| 115 | + rerender({...baseParams, selectedTab: CONST.TAB_REQUEST.DISTANCE}); |
| 116 | + |
| 117 | + expect(result.current.startReading).toBe(''); |
| 118 | + expect(result.current.endReading).toBe(''); |
| 119 | + expect(result.current.formError).toBe(''); |
| 120 | + expect(result.current.inputKey).toBe(initialKey + 1); |
| 121 | + }); |
| 122 | + |
| 123 | + it('does not run the tab-reset effect while the selected-tab Onyx key is still loading', () => { |
| 124 | + const {result, rerender} = renderHook((params: Params) => useOdometerReadingsState(params), { |
| 125 | + initialProps: {...baseParams, isLoadingSelectedTab: true, selectedTab: undefined}, |
| 126 | + }); |
| 127 | + |
| 128 | + const initialKey = result.current.inputKey; |
| 129 | + rerender({...baseParams, isLoadingSelectedTab: true, selectedTab: CONST.TAB_REQUEST.DISTANCE}); |
| 130 | + |
| 131 | + expect(result.current.inputKey).toBe(initialKey); |
| 132 | + }); |
| 133 | +}); |
0 commit comments