Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ function WorkspacesTabButton({selectedTab, isWideLayout}: WorkspacesTabButtonPro
return domains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${paramsDomainAccountID}`];
};
const [lastViewedDomain] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN, {selector: lastViewedDomainSelector}, [navigationState]);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);

const navigateToWorkspaces = () => {
navigateToWorkspacesPage({shouldUseNarrowLayout, currentUserLogin, policy: lastViewedPolicy, domain: lastViewedDomain});
navigateToWorkspacesPage({shouldUseNarrowLayout, currentUserLogin, policy: lastViewedPolicy, domain: lastViewedDomain, activePolicyID});
};

const workspacesAccessibilityState = {selected: selectedTab === NAVIGATION_TABS.WORKSPACES};
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useSearchTypeMenuSections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
const shouldRedirectToExpensifyClassic = useMemo(() => areAllGroupPoliciesExpenseChatDisabled(allPolicies ?? {}), [allPolicies]);
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [pendingReportCreation, setPendingReportCreation] = useState<{policyID: string; policyName?: string; onConfirm: (shouldDismissEmptyReportsConfirmation: boolean) => void} | null>(
null,
);
Expand Down Expand Up @@ -113,6 +114,7 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
defaultExpensifyCard,
shouldRedirectToExpensifyClassic,
draftTransactionIDs,
activePolicyID,
}),
[
currentUserLoginAndAccountID?.email,
Expand All @@ -126,6 +128,7 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
shouldRedirectToExpensifyClassic,
icons,
draftTransactionIDs,
activePolicyID,
],
);

Expand Down
6 changes: 4 additions & 2 deletions src/libs/Navigation/helpers/navigateToWorkspacesPage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {OnyxEntry} from 'react-native-onyx';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import Log from '@libs/Log';
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
Expand All @@ -17,6 +18,7 @@ type Params = {
shouldUseNarrowLayout: boolean;
policy?: Policy;
domain?: Domain;
activePolicyID: OnyxEntry<string>;
};

// Gets the latest workspace navigation state, restoring from session or preserved state if needed.
Expand Down Expand Up @@ -49,7 +51,7 @@ const getWorkspaceNavigationRouteState = () => {
};

// Navigates to the appropriate workspace tab or workspace list page.
const navigateToWorkspacesPage = ({currentUserLogin, shouldUseNarrowLayout, policy, domain}: Params) => {
const navigateToWorkspacesPage = ({currentUserLogin, shouldUseNarrowLayout, policy, domain, activePolicyID}: Params) => {
const {lastWorkspacesTabNavigatorRoute, topmostFullScreenRoute} = getWorkspaceNavigationRouteState();

if (!topmostFullScreenRoute || topmostFullScreenRoute.name === SCREENS.WORKSPACES_LIST) {
Expand Down Expand Up @@ -107,7 +109,7 @@ const navigateToWorkspacesPage = ({currentUserLogin, shouldUseNarrowLayout, poli

// Fallback: any other state, go to the list.
Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
});
}, activePolicyID);
};

export default navigateToWorkspacesPage;
Expand Down
4 changes: 3 additions & 1 deletion src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3671,6 +3671,7 @@ type TypeMenuSectionsParams = {
defaultExpensifyCard: CardFeedForDisplay | undefined;
shouldRedirectToExpensifyClassic: boolean;
draftTransactionIDs: string[] | undefined;
activePolicyID: OnyxEntry<string>;
};

function createTypeMenuSections(params: TypeMenuSectionsParams): SearchTypeMenuSection[] {
Expand All @@ -3686,6 +3687,7 @@ function createTypeMenuSections(params: TypeMenuSectionsParams): SearchTypeMenuS
defaultExpensifyCard,
shouldRedirectToExpensifyClassic,
draftTransactionIDs,
activePolicyID,
} = params;
const typeMenuSections: SearchTypeMenuSection[] = [];

Expand Down Expand Up @@ -3740,7 +3742,7 @@ function createTypeMenuSections(params: TypeMenuSectionsParams): SearchTypeMenuS
}

startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID(), draftTransactionIDs, CONST.IOU.REQUEST_TYPE.SCAN);
});
}, activePolicyID);
},
},
]
Expand Down
12 changes: 9 additions & 3 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
let isHybridAppSetupFinished = false;
let hasSwitchedAccountInHybridMode = false;

Onyx.connect({

Check warning on line 82 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
session = value ?? {};
Expand All @@ -104,28 +104,28 @@
});

let stashedSession: Session = {};
Onyx.connect({

Check warning on line 107 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_SESSION,
callback: (value) => (stashedSession = value ?? {}),
});

let credentials: Credentials = {};
Onyx.connect({

Check warning on line 113 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CREDENTIALS,
callback: (value) => (credentials = value ?? {}),
});

let stashedCredentials: Credentials = {};
Onyx.connect({

Check warning on line 119 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_CREDENTIALS,
callback: (value) => (stashedCredentials = value ?? {}),
});

let activePolicyID: OnyxEntry<string>;
let deprecatedActivePolicyID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 125 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (newActivePolicyID) => {
activePolicyID = newActivePolicyID;
deprecatedActivePolicyID = newActivePolicyID;
},
});

Expand Down Expand Up @@ -322,7 +322,13 @@
ONYXKEYS.IS_USING_IMPORTED_STATE,
];

function signOutAndRedirectToSignIn(shouldResetToHome?: boolean, shouldStashSession?: boolean, shouldSignOutFromOldDot = true, shouldForceUseStashedSession?: boolean) {
function signOutAndRedirectToSignIn(
shouldResetToHome?: boolean,
shouldStashSession?: boolean,
shouldSignOutFromOldDot = true,
shouldForceUseStashedSession?: boolean,
activePolicyID = deprecatedActivePolicyID,
) {
Log.info('Redirecting to Sign In because signOut() was called');
hideContextMenu(false);

Expand Down
10 changes: 5 additions & 5 deletions src/libs/interceptAnonymousUser.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Session from './actions/Session';
import type {OnyxEntry} from 'react-native-onyx';
import {isAnonymousUser, signOutAndRedirectToSignIn} from './actions/Session';

/**
* Checks if user is anonymous. If true, shows the sign in modal, else,
* executes the callback.
*/
const interceptAnonymousUser = (callback: () => void) => {
const isAnonymousUser = Session.isAnonymousUser();
if (isAnonymousUser) {
Session.signOutAndRedirectToSignIn();
const interceptAnonymousUser = (callback: () => void, activePolicyID?: OnyxEntry<string>) => {
if (isAnonymousUser()) {
signOutAndRedirectToSignIn(undefined, undefined, undefined, undefined, activePolicyID);
} else {
callback();
}
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/Search/SearchUIUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
TransactionYearGroupListItemType,
} from '@components/SelectionListWithSections/types';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import Navigation from '@navigation/Navigation';
// eslint-disable-next-line no-restricted-syntax
import type * as ReportUserActions from '@userActions/Report';
Expand Down Expand Up @@ -62,6 +63,7 @@ jest.mock('@userActions/Search', () => ({
setOptimisticDataForTransactionThreadPreview: jest.fn(),
}));
jest.mock('@hooks/useCardFeedsForDisplay', () => jest.fn(() => ({defaultCardFeed: null, cardFeedsByPolicy: {}})));
jest.mock('@libs/interceptAnonymousUser');

const adminAccountID = 18439984;
const adminEmail = 'admin@policy.com';
Expand Down Expand Up @@ -5270,6 +5272,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
})
.map((section) => section.menuItems)
.flat();
Expand Down Expand Up @@ -5361,6 +5364,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const todoSection = sections.find((section) => section.translationPath === 'common.todo');
Expand Down Expand Up @@ -5424,6 +5428,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const monthlyAccrualSection = sections.find((section) => section.translationPath === 'search.monthlyAccrual');
Expand Down Expand Up @@ -5473,6 +5478,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const savedSection = sections.find((section) => section.translationPath === 'search.savedSearchesMenuItemTitle');
Expand All @@ -5495,6 +5501,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const savedSection = sections.find((section) => section.translationPath === 'search.savedSearchesMenuItemTitle');
Expand Down Expand Up @@ -5524,6 +5531,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const savedSection = sections.find((section) => section.translationPath === 'search.savedSearchesMenuItemTitle');
Expand Down Expand Up @@ -5553,6 +5561,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const savedSection = sections.find((section) => section.translationPath === 'search.savedSearchesMenuItemTitle');
Expand Down Expand Up @@ -5586,6 +5595,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const todoSection = sections.find((section) => section.translationPath === 'common.todo');
Expand Down Expand Up @@ -5619,6 +5629,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const monthlyAccrualSection = sections.find((section) => section.translationPath === 'search.monthlyAccrual');
Expand Down Expand Up @@ -5664,6 +5675,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});

const reconciliationSection = sections.find((section) => section.translationPath === 'search.reconciliation');
Expand Down Expand Up @@ -5703,6 +5715,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});
const reconciliationSection = sections.find((section) => section.translationPath === 'search.reconciliation');
expect(reconciliationSection).toBeDefined();
Expand All @@ -5727,6 +5740,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
})
.map((section) => section.menuItems)
.flat();
Expand Down Expand Up @@ -5802,6 +5816,7 @@ describe('SearchUIUtils', () => {
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: '',
});
const todoSection = sections.find((section) => section.translationPath === 'common.todo');
expect(todoSection).toBeDefined();
Expand All @@ -5816,6 +5831,46 @@ describe('SearchUIUtils', () => {
expect(payItem).toBeDefined();
expect(exportItem).toBeDefined();
});

it('should pass activePolicyID to interceptAnonymousUser in submit button action', () => {
const mockPolicies: OnyxCollection<OnyxTypes.Policy> = {
[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]: {
id: policyID,
name: 'Test Policy',
type: CONST.POLICY.TYPE.TEAM,
role: CONST.POLICY.ROLE.ADMIN,
owner: adminEmail,
isPolicyExpenseChatEnabled: true,
pendingAction: undefined,
} as OnyxTypes.Policy,
};

const testActivePolicyID = 'test-active-policy-456';
const {result: icons} = renderHook(() => useMemoizedLazyExpensifyIcons(['Document', 'Send', 'ThumbsUp']));
const sections = SearchUIUtils.createTypeMenuSections({
icons: icons.current,
currentUserEmail: adminEmail,
currentUserAccountID: adminAccountID,
cardFeedsByPolicy: {},
defaultCardFeed: undefined,
policies: mockPolicies,
savedSearches: {},
isOffline: false,
defaultExpensifyCard: undefined,
shouldRedirectToExpensifyClassic: false,
draftTransactionIDs: [],
activePolicyID: testActivePolicyID,
});

const todoSection = sections.find((section) => section.translationPath === 'common.todo');
const submitItem = todoSection?.menuItems.find((item) => item.key === CONST.SEARCH.SEARCH_KEYS.SUBMIT);
const buttonAction = submitItem?.emptyState?.buttons?.[0]?.buttonAction;

if (buttonAction) {
buttonAction();
expect(interceptAnonymousUser).toHaveBeenCalledWith(expect.any(Function), testActivePolicyID);
}
});
});

describe('Test isSearchResultsEmpty', () => {
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/interceptAnonymousUserTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// eslint-disable-next-line no-restricted-syntax
import * as Session from '@libs/actions/Session';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';

jest.mock('@libs/actions/Session', () => ({
isAnonymousUser: jest.fn(),
signOutAndRedirectToSignIn: jest.fn(),
}));

describe('interceptAnonymousUser', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('calls signOutAndRedirectToSignIn with activePolicyID when user is anonymous', () => {
(Session.isAnonymousUser as jest.Mock).mockReturnValue(true);
const callback = jest.fn();

interceptAnonymousUser(callback, 'policy-123');

expect(Session.signOutAndRedirectToSignIn).toHaveBeenCalledWith(undefined, undefined, undefined, undefined, 'policy-123');
expect(callback).not.toHaveBeenCalled();
});

it('calls signOutAndRedirectToSignIn with undefined activePolicyID when not provided', () => {
(Session.isAnonymousUser as jest.Mock).mockReturnValue(true);
const callback = jest.fn();

interceptAnonymousUser(callback);

expect(Session.signOutAndRedirectToSignIn).toHaveBeenCalledWith(undefined, undefined, undefined, undefined, undefined);
expect(callback).not.toHaveBeenCalled();
});

it('executes callback and does not sign out when user is not anonymous', () => {
(Session.isAnonymousUser as jest.Mock).mockReturnValue(false);
const callback = jest.fn();

interceptAnonymousUser(callback, 'policy-123');

expect(callback).toHaveBeenCalledTimes(1);
expect(Session.signOutAndRedirectToSignIn).not.toHaveBeenCalled();
});
});
17 changes: 16 additions & 1 deletion tests/unit/navigateToWorkspacesPageTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jest.mock('@libs/interceptAnonymousUser');

const fakePolicyID = '344559B2CCF2B6C1';
const mockPolicy = {...createRandomPolicy(0), id: fakePolicyID};
const mockParams = {currentUserLogin: 'test@example.com', shouldUseNarrowLayout: false, policy: mockPolicy};
const mockParams = {currentUserLogin: 'test@example.com', shouldUseNarrowLayout: false, policy: mockPolicy, activePolicyID: 'test-active-policy-id'};

describe('navigateToWorkspacesPage', () => {
beforeEach(() => {
Expand Down Expand Up @@ -160,6 +160,21 @@ describe('navigateToWorkspacesPage', () => {
expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACES_LIST.route);
});

it('passes activePolicyID to interceptAnonymousUser', () => {
(navigationRef.getRootState as jest.Mock).mockReturnValue({
routes: [{name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR}],
});

(lastVisitedTabPathUtils.getWorkspacesTabStateFromSessionStorage as jest.Mock).mockReturnValue({
routes: [{name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR}],
});

(interceptAnonymousUser as jest.Mock).mockImplementation(() => {});
navigateToWorkspacesPage({...mockParams, activePolicyID: 'my-policy-123'});

expect(interceptAnonymousUser).toHaveBeenCalledWith(expect.any(Function), 'my-policy-123');
});

it('navigates to WORKSPACES_LIST if policyID is missing', () => {
(navigationRef.getRootState as jest.Mock).mockReturnValue({
routes: [{name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR}],
Expand Down
Loading