diff --git a/src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx b/src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx
index 488712f6f637..ae2848b12ee9 100644
--- a/src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx
+++ b/src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx
@@ -160,7 +160,7 @@ function WorkflowsPaymentsTab({policyID}: WorkflowsPaymentsTabProps) {
) : undefined;
const bankConnectionMessage = bankConnectionStatus?.messageKey ? translate(bankConnectionStatus.messageKey) : undefined;
const bankConnectionActionText = bankConnectionStatus?.actionKey ? translate(bankConnectionStatus.actionKey) : undefined;
- const canInteractWithBankAccountRow = canWritePayments && !isOffline && !isBankAccountPendingDelete;
+ const canInteractWithBankAccountRow = canWritePayments && !isBankAccountPendingDelete;
// Only the reimburser can send the unlock request, so a locked account offers no action to anyone else rather than
// an Unlock button that would instead start connecting a different bank account.
const canPerformBankAccountAction = !isBusinessBankAccountLocked || isUserReimburser;
@@ -209,7 +209,7 @@ function WorkflowsPaymentsTab({policyID}: WorkflowsPaymentsTabProps) {
descriptionTextStyle: isBankAccountPendingDelete ? styles.offlineFeedbackDeleted : undefined,
sentryLabel: CONST.SENTRY_LABEL.WORKSPACE.WORKFLOWS.BANK_ACCOUNT,
shouldGreyOutWhenDisabled: !policy?.pendingFields?.reimbursementChoice,
- disabled: isOffline || !canWritePayments || isBankAccountPendingDelete,
+ disabled: !canWritePayments || isBankAccountPendingDelete,
shouldShowRightIcon: canWritePayments && !isBankAccountPendingDelete,
interactive: canWritePayments && !isBankAccountPendingDelete,
descriptionAddon: bankConnectionStatusAddon,
diff --git a/tests/ui/WorkspaceWorkflowsLockedBankAccountTest.tsx b/tests/ui/WorkspaceWorkflowsLockedBankAccountTest.tsx
new file mode 100644
index 000000000000..5430df73fab3
--- /dev/null
+++ b/tests/ui/WorkspaceWorkflowsLockedBankAccountTest.tsx
@@ -0,0 +1,183 @@
+import {act, fireEvent, render, screen} from '@testing-library/react-native';
+
+import ComposeProviders from '@components/ComposeProviders';
+import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider';
+import {LocaleContextProvider} from '@components/LocaleContextProvider';
+import {ModalProvider} from '@components/Modal/Global/ModalContext';
+import OnyxListItemProvider from '@components/OnyxListItemProvider';
+
+import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID';
+import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout';
+import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types';
+
+import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
+import {setForceOffline} from '@libs/NetworkState';
+
+import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
+
+import WorkspaceWorkflowsPageRevamp from '@pages/workspace/workflows/WorkspaceWorkflowsPageRevamp';
+
+import type * as ReportUserActionsModule from '@userActions/Report';
+
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import SCREENS from '@src/SCREENS';
+import type {Policy} from '@src/types/onyx';
+
+import {PortalProvider} from '@gorhom/portal';
+import {NavigationContainer} from '@react-navigation/native';
+import React from 'react';
+import Onyx from 'react-native-onyx';
+
+import type * as MockReanimatedModalModule from '../utils/mockReanimatedModal';
+
+import createMock from '../utils/createMock';
+import * as LHNTestUtils from '../utils/LHNTestUtils';
+import * as TestHelper from '../utils/TestHelper';
+import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
+
+jest.mock('@src/components/ConfirmedRoute.tsx');
+
+jest.mock('react-native-render-html', () => {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const {View: MockView} = require('react-native');
+ return {
+ RenderHTMLConfigProvider: ({children}: {children: React.ReactNode}) => children,
+ RenderHTMLSource: () => ,
+ };
+});
+
+jest.mock('@components/Modal/ReanimatedModal', () => {
+ const {default: MockReanimatedModal} = jest.requireActual('../utils/mockReanimatedModal');
+ return MockReanimatedModal;
+});
+
+const mockNavigateToConciergeChat = jest.fn();
+jest.mock('@userActions/Report', () => ({
+ ...jest.requireActual('@userActions/Report'),
+ navigateToConciergeChat: () => {
+ mockNavigateToConciergeChat();
+ },
+}));
+
+TestHelper.setupGlobalFetchMock();
+
+const POLICY_ID = 'workflows-locked-bank-account-test';
+const CURRENT_USER_LOGIN = 'test@user.com';
+const OTHER_REIMBURSER_LOGIN = 'reimburser@user.com';
+const BANK_ACCOUNT_ID = 123456;
+
+type TestNavigatorParamList = WorkspaceSplitNavigatorParamList;
+
+const Stack = createPlatformStackNavigator();
+
+const buildPolicyWithLockedBankAccount = (reimburser: string): Policy =>
+ ({
+ ...LHNTestUtils.getFakePolicy(POLICY_ID),
+ type: CONST.POLICY.TYPE.CORPORATE,
+ role: CONST.POLICY.ROLE.ADMIN,
+ outputCurrency: 'USD',
+ areWorkflowsEnabled: true,
+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
+ achAccount: {
+ reimburser,
+ bankAccountID: BANK_ACCOUNT_ID,
+ accountNumber: '1234567890',
+ routingNumber: '011000015',
+ bankName: 'Test Bank',
+ addressName: 'Test Address',
+ state: CONST.BANK_ACCOUNT.STATE.LOCKED,
+ },
+ }) as Policy;
+
+const renderPage = () =>
+ render(
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+
+const setUpLockedBankAccount = async (reimburser: string) => {
+ await TestHelper.signInWithTestUser(1, CURRENT_USER_LOGIN);
+ await act(async () => {
+ await Onyx.merge(ONYXKEYS.ACCOUNT, {primaryLogin: CURRENT_USER_LOGIN});
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, buildPolicyWithLockedBankAccount(reimburser));
+ });
+};
+
+const getInitiatingBankAccountUnlock = () =>
+ new Promise((resolve) => {
+ const connection = Onyx.connect({
+ key: ONYXKEYS.INITIATING_BANK_ACCOUNT_UNLOCK,
+ callback: (value) => {
+ Onyx.disconnect(connection);
+ resolve(value);
+ },
+ });
+ });
+
+describe('WorkspaceWorkflowsPageRevamp - locked bank account row', () => {
+ beforeAll(() => {
+ Onyx.init({keys: ONYXKEYS});
+ });
+
+ beforeEach(async () => {
+ await act(async () => {
+ await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
+ });
+ jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue(
+ createMock({
+ isSmallScreenWidth: false,
+ shouldUseNarrowLayout: false,
+ }),
+ );
+ });
+
+ afterEach(async () => {
+ setForceOffline(false);
+ mockNavigateToConciergeChat.mockClear();
+ await act(async () => {
+ await Onyx.clear();
+ });
+ jest.restoreAllMocks();
+ });
+
+ it('lets the reimburser request an unlock while offline, because the request is optimistic and queues until reconnect', async () => {
+ await setUpLockedBankAccount(CURRENT_USER_LOGIN);
+ setForceOffline(true);
+
+ renderPage();
+ await waitForBatchedUpdatesWithAct();
+
+ fireEvent.press(screen.getByText(TestHelper.translateLocal('walletPage.bankAccountStatus.unlock')));
+ await waitForBatchedUpdatesWithAct();
+
+ expect(mockNavigateToConciergeChat).toHaveBeenCalled();
+ await expect(getInitiatingBankAccountUnlock()).resolves.toEqual(expect.objectContaining({bankAccountIDToUnlock: BANK_ACCOUNT_ID}));
+ });
+
+ it('does not offer the unlock action offline to someone who is not the reimburser', async () => {
+ await setUpLockedBankAccount(OTHER_REIMBURSER_LOGIN);
+ setForceOffline(true);
+
+ renderPage();
+ await waitForBatchedUpdatesWithAct();
+
+ // Only the reimburser can send the unlock request, so nobody else gets an Unlock button to press.
+ expect(screen.queryByText(TestHelper.translateLocal('walletPage.bankAccountStatus.unlock'))).not.toBeOnTheScreen();
+ await expect(getInitiatingBankAccountUnlock()).resolves.toBeUndefined();
+ });
+});