diff --git a/src/libs/TryNewDotUtils.ts b/src/libs/TryNewDotUtils.ts index 10e692afa63f..6cfa5a30f26b 100644 --- a/src/libs/TryNewDotUtils.ts +++ b/src/libs/TryNewDotUtils.ts @@ -1,11 +1,24 @@ +import {differenceInDays} from 'date-fns'; import type {OnyxEntry} from 'react-native-onyx'; import type {TryNewDot} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +const NEW_DOT_MIN_DAYS_BEFORE_HIDING_CLASSIC_REDIRECT = 30; + function isLockedToNewApp(tryNewDot: OnyxEntry): boolean { return tryNewDot?.isLockedToNewApp === true; } +function hasBeenInNewDot30Days(tryNewDot: OnyxEntry): boolean { + const classicRedirect = tryNewDot?.classicRedirect; + if (!classicRedirect || classicRedirect.dismissed !== false || !classicRedirect.timestamp) { + return false; + } + + const daysSinceNudge = differenceInDays(new Date(), new Date(classicRedirect.timestamp)); + return daysSinceNudge >= NEW_DOT_MIN_DAYS_BEFORE_HIDING_CLASSIC_REDIRECT; +} + function shouldBlockOldAppExit(tryNewDot: OnyxEntry, isLoadingTryNewDot: boolean, shouldSetNVP: boolean): boolean { if (isLockedToNewApp(tryNewDot)) { return true; @@ -15,7 +28,7 @@ function shouldBlockOldAppExit(tryNewDot: OnyxEntry, isLoadingTryNewD } function isOldAppRedirectBlocked(tryNewDot: OnyxEntry, shouldRespectMobileLock: boolean): boolean { - return tryNewDot?.classicRedirect?.isLockedToNewDot === true || (shouldRespectMobileLock && isLockedToNewApp(tryNewDot)); + return tryNewDot?.classicRedirect?.isLockedToNewDot === true || hasBeenInNewDot30Days(tryNewDot) || (shouldRespectMobileLock && isLockedToNewApp(tryNewDot)); } function shouldHideOldAppRedirect(tryNewDot: OnyxEntry, isLoadingTryNewDot: boolean, shouldRespectMobileLock: boolean): boolean { @@ -34,4 +47,4 @@ function shouldUseOldApp(tryNewDot: TryNewDot): boolean | undefined { return tryNewDot.classicRedirect.dismissed; } -export {isLockedToNewApp, isOldAppRedirectBlocked, shouldBlockOldAppExit, shouldHideOldAppRedirect, shouldUseOldApp}; +export {hasBeenInNewDot30Days, isLockedToNewApp, isOldAppRedirectBlocked, shouldBlockOldAppExit, shouldHideOldAppRedirect, shouldUseOldApp}; diff --git a/tests/unit/TryNewDotUtilsTest.ts b/tests/unit/TryNewDotUtilsTest.ts index 76b7e20678d7..a2f4244aed33 100644 --- a/tests/unit/TryNewDotUtilsTest.ts +++ b/tests/unit/TryNewDotUtilsTest.ts @@ -1,5 +1,6 @@ +import {subDays} from 'date-fns'; import Onyx from 'react-native-onyx'; -import {isOldAppRedirectBlocked, shouldBlockOldAppExit, shouldHideOldAppRedirect, shouldUseOldApp} from '@src/libs/TryNewDotUtils'; +import {hasBeenInNewDot30Days, isOldAppRedirectBlocked, shouldBlockOldAppExit, shouldHideOldAppRedirect, shouldUseOldApp} from '@src/libs/TryNewDotUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {TryNewDot} from '@src/types/onyx'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -68,6 +69,71 @@ describe('TryNewDotUtils', () => { expect(shouldBlockOldAppExit({isLockedToNewApp: true} as TryNewDot, false, false)).toBe(true); }); + it('blocks the OldDot redirect when the classicRedirect nudge has gone stale', () => { + const tryNewDot = { + classicRedirect: { + dismissed: false, + timestamp: subDays(new Date(), 31).toISOString(), + }, + } as unknown as TryNewDot; + + expect(isOldAppRedirectBlocked(tryNewDot, false)).toBe(true); + }); + + it('still shows the OldDot redirect when the classicRedirect nudge is fresh', () => { + const tryNewDot = { + classicRedirect: { + dismissed: false, + timestamp: subDays(new Date(), 5).toISOString(), + }, + } as unknown as TryNewDot; + + expect(isOldAppRedirectBlocked(tryNewDot, false)).toBe(false); + }); + + it('reports that a user has been in NewDot 30 days when the nudge is over a month old and not dismissed', () => { + const tryNewDot = { + classicRedirect: { + dismissed: false, + timestamp: subDays(new Date(), 31).toISOString(), + }, + } as unknown as TryNewDot; + + expect(hasBeenInNewDot30Days(tryNewDot)).toBe(true); + }); + + it('does not report 30 days in NewDot when the nudge is less than a month old', () => { + const tryNewDot = { + classicRedirect: { + dismissed: false, + timestamp: subDays(new Date(), 10).toISOString(), + }, + } as unknown as TryNewDot; + + expect(hasBeenInNewDot30Days(tryNewDot)).toBe(false); + }); + + it('does not report 30 days in NewDot once the user has dismissed the nudge', () => { + const tryNewDot = { + classicRedirect: { + dismissed: true, + timestamp: subDays(new Date(), 60).toISOString(), + }, + } as unknown as TryNewDot; + + expect(hasBeenInNewDot30Days(tryNewDot)).toBe(false); + }); + + it('does not report 30 days in NewDot when no timestamp is set', () => { + const tryNewDot = { + classicRedirect: { + dismissed: false, + }, + } as unknown as TryNewDot; + + expect(hasBeenInNewDot30Days(tryNewDot)).toBe(false); + }); + it('preserves isLockedToNewApp when nvp_tryNewDot is merged', async () => { await Onyx.set(ONYXKEYS.NVP_TRY_NEW_DOT, { isLockedToNewApp: true,