Skip to content

Commit 1585469

Browse files
authored
Merge pull request #90538 from Expensify/claude-payerButtonHiddenDuringBankSetup
Hide Payer row during incomplete bank account setup
2 parents bf59025 + 4b4a868 commit 1585469

3 files changed

Lines changed: 232 additions & 2 deletions

File tree

src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
700700
/>
701701
)
702702
)}
703-
{shouldShowBankAccount && (
703+
{shouldShowBankAccount && policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL && (
704704
<OfflineWithFeedback
705705
pendingAction={policy?.pendingFields?.reimburser}
706706
shouldDisableOpacity={isOffline && !!policy?.pendingFields?.reimbursementChoice && !!policy?.pendingFields?.reimburser}

src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,10 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR
248248
const setPolicyAuthorizedPayer = (member: MemberOption) => setSelectedPayer(personalDetails?.[member.accountID]?.login);
249249

250250
const shouldShowBlockingPage =
251-
(isEmptyObject(policy) && !isLoadingReportData) || isPendingDeletePolicy(policy) || policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES;
251+
(isEmptyObject(policy) && !isLoadingReportData) ||
252+
isPendingDeletePolicy(policy) ||
253+
policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO ||
254+
policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL;
252255

253256
const totalNumberOfEmployeesEitherOwnerOrAdmin = Object.entries(policy?.employeeList ?? {}).filter(([email, policyEmployee]) => {
254257
const isOwner = policy?.owner === email;
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import {PortalProvider} from '@gorhom/portal';
2+
import {NavigationContainer} from '@react-navigation/native';
3+
import {act, render, screen} from '@testing-library/react-native';
4+
import React from 'react';
5+
import Onyx from 'react-native-onyx';
6+
import ComposeProviders from '@components/ComposeProviders';
7+
import {LocaleContextProvider} from '@components/LocaleContextProvider';
8+
import {ModalProvider} from '@components/Modal/Global/ModalContext';
9+
import OnyxListItemProvider from '@components/OnyxListItemProvider';
10+
import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID';
11+
import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout';
12+
import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types';
13+
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
14+
import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
15+
import WorkspaceWorkflowsPage from '@pages/workspace/workflows/WorkspaceWorkflowsPage';
16+
import CONST from '@src/CONST';
17+
import ONYXKEYS from '@src/ONYXKEYS';
18+
import SCREENS from '@src/SCREENS';
19+
import type {BankAccountList, Policy} from '@src/types/onyx';
20+
import * as LHNTestUtils from '../utils/LHNTestUtils';
21+
import * as TestHelper from '../utils/TestHelper';
22+
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
23+
24+
jest.mock('@src/components/ConfirmedRoute.tsx');
25+
26+
jest.mock('react-native-render-html', () => {
27+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
28+
const {View: MockView} = require('react-native');
29+
return {
30+
RenderHTMLConfigProvider: ({children}: {children: React.ReactNode}) => children,
31+
RenderHTMLSource: () => <MockView />,
32+
};
33+
});
34+
35+
jest.mock('@components/Modal/ReanimatedModal', () => {
36+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
37+
const {useEffect, useRef}: {useEffect: typeof React.useEffect; useRef: typeof React.useRef} = require('react');
38+
39+
return function MockReanimatedModal({isVisible, onModalHide, children}: {isVisible: boolean; onModalHide?: () => void; children: React.ReactNode}) {
40+
const wasVisible = useRef<boolean>(isVisible);
41+
42+
useEffect(() => {
43+
if (wasVisible.current && !isVisible) {
44+
onModalHide?.();
45+
}
46+
wasVisible.current = isVisible;
47+
}, [isVisible, onModalHide]);
48+
49+
if (!isVisible) {
50+
return null;
51+
}
52+
53+
return children as React.ReactElement;
54+
};
55+
});
56+
57+
TestHelper.setupGlobalFetchMock();
58+
59+
const POLICY_ID = 'workflows-payer-test';
60+
61+
const Stack = createPlatformStackNavigator<WorkspaceSplitNavigatorParamList>();
62+
63+
const buildPolicy = (overrides: Partial<Policy> = {}): Policy =>
64+
({
65+
...LHNTestUtils.getFakePolicy(POLICY_ID),
66+
type: CONST.POLICY.TYPE.CORPORATE,
67+
role: CONST.POLICY.ROLE.ADMIN,
68+
outputCurrency: 'USD',
69+
areWorkflowsEnabled: true,
70+
...overrides,
71+
}) as Policy;
72+
73+
const renderPage = () =>
74+
render(
75+
<ComposeProviders components={[OnyxListItemProvider, LocaleContextProvider, CurrentReportIDContextProvider]}>
76+
<PortalProvider>
77+
<ModalProvider>
78+
<NavigationContainer>
79+
<Stack.Navigator initialRouteName={SCREENS.WORKSPACE.WORKFLOWS}>
80+
<Stack.Screen
81+
name={SCREENS.WORKSPACE.WORKFLOWS}
82+
component={WorkspaceWorkflowsPage}
83+
initialParams={{policyID: POLICY_ID}}
84+
/>
85+
</Stack.Navigator>
86+
</NavigationContainer>
87+
</ModalProvider>
88+
</PortalProvider>
89+
</ComposeProviders>,
90+
);
91+
92+
describe('WorkspaceWorkflowsPage - Payer row visibility', () => {
93+
beforeAll(() => {
94+
Onyx.init({keys: ONYXKEYS});
95+
});
96+
97+
beforeEach(async () => {
98+
await act(async () => {
99+
await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
100+
});
101+
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
102+
isSmallScreenWidth: false,
103+
shouldUseNarrowLayout: false,
104+
} as ResponsiveLayoutResult);
105+
});
106+
107+
afterEach(async () => {
108+
await act(async () => {
109+
await Onyx.clear();
110+
});
111+
jest.clearAllMocks();
112+
});
113+
114+
it('shows the Payer row when reimbursementChoice is REIMBURSEMENT_YES and a bank account exists', async () => {
115+
await TestHelper.signInWithTestUser();
116+
await act(async () => {
117+
await Onyx.merge(
118+
`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`,
119+
buildPolicy({
120+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
121+
achAccount: {
122+
reimburser: 'test@user.com',
123+
bankAccountID: 123456,
124+
accountNumber: '1234567890',
125+
routingNumber: '011000015',
126+
bankName: 'Test Bank',
127+
addressName: 'Test Address',
128+
state: CONST.BANK_ACCOUNT.STATE.OPEN,
129+
},
130+
}),
131+
);
132+
});
133+
134+
renderPage();
135+
await waitForBatchedUpdatesWithAct();
136+
137+
expect(screen.getByText(TestHelper.translateLocal('workflowsPayerPage.payer'))).toBeOnTheScreen();
138+
});
139+
140+
it('hides the Payer row when reimbursementChoice is REIMBURSEMENT_MANUAL', async () => {
141+
await TestHelper.signInWithTestUser();
142+
await act(async () => {
143+
await Onyx.merge(
144+
`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`,
145+
buildPolicy({
146+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL,
147+
achAccount: {
148+
reimburser: 'test@user.com',
149+
bankAccountID: 123456,
150+
accountNumber: '1234567890',
151+
routingNumber: '011000015',
152+
bankName: 'Test Bank',
153+
addressName: 'Test Address',
154+
state: CONST.BANK_ACCOUNT.STATE.OPEN,
155+
},
156+
}),
157+
);
158+
});
159+
160+
renderPage();
161+
await waitForBatchedUpdatesWithAct();
162+
163+
expect(screen.queryByText(TestHelper.translateLocal('workflowsPayerPage.payer'))).not.toBeOnTheScreen();
164+
});
165+
166+
it('shows the Payer row when reimbursementChoice is undefined (legacy workspaces)', async () => {
167+
await TestHelper.signInWithTestUser();
168+
await act(async () => {
169+
await Onyx.merge(
170+
`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`,
171+
buildPolicy({
172+
reimbursementChoice: undefined,
173+
achAccount: {
174+
reimburser: 'test@user.com',
175+
bankAccountID: 123456,
176+
accountNumber: '1234567890',
177+
routingNumber: '011000015',
178+
bankName: 'Test Bank',
179+
addressName: 'Test Address',
180+
state: CONST.BANK_ACCOUNT.STATE.OPEN,
181+
},
182+
}),
183+
);
184+
});
185+
186+
renderPage();
187+
await waitForBatchedUpdatesWithAct();
188+
189+
expect(screen.getByText(TestHelper.translateLocal('workflowsPayerPage.payer'))).toBeOnTheScreen();
190+
});
191+
192+
it('shows the Payer row during partial bank setup when reimbursementChoice is REIMBURSEMENT_YES', async () => {
193+
await TestHelper.signInWithTestUser();
194+
195+
const bankAccountID = 123456;
196+
const bankAccountList: BankAccountList = {
197+
[bankAccountID]: {
198+
methodID: bankAccountID,
199+
bankCurrency: 'USD',
200+
bankCountry: 'US',
201+
accountData: {
202+
additionalData: {
203+
policyID: POLICY_ID,
204+
bankName: CONST.BANK_NAMES.GENERIC_BANK,
205+
},
206+
addressName: 'Test Address',
207+
state: CONST.BANK_ACCOUNT.STATE.SETUP,
208+
},
209+
},
210+
};
211+
212+
await act(async () => {
213+
await Onyx.merge(
214+
`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`,
215+
buildPolicy({
216+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
217+
}),
218+
);
219+
await Onyx.set(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList);
220+
});
221+
222+
renderPage();
223+
await waitForBatchedUpdatesWithAct();
224+
225+
expect(screen.getByText(TestHelper.translateLocal('workflowsPayerPage.payer'))).toBeOnTheScreen();
226+
});
227+
});

0 commit comments

Comments
 (0)