Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand",
"perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure",
"typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc",
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=325 --cache --cache-location=node_modules/.cache/eslint",
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=322 --cache --cache-location=node_modules/.cache/eslint",
"lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh",
"lint-watch": "npx eslint-watch --watch --changed",
"shellcheck": "./scripts/shellCheck.sh",
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/useIOUUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import DateUtils from '@libs/DateUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import useOnyx from './useOnyx';

function useIOUUtils() {
const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true});
function shouldStartLocationPermissionFlow() {
return (
!lastLocationPermissionPrompt ||
(DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') &&
DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS)
);
}

return {shouldStartLocationPermissionFlow};
}

export default useIOUUtils;
18 changes: 0 additions & 18 deletions src/libs/IOUUtils.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx';
import type {Attendee} from '@src/types/onyx/IOU';
import type {IOURequestType} from './actions/IOU';
import {getCurrencyUnit} from './CurrencyUtils';
import DateUtils from './DateUtils';
import Navigation from './Navigation/Navigation';
import Performance from './Performance';
import {getReportTransactions} from './ReportUtils';
import {getCurrency, getTagArrayFromName} from './TransactionUtils';

let lastLocationPermissionPrompt: string;
Onyx.connect({
key: ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT,
callback: (val) => (lastLocationPermissionPrompt = val ?? ''),
});

function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: IOUType, transactionID: string, reportID: string, iouAction?: IOUAction): void {
if (iouAction === CONST.IOU.ACTION.CATEGORIZE || iouAction === CONST.IOU.ACTION.SUBMIT || iouAction === CONST.IOU.ACTION.SHARE) {
Navigation.goBack();
Expand Down Expand Up @@ -214,14 +205,6 @@ function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: s
return [initialAttendee];
}

function shouldStartLocationPermissionFlow() {
return (
!lastLocationPermissionPrompt ||
(DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') &&
DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS)
);
}

export {
calculateAmount,
insertTagIntoTransactionTagsString,
Expand All @@ -232,6 +215,5 @@ export {
navigateToStartMoneyRequestStep,
updateIOUOwnerAndTotal,
formatCurrentUserToAttendee,
shouldStartLocationPermissionFlow,
navigateToParticipantPage,
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Text from '@components/Text';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import useFilesValidation from '@hooks/useFilesValidation';
import useIOUUtils from '@hooks/useIOUUtils';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
Expand All @@ -38,7 +39,7 @@ import getPlatform from '@libs/getPlatform';
import type Platform from '@libs/getPlatform/types';
import getReceiptsUploadFolderPath from '@libs/getReceiptsUploadFolderPath';
import HapticFeedback from '@libs/HapticFeedback';
import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
import {navigateToParticipantPage} from '@libs/IOUUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
Expand Down Expand Up @@ -117,6 +118,7 @@ function IOURequestStepScan({
const [didCapturePhoto, setDidCapturePhoto] = useState(false);
const [shouldShowMultiScanEducationalPopup, setShouldShowMultiScanEducationalPopup] = useState(false);
const [cameraKey, setCameraKey] = useState(0);
const {shouldStartLocationPermissionFlow} = useIOUUtils();

const defaultTaxCode = getDefaultTaxCode(policy, initialTransaction);
const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? '';
Expand Down Expand Up @@ -602,7 +604,7 @@ function IOURequestStepScan({
}
navigateToConfirmationStep(files, false);
},
[initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation],
[shouldSkipConfirmation, navigateToConfirmationStep, initialTransaction, iouType, shouldStartLocationPermissionFlow],
);

const capturePhoto = useCallback(() => {
Expand Down
6 changes: 4 additions & 2 deletions src/pages/iou/request/step/IOURequestStepScan/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import useFilesValidation from '@hooks/useFilesValidation';
import useIOUUtils from '@hooks/useIOUUtils';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
Expand All @@ -38,7 +39,7 @@ import {dismissProductTraining} from '@libs/actions/Welcome';
import {isMobile, isMobileWebKit} from '@libs/Browser';
import {base64ToFile, isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils';
import getCurrentPosition from '@libs/getCurrentPosition';
import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils';
import {navigateToParticipantPage} from '@libs/IOUUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
Expand Down Expand Up @@ -119,6 +120,7 @@ function IOURequestStepScan({
const isEditing = action === CONST.IOU.ACTION.EDIT;
const canUseMultiScan = !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo);
const {shouldStartLocationPermissionFlow} = useIOUUtils();

const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {
selector: (items) => Object.values(items ?? {}),
Expand Down Expand Up @@ -670,7 +672,7 @@ function IOURequestStepScan({
}
navigateToConfirmationStep(files, false);
},
[initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation],
[initialTransaction, iouType, shouldStartLocationPermissionFlow, navigateToConfirmationStep, shouldSkipConfirmation],
);

const getScreenshot = useCallback(() => {
Expand Down
116 changes: 116 additions & 0 deletions tests/unit/useIOUUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {renderHook} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
import useIOUUtils from '@src/hooks/useIOUUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';

describe('useIOUUtils', () => {
beforeAll(() => {
Onyx.init({
keys: ONYXKEYS,
});
});

beforeEach(async () => {
await Onyx.clear();
});

describe('shouldStartLocationPermissionFlow', () => {
const now = new Date();
const daysAgo = (days: number) => {
const d = new Date(now);
d.setDate(d.getDate() - days);
return d.toISOString();
};

it('returns true when lastLocationPermissionPrompt is undefined', async () => {
const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);
});

it('returns true when lastLocationPermissionPrompt is null', async () => {
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, null);
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);
});

it('returns true when lastLocationPermissionPrompt is empty string', async () => {
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, '');
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);
});

it('returns false when lastLocationPermissionPrompt is a valid date string within threshold', async () => {
const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1);
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, recentDate);
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(false);
});

it('returns true when lastLocationPermissionPrompt is a valid date string outside threshold', async () => {
const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1);
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, oldDate);
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);
});

it('returns false when lastLocationPermissionPrompt is an invalid date string', async () => {
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, 'not-a-date');
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(false);
});

it('returns false when lastLocationPermissionPrompt is exactly at threshold', async () => {
const thresholdDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS);
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, thresholdDate);
await waitForBatchedUpdatesWithAct();

const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(false);
});

it('reacts to changes in lastLocationPermissionPrompt', async () => {
const {result} = renderHook(() => useIOUUtils());
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);

const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1);
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, recentDate);
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(false);

const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1);
Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, oldDate);
await waitForBatchedUpdatesWithAct();

expect(result.current.shouldStartLocationPermissionFlow()).toBe(true);
});
});
});
Loading