diff --git a/src/components/Search/SearchRouter/useCreateNavigationSuggestions.ts b/src/components/Search/SearchRouter/useCreateNavigationSuggestions.ts index c221b69fca07..73223e784536 100644 --- a/src/components/Search/SearchRouter/useCreateNavigationSuggestions.ts +++ b/src/components/Search/SearchRouter/useCreateNavigationSuggestions.ts @@ -23,7 +23,7 @@ import getCreateReportRoute, {getReportsRootRoute, navigateToCreateReportWorkspa import Navigation from '@libs/Navigation/Navigation'; import {openTravelDotLink} from '@libs/openTravelDotLink'; // eslint-disable-next-line no-restricted-imports -- TravelDot booking requires a paid workspace, matching the existing FAB behavior. -import {canSendInvoice, getDefaultChatEnabledPolicy, getGroupPoliciesWhereReportCanBeCreated, hasAcceptedTravelTerms, isPaidGroupPolicy, shouldShowPolicy} from '@libs/PolicyUtils'; +import {canSendInvoice, getGroupPoliciesWhereReportCanBeCreated, hasAcceptedTravelTerms, isPaidGroupPolicy, shouldShowPolicy} from '@libs/PolicyUtils'; import {generateReportID} from '@libs/ReportUtils'; import isOnSearchMoneyRequestReportPage from '@navigation/helpers/isOnSearchMoneyRequestReportPage'; @@ -37,8 +37,11 @@ import {primaryLoginSelector} from '@src/selectors/Account'; import {isTrackIntentUserSelector} from '@src/selectors/Onboarding'; import {emailSelector} from '@src/selectors/Session'; import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; +import type {Policy} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; +import type {OnyxEntry} from 'react-native-onyx'; + import {Str} from 'expensify-common'; import {useState} from 'react'; @@ -113,7 +116,6 @@ function useCreateNavigationSuggestions(query = ''): NavigationSuggestionSourceI const [isLoading = false] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [rules] = useOnyx(ONYXKEYS.COLLECTION.RULE); - const defaultChatEnabledPolicy = getDefaultChatEnabledPolicy([...groupPoliciesWithChatEnabled], activePolicy); const isInvoiceVisible = canSendInvoice(allPolicies ?? null, sessionEmail); const isTravelVisible = !!activePolicy?.isTravelEnabled; const isBlockedFromSpotnanaTravel = isBetaEnabled(CONST.BETAS.PREVENT_SPOTNANA_TRAVEL); @@ -141,8 +143,8 @@ function useCreateNavigationSuggestions(query = ''): NavigationSuggestionSourceI ); const {createReport, isVisible: isCreateReportVisible} = useCreateReport({ - onCreateReport: (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!defaultChatEnabledPolicy?.id) { + onCreateReport: (policy: OnyxEntry, shouldDismissEmptyReportsConfirmation?: boolean) => { + if (!policy?.id) { return; } @@ -156,7 +158,7 @@ function useCreateNavigationSuggestions(query = ''): NavigationSuggestionSourceI currentUserPersonalDetails, false, isBetaEnabled(CONST.BETAS.ASAP_SUBMIT), - defaultChatEnabledPolicy, + policy, isTrackIntentUser, getCurrencyDecimals, rules, diff --git a/src/hooks/useCreateReport.tsx b/src/hooks/useCreateReport.tsx index a67a94f32316..d321978152ed 100644 --- a/src/hooks/useCreateReport.tsx +++ b/src/hooks/useCreateReport.tsx @@ -18,11 +18,12 @@ import {useCallback} from 'react'; import useCreateEmptyReportConfirmation from './useCreateEmptyReportConfirmation'; import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; import useOnyx from './useOnyx'; +import usePreferredPolicy from './usePreferredPolicy'; import useShouldShowEmptyReportConfirmation from './useShouldShowEmptyReportConfirmation'; type UseCreateReportParams = { - /** Callback that creates the report and navigates after creation */ - onCreateReport: (shouldDismissEmptyReportsConfirmation?: boolean) => void; + /** Callback that creates the report on the resolved workspace and navigates after creation */ + onCreateReport: (policy: OnyxEntry, shouldDismissEmptyReportsConfirmation?: boolean) => void; /** Group paid policies with expense chat enabled */ groupPoliciesWithChatEnabled: readonly never[] | Array>; /** Optional custom navigation to the workspace selector */ @@ -64,6 +65,7 @@ export default function useCreateReport({ const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const {accountID} = useCurrentUserPersonalDetails(); + const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); // Gate visibility and routing on policy hydration. Without this, during Onyx cold-start // groupPoliciesWithChatEnabled.length === 0 would be true even for users who actually have @@ -72,7 +74,10 @@ export default function useCreateReport({ const isVisible = arePoliciesLoaded; const shouldNavigateToUpgradePath = groupPoliciesWithChatEnabled.length === 0; - const defaultChatEnabledPolicy = getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array>, activePolicy); + // A domain security group can lock the user to a preferred workspace. It then takes precedence over the active policy and the selector is skipped. + const lockedPreferredPolicy = isRestrictedToPreferredPolicy ? groupPoliciesWithChatEnabled.find((policy) => policy?.id === preferredPolicyID) : undefined; + const isLockedToPreferredPolicy = !!lockedPreferredPolicy; + const defaultChatEnabledPolicy = lockedPreferredPolicy ?? getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array>, activePolicy) ?? undefined; const defaultChatEnabledPolicyID = defaultChatEnabledPolicy?.id; const shouldShowEmptyReportConfirmation = useShouldShowEmptyReportConfirmation(defaultChatEnabledPolicyID, shouldSkipEmptyReportConfirmation); @@ -80,7 +85,7 @@ export default function useCreateReport({ const {openCreateReportConfirmation} = useCreateEmptyReportConfirmation({ policyID: defaultChatEnabledPolicyID, policyName: defaultChatEnabledPolicy?.name ?? '', - onConfirm: onCreateReport, + onConfirm: (shouldDismissEmptyReportsConfirmation: boolean) => onCreateReport(defaultChatEnabledPolicy, shouldDismissEmptyReportsConfirmation), shouldHandleNavigationBack, }); @@ -118,8 +123,9 @@ export default function useCreateReport({ const hasMultipleNonPersonalWorkspaces = groupPoliciesWithChatEnabled.length > 1; const isDefaultBillingRestricted = !!workspaceIDForReportCreation && shouldRestrictUserBillableActions(defaultChatEnabledPolicy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, accountID); + const shouldOfferAlternatives = !isLockedToPreferredPolicy && hasMultipleNonPersonalWorkspaces && (isDefaultPersonal || isDefaultBillingRestricted); - if (!workspaceIDForReportCreation || (isDefaultPersonal && hasMultipleNonPersonalWorkspaces) || (isDefaultBillingRestricted && hasMultipleNonPersonalWorkspaces)) { + if (!workspaceIDForReportCreation || shouldOfferAlternatives) { if (onNavigateToWorkspaceSelection) { onNavigateToWorkspaceSelection(); } else { @@ -133,7 +139,7 @@ export default function useCreateReport({ if (shouldShowEmptyReportConfirmation) { openCreateReportConfirmation(); } else { - onCreateReport(false); + onCreateReport(defaultChatEnabledPolicy, false); } return; } @@ -150,6 +156,7 @@ export default function useCreateReport({ userBillingGracePeriodEnds, amountOwed, accountID, + isLockedToPreferredPolicy, groupPoliciesWithChatEnabled.length, onNavigateToWorkspaceSelection, shouldShowEmptyReportConfirmation, diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index f32e4d4a8902..7ff8db3414e5 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -24,7 +24,7 @@ import {startTestDrive} from '@libs/actions/Tour'; import DateUtils from '@libs/DateUtils'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; -import {canSendInvoice, getDefaultChatEnabledPolicy, getGroupPoliciesWhereReportCanBeCreated} from '@libs/PolicyUtils'; +import {canSendInvoice, getGroupPoliciesWhereReportCanBeCreated} from '@libs/PolicyUtils'; import {generateReportID, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils'; import {getAllPolicyValues, getFilterFromQuery, isDefaultExpenseReportsQuery, isDefaultExpensesQuery, isSearchBeforeViolationsSnapshotStarted} from '@libs/SearchQueryUtils'; import {TODO_SEARCH_KEYS} from '@libs/SearchUIUtils'; @@ -157,8 +157,6 @@ function EmptySearchViewContent({ const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); const [rules] = useOnyx(ONYXKEYS.COLLECTION.RULE); - const defaultChatEnabledPolicy = getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array>, activePolicy); - const filteredPolicyID = getFilterFromQuery(queryJSON, CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID); let isFilteredWorkspaceAccessible = true; if (filteredPolicyID.value) { @@ -167,8 +165,8 @@ function EmptySearchViewContent({ isFilteredWorkspaceAccessible = !!filteredPolicy; } - const handleCreateWorkspaceReport = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!defaultChatEnabledPolicy?.id) { + const handleCreateWorkspaceReport = (policy: OnyxEntry, shouldDismissEmptyReportsConfirmation?: boolean) => { + if (!policy?.id) { return; } @@ -176,7 +174,7 @@ function EmptySearchViewContent({ currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, - defaultChatEnabledPolicy, + policy, isTrackIntentUser, getCurrencyDecimals, rules, diff --git a/src/pages/inbox/sidebar/FABPopoverContent/menuItems/CreateReportMenuItem.tsx b/src/pages/inbox/sidebar/FABPopoverContent/menuItems/CreateReportMenuItem.tsx index 8ae38e89c110..b8b619c0062a 100644 --- a/src/pages/inbox/sidebar/FABPopoverContent/menuItems/CreateReportMenuItem.tsx +++ b/src/pages/inbox/sidebar/FABPopoverContent/menuItems/CreateReportMenuItem.tsx @@ -10,7 +10,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import {createNewReport} from '@libs/actions/Report'; import getCreateReportRoute, {getReportsRootRoute, navigateToCreateReportWorkspaceSelection} from '@libs/Navigation/helpers/getCreateReportRoute'; import Navigation from '@libs/Navigation/Navigation'; -import {getDefaultChatEnabledPolicy, getGroupPoliciesWhereReportCanBeCreated} from '@libs/PolicyUtils'; +import {getGroupPoliciesWhereReportCanBeCreated} from '@libs/PolicyUtils'; import {hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils'; import isOnSearchMoneyRequestReportPage from '@navigation/helpers/isOnSearchMoneyRequestReportPage'; @@ -31,16 +31,13 @@ import React from 'react'; const ITEM_ID = CONST.FAB_MENU_ITEM_IDS.CREATE_REPORT; -// Returns up to 2 matching policies const chatEnabledPaidGroupPoliciesSelector = (policies: OnyxCollection, currentUserLogin: string | undefined) => - getGroupPoliciesWhereReportCanBeCreated(policies, currentUserLogin).slice(0, 2); + getGroupPoliciesWhereReportCanBeCreated(policies, currentUserLogin); function CreateReportMenuItem() { - const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const icons = useMemoizedLazyExpensifyIcons(['Document']); - const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: sessionEmailAndAccountIDSelector}); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); @@ -54,12 +51,10 @@ function CreateReportMenuItem() { const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); const [rules] = useOnyx(ONYXKEYS.COLLECTION.RULE); - const defaultChatEnabledPolicy = getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array>, activePolicy); - const isReportInSearch = isOnSearchMoneyRequestReportPage(); - const handleCreateWorkspaceReport = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!defaultChatEnabledPolicy?.id) { + const handleCreateWorkspaceReport = (policy: OnyxEntry, shouldDismissEmptyReportsConfirmation?: boolean) => { + if (!policy?.id) { return; } @@ -71,7 +66,7 @@ function CreateReportMenuItem() { currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, - defaultChatEnabledPolicy, + policy, isTrackIntentUser, getCurrencyDecimals, rules, diff --git a/tests/unit/CreateReportMenuItemTest.tsx b/tests/unit/CreateReportMenuItemTest.tsx index c5cdad8abd48..deb1063f2298 100644 --- a/tests/unit/CreateReportMenuItemTest.tsx +++ b/tests/unit/CreateReportMenuItemTest.tsx @@ -68,7 +68,6 @@ jest.mock('@libs/PolicyUtils', () => { const CONSTANTS = jest.requireActual<{default: typeof CONST}>('@src/CONST').default; return { - getDefaultChatEnabledPolicy: jest.fn((policies: Policy[]) => policies.at(0)), getGroupPoliciesWhereReportCanBeCreated: jest.fn((policies: Record | undefined) => Object.values(policies ?? {}).filter( (policy): policy is Policy => @@ -101,21 +100,21 @@ function makePolicy(id: string, type: Policy['type']): Policy { } as Policy; } -function setupUseOnyx() { +function setupUseOnyx(activePolicyID = 'personal-1') { const personalPolicy = makePolicy('personal-1', CONST.POLICY.TYPE.PERSONAL); const groupPolicy = makePolicy('team-1', CONST.POLICY.TYPE.TEAM); const submitPolicy = makePolicy('submit-1', CONST.POLICY.TYPE.SUBMIT); + const corporatePolicy = makePolicy('corporate-1', CONST.POLICY.TYPE.CORPORATE); + const policies = { + [`${ONYXKEYS.COLLECTION.POLICY}${personalPolicy.id}`]: personalPolicy, + [`${ONYXKEYS.COLLECTION.POLICY}${groupPolicy.id}`]: groupPolicy, + [`${ONYXKEYS.COLLECTION.POLICY}${submitPolicy.id}`]: submitPolicy, + [`${ONYXKEYS.COLLECTION.POLICY}${corporatePolicy.id}`]: corporatePolicy, + }; const values = new Map([ - [ONYXKEYS.NVP_ACTIVE_POLICY_ID, personalPolicy.id], - [`${ONYXKEYS.COLLECTION.POLICY}${personalPolicy.id}`, personalPolicy], - [ - ONYXKEYS.COLLECTION.POLICY, - { - [`${ONYXKEYS.COLLECTION.POLICY}${personalPolicy.id}`]: personalPolicy, - [`${ONYXKEYS.COLLECTION.POLICY}${groupPolicy.id}`]: groupPolicy, - [`${ONYXKEYS.COLLECTION.POLICY}${submitPolicy.id}`]: submitPolicy, - }, - ], + [ONYXKEYS.NVP_ACTIVE_POLICY_ID, activePolicyID], + [`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, policies[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]], + [ONYXKEYS.COLLECTION.POLICY, policies], [ONYXKEYS.SESSION, {accountID: 1, email: 'user@test.com'}], [ONYXKEYS.BETAS, []], [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {}], @@ -134,14 +133,19 @@ describe('CreateReportMenuItem', () => { setupUseOnyx(); }); - it('passes only report-creation workspaces to useCreateReport', () => { + it.each([ + ['the personal workspace is active', 'personal-1'], + ['a workspace beyond the first two eligible ones is active', 'corporate-1'], + ])('passes every report-creation workspace to useCreateReport when %s', (_description, activePolicyID) => { + setupUseOnyx(activePolicyID); + render(); const params = mockUseCreateReport.mock.calls.at(0)?.at(0); - expect(params?.groupPoliciesWithChatEnabled).toHaveLength(2); expect(params?.groupPoliciesWithChatEnabled).toEqual([ expect.objectContaining({id: 'team-1', type: CONST.POLICY.TYPE.TEAM}), expect.objectContaining({id: 'submit-1', type: CONST.POLICY.TYPE.SUBMIT}), + expect.objectContaining({id: 'corporate-1', type: CONST.POLICY.TYPE.CORPORATE}), ]); }); }); diff --git a/tests/unit/useCreateNavigationSuggestionsTest.ts b/tests/unit/useCreateNavigationSuggestionsTest.ts index c0dcfd84b0e4..c88258dac7aa 100644 --- a/tests/unit/useCreateNavigationSuggestionsTest.ts +++ b/tests/unit/useCreateNavigationSuggestionsTest.ts @@ -15,7 +15,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; type MockUseCreateReportParams = { - onCreateReport: (shouldDismissEmptyReportsConfirmation?: boolean) => void; + onCreateReport: (policy: unknown, shouldDismissEmptyReportsConfirmation?: boolean) => void; groupPoliciesWithChatEnabled: unknown[] | readonly never[]; onNavigateToWorkspaceSelection: () => void; shouldHandleNavigationBack: boolean; @@ -37,7 +37,6 @@ const mockUseOnyx = jest.fn const isBetaEnabledByDefault = (beta: string) => beta !== CONST.BETAS.PREVENT_SPOTNANA_TRAVEL; const mockIsBetaEnabled = jest.fn(isBetaEnabledByDefault); const mockCanSendInvoice = jest.fn(() => false); -const mockGetDefaultChatEnabledPolicy = jest.fn((policies: unknown[]) => (policies.length === 1 ? policies.at(0) : undefined)); const mockGetGroupPoliciesWhereReportCanBeCreated = jest.fn(); const mockShouldShowPolicy = jest.fn(() => true); const mockHasAcceptedTravelTerms = jest.fn(() => false); @@ -165,7 +164,6 @@ jest.mock('@libs/openTravelDotLink', () => ({ jest.mock('@libs/PolicyUtils', () => ({ canSendInvoice: (...args: unknown[]) => mockCanSendInvoice(...args), - getDefaultChatEnabledPolicy: (policies: unknown[]) => mockGetDefaultChatEnabledPolicy(policies), getGroupPoliciesWhereReportCanBeCreated: (policies: unknown, currentUserLogin?: string) => mockGetGroupPoliciesWhereReportCanBeCreated(policies, currentUserLogin), hasAcceptedTravelTerms: () => mockHasAcceptedTravelTerms(), isPaidGroupPolicy: () => mockIsPaidGroupPolicy(), @@ -282,7 +280,7 @@ describe('useCreateNavigationSuggestions', () => { renderHook(() => useCreateNavigationSuggestions()); const onCreateReport = mockUseCreateReport.mock.calls.at(0)?.at(0)?.onCreateReport; - act(() => onCreateReport?.()); + act(() => onCreateReport?.(undefined)); expect(createNewReport).not.toHaveBeenCalled(); expect(Navigation.navigate).not.toHaveBeenCalled(); @@ -421,7 +419,7 @@ describe('useCreateNavigationSuggestions', () => { renderHook(() => useCreateNavigationSuggestions()); const onCreateReport = mockUseCreateReport.mock.calls.at(0)?.at(0)?.onCreateReport; - act(() => onCreateReport?.(true)); + act(() => onCreateReport?.(submitPolicy, true)); expect(createNewReport).toHaveBeenCalledWith(expect.anything(), false, true, submitPolicy, false, mockGetCurrencyDecimals, undefined, false, true); expect(clearLastSearchParams).not.toHaveBeenCalled(); @@ -437,7 +435,7 @@ describe('useCreateNavigationSuggestions', () => { mockIsOnSearchMoneyRequestReportPage.mockReturnValue(true); const createReportParams = mockUseCreateReport.mock.calls.at(0)?.at(0); - act(() => createReportParams?.onCreateReport()); + act(() => createReportParams?.onCreateReport(submitPolicy)); act(() => createReportParams?.onNavigateToWorkspaceSelection()); expect(clearLastSearchParams).toHaveBeenCalledTimes(1); diff --git a/tests/unit/useCreateReportTest.tsx b/tests/unit/useCreateReportTest.tsx index 7aed632c96da..7a7e6048ea8a 100644 --- a/tests/unit/useCreateReportTest.tsx +++ b/tests/unit/useCreateReportTest.tsx @@ -75,6 +75,13 @@ jest.mock('@libs/SubscriptionUtils', () => ({ shouldRestrictUserBillableActions: (...args: Parameters) => mockShouldRestrictUserBillableActions(...args), })); +const mockUsePreferredPolicy = jest.fn(() => ({ + isRestrictedToPreferredPolicy: false, + preferredPolicyID: undefined as string | undefined, + isRestrictedPolicyCreation: false, +})); +jest.mock('@hooks/usePreferredPolicy', () => () => mockUsePreferredPolicy()); + // ── Helpers ──────────────────────────────────────────────────────────────────── const POLICY_ID = 'policy-123'; @@ -102,7 +109,7 @@ function setupUseCreateReportOnyx({activePolicy, emptyReportsConfirmationDismiss if (key === ONYXKEYS.NVP_ACTIVE_POLICY_ID) { return [activePolicy?.id, {status: 'loaded'}]; } - if (key === `${ONYXKEYS.COLLECTION.POLICY}${activePolicy?.id}`) { + if (activePolicy && key === `${ONYXKEYS.COLLECTION.POLICY}${activePolicy.id}`) { return [activePolicy, {status: 'loaded'}]; } if (key === ONYXKEYS.NVP_EMPTY_REPORTS_CONFIRMATION_DISMISSED) { @@ -119,10 +126,56 @@ describe('useCreateReport', () => { jest.clearAllMocks(); reportIDCounter.value = 100; mockShouldRestrictUserBillableActions.mockReturnValue(false); + mockUsePreferredPolicy.mockReturnValue({isRestrictedToPreferredPolicy: false, preferredPolicyID: undefined, isRestrictedPolicyCreation: false}); mockUseShouldShowEmptyReportConfirmation.mockReturnValue(false); setupUseCreateReportOnyx(); }); + describe('domain preferred workspace restriction', () => { + const personalPolicy: OnyxEntry = {...makePaidPolicy('personal-1'), type: CONST.POLICY.TYPE.PERSONAL}; + + it.each([ + ['creates on the preferred workspace instead of the active one', makePaidPolicy('p1'), 'preferred-1', false, 'create'], + ['creates on the preferred workspace when the active one is personal and multiple workspaces exist', personalPolicy, 'preferred-1', false, 'create'], + ['shows the billing restriction page instead of the selector when the preferred workspace is billing-restricted', makePaidPolicy('p1'), 'preferred-1', true, 'restricted'], + ['falls back to the normal rules when the preferred workspace is not one the user can create reports on', personalPolicy, 'not-eligible', false, 'selector'], + ])('%s', (_description, activePolicy, preferredPolicyID, isBillingRestricted, expected) => { + setupUseCreateReportOnyx({activePolicy}); + mockUsePreferredPolicy.mockReturnValue({ + isRestrictedToPreferredPolicy: true, + preferredPolicyID, + isRestrictedPolicyCreation: false, + }); + mockShouldRestrictUserBillableActions.mockReturnValue(isBillingRestricted); + const onCreateReport = jest.fn(); + const policies = [makePaidPolicy('p1'), makePaidPolicy('p2'), makePaidPolicy('preferred-1')]; + + const {result} = renderHook(() => + useCreateReport({ + onCreateReport, + groupPoliciesWithChatEnabled: policies, + }), + ); + + act(() => { + result.current.createReport(); + }); + + const selectorRoute = DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute(); + if (expected === 'create') { + expect(onCreateReport).toHaveBeenCalledWith(expect.objectContaining({id: 'preferred-1'}), false); + expect(Navigation.navigate).not.toHaveBeenCalled(); + } else if (expected === 'restricted') { + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.RESTRICTED_ACTION.getRoute('preferred-1')); + expect(Navigation.navigate).not.toHaveBeenCalledWith(selectorRoute); + expect(onCreateReport).not.toHaveBeenCalled(); + } else { + expect(Navigation.navigate).toHaveBeenCalledWith(selectorRoute); + expect(onCreateReport).not.toHaveBeenCalled(); + } + }); + }); + describe('upgrade path (no policies)', () => { it('navigates to upgrade path when user has no group policies', () => { const onCreateReport = jest.fn(); @@ -232,7 +285,7 @@ describe('useCreateReport', () => { }); expect(Navigation.navigate).not.toHaveBeenCalledWith(DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); }); it('does NOT show selector when default is a Submit workspace, even with 2+ Submit workspaces', () => { @@ -254,7 +307,7 @@ describe('useCreateReport', () => { }); expect(Navigation.navigate).not.toHaveBeenCalledWith(DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); }); it('does NOT show selector when default is personal but only 1 non-personal workspace exists', () => { @@ -279,7 +332,7 @@ describe('useCreateReport', () => { }); expect(Navigation.navigate).not.toHaveBeenCalledWith(DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); }); }); @@ -299,7 +352,7 @@ describe('useCreateReport', () => { result.current.createReport(); }); - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); expect(Navigation.navigate).not.toHaveBeenCalled(); }); @@ -383,7 +436,7 @@ describe('useCreateReport', () => { }); // Should call onCreateReport directly, not navigate to upgrade - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); const calls = jest.mocked(Navigation.navigate).mock.calls; const navigatedToUpgrade = calls.some((call) => { const firstArg = call.at(0); @@ -412,7 +465,7 @@ describe('useCreateReport', () => { result.current.createReport(); }); - expect(onCreateReport).toHaveBeenCalledWith(false); + expect(onCreateReport).toHaveBeenCalledWith(expect.anything(), false); expect(mockOpenCreateReportConfirmation).not.toHaveBeenCalled(); }); });