Prevent Pay elsewhere fallback while bank account data is loading#85578
Prevent Pay elsewhere fallback while bank account data is loading#85578
Conversation
When bankAccountList hasn't loaded from Onyx, the isPayer check for ACH-configured policies incorrectly fails for admin users who access the bank account via sharees, causing onlyShowPayElsewhere to become true. This shows only 'Pay elsewhere' instead of the ACH option, letting users accidentally mark reports as paid without actual reimbursement. Add loading guards for bankAccountList in MoneyReportHeader, MoneyRequestReportPreviewContent, and useSearchBulkActions to prevent the payment button from being actionable while bank account data is still loading. Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
The failing check Analysis: The Android Gradle build failed because Evidence: This PR only modifies TypeScript files ( |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@MelvinBot consider adding tests for the hook |
|
Thanks for the suggestion! I looked into adding tests for the Challenges with testing this hook directly:
What gives confidence in this change:
If you'd like me to proceed with adding tests despite the heavy mocking needed, I'm happy to — just let me know the scope you'd prefer (e.g., full hook test vs. testing just the |
|
@MelvinBot @abzokhattab One edge case worth considering, if bankAccountList fetch fails and Onyx never transitions from status='loading', the pay button would be indefinitely disabled. Should we add a timeout fallback or treat network errors as loaded to avoid a permanently broken Pay button? |
|
@MelvinBot, let's add test cases for the whole file in order to increase the coverage and keep in mind tests best practices |
|
@neerajbachani Good catch on thinking about edge cases! Onyx resolves the loading state regardless of whether the network request succeeds or fails ... even on failure, it transitions out of |
Co-authored-by: abzokhattab <61255839+abzokhattab@users.noreply.github.com> Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Added comprehensive tests for Test coverage includes:Initial state (3 tests)
Derived values (3 tests)
Header button options (25 tests)
Modal handlers (5 tests)
Option ordering (1 test)
|
Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
remove unnecessary tests @MelvinBot not all of them are needed |
|
Fixed the failing spellcheck: corrected 'unholded' to 'unheld' in two test descriptions in |
Removed redundant tests: modal handler typeof checks, derived value computations, offline/on-hold/mixed-action variants, option ordering. Kept 13 focused tests: initial state, loading guard (PR change), and one positive case per action type. Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
The createBaseQueryJSON helper was casting CONST values to string, widening the types and making them incompatible with SearchQueryJSON. Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Trimmed the test suite from 38 to 13 tests. Removed:
Kept 13 focused tests:
|
…com/Expensify/App into claude-fixPayElsewhereRaceCondition Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Fixed the failing typecheck: removed unnecessary |
|
I have failed to help you. I let you down and for that I am sorry. Please try again later. This may be due to a Claude outage (check here). Failure details: https://github.com/Expensify/Expensify/actions/runs/23215725164 |
|
I have failed to help you. I let you down and for that I am sorry. Please try again later. This may be due to a Claude outage (check here). Failure details: https://github.com/Expensify/Expensify/actions/runs/23215769997 |
|
I have failed to help you. I let you down and for that I am sorry. Please try again later. This may be due to a Claude outage (check here). Failure details: https://github.com/Expensify/Expensify/actions/runs/23215993947 |
|
@MelvinBot fix the pipelines |
|
@MelvinBot since we mocking a lot of functions, does it still make sense to keep the tests or should it be better cleaned? |
…y in test Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Fixed the failing checks. The test file (
|
|
Good question. Here's my take on the value vs. mocking cost: Current state: 13 tests, ~300 lines of mocks (about half the file). Nearly every dependency is mocked — Worth keeping (4 tests):
Could be cleaned (9 headerButtonsOptions tests): Recommendation: Trim down to the 4 high-value tests above. This keeps meaningful regression coverage for the PR change while reducing the mocking surface significantly. The positive-case option tests (approve, pay, hold, etc.) test simple conditional logic that's better caught in integration/E2E tests where real Onyx data flows through. Alternatively, if you'd prefer to keep a few representative option tests as regression guards, I'd suggest keeping just 2: the "export always present" test and one action test (e.g., approve). That gives a smoke test without maintaining 9 similar patterns. Let me know which direction you'd like and I'll clean it up. |
|
ok lets keep the 4 tests @MelvinBot |
Remove 9 headerButtonsOptions tests that only verified mocked conditional branching. Keep 2 initial state tests and 2 loading guard tests that directly validate the PR change. Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Trimmed the test suite to 4 focused tests as requested:
Removed 9 headerButtonsOptions tests that were testing mocked conditional branching (approve, pay, hold, unhold, submit, reject, delete, export variants). |
Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
Fixed the failing Prettier check: removed a trailing blank line in |
|
@MelvinBot mark the PR as ready |
|
@abzokhattab Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
| @@ -0,0 +1,471 @@ | |||
| /* eslint-disable @typescript-eslint/naming-convention */ | |||
There was a problem hiding this comment.
❌ CONSISTENCY-5 (docs)
The /* eslint-disable @typescript-eslint/naming-convention */ directive at the top of this new test file lacks an accompanying comment explaining why the rule needs to be disabled. ESLint rule disables without justification can mask underlying issues and reduce code quality.
Add a comment explaining why the naming convention rule is disabled. For example:
/* eslint-disable @typescript-eslint/naming-convention -- Mock data uses Onyx collection key patterns (e.g., `report_report1`) that don't conform to standard naming conventions */Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed — added justification comment explaining that mock data uses Onyx collection key patterns that don't conform to standard naming conventions.
|
All CI checks are passing (the only failing one is the PR Author Checklist, which is expected). Could you clarify what you'd like me to check? For example:
|
Co-authored-by: Abdelrahman Khattab <abzokhattab@users.noreply.github.com>
|
@arosiclair Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
This unit test seems very contrived and I'm not sure it's really adding value. I think we should just remove it.
| const isLoadingBankAccountList = isLoadingOnyxValue(bankAccountListResult); | ||
| const onlyShowPayElsewhere = useMemo(() => { | ||
| if (isLoadingBankAccountList) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
This race condition seems valid, but are we sure this is the root problem? I think it would only take a few milliseconds for Onyx to load values. How would a user open the page and click the Pay button so quickly?
Explanation of Change
When
bankAccountListhasn't loaded from Onyx yet, theisPayer()check for ACH-configured policies (REIMBURSEMENT_YES) incorrectly fails for admin users who access the business bank account viasharees. This causesonlyShowPayElsewhereto becometrue, showing only the "Pay elsewhere" option instead of the ACH reimbursement option. If the user clicks "Pay elsewhere" during this loading window, the report is marked as paid without actual money movement.This PR adds loading guards for
bankAccountListin the three places whereonlyShowPayElsewhereis computed:MoneyReportHeader.tsx— Disables and shows loading state on the settlement button whilebankAccountListis loadingMoneyRequestReportPreviewContent.tsx— Same loading guard applied to the report preview settlement buttonuseSearchBulkActions.ts— PreventsonlyShowPayElsewherefrom being computed astruewhilebankAccountListis loadingThe fix follows the existing pattern used in
PaymentMethodList.tsxwhereisLoadingOnyxValue(bankAccountListResult)is already used for the same purpose.Fixed Issues
$ #85319
PROPOSAL: #85319 (comment)
Tests
reimbursementChoice=REIMBURSEMENT_YESOffline tests
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
N/A - No UI changes, only loading state behavior change
Android: mWeb Chrome
N/A - No UI changes, only loading state behavior change
iOS: Native
N/A - No UI changes, only loading state behavior change
iOS: mWeb Safari
N/A - No UI changes, only loading state behavior change
MacOS: Chrome / Safari
N/A - No UI changes, only loading state behavior change