Skip to content

test(send): Phase A characterization tests for SendFormViewModel#4377

Merged
johnnyluo merged 3 commits into
mainfrom
aminsato/sendform_phase_a_tests
May 3, 2026
Merged

test(send): Phase A characterization tests for SendFormViewModel#4377
johnnyluo merged 3 commits into
mainfrom
aminsato/sendform_phase_a_tests

Conversation

@aminsato
Copy link
Copy Markdown
Collaborator

@aminsato aminsato commented May 3, 2026

Summary

  • Adds 42 unit tests across 4 new files locking down the public surface of SendFormViewModel.kt (3415 LOC) before splitting it into focused sub-components.
  • No production code changes — pure additions under app/src/test/. All tests pass green against current main.
  • Lays the safety net for an upcoming refactor that will extract AddressManager, AmountManager, and a per-chain SendDispatcher from the ViewModel in follow-up PRs.

Why

SendFormViewModel is the largest file in the project not currently touched by any open PR. At 3415 lines with 30+ public methods and ~24 injected dependencies, it cannot be safely refactored without a behavioral baseline. These tests pin the seams that the section composables (SendFormAddressSection, SendFormAmountSection, SendFormAssetSection) and SendFormScreen rely on, so any regression during the split fails the suite loudly.

What's covered

File # Surface
SendFormViewModelInitTest.kt 14 loadData arg parsing (defiType, amount, memo, slippage, autocompound), loadVaultNamesrcVaultName, fiat currency from AppCurrencyRepository, expandedSection rules from preSelectedTokenId + address
SendFormViewModelAddressTest.kt 11 setOutputAddress, setProviderAddress, isDstAddressComplete flow, scanAddress / scanProviderAddress (QR roundtrip), setAddressFromQrCode, openAddressBook no-token gate
SendFormViewModelAmountTest.kt 13 validateTokenAmount (4 branches: empty / zero / non-numeric / valid), toggleAmountInputType, expandSection, dismissError, back → navigator, chooseMaxTokenAmount, choosePercentageAmount for F25/F50/F75/F100, default fraction list
SendFormViewModelSubmitEvmTest.kt 4 send() validation gates: blank-address focus + expandSection(Address), blank-amount focus + expandSection(Amount), no-selected-account error + no nav, onClickContinue dispatch to send()

Notable test technique

textAsFlow() uses Compose's snapshotFlow, which does not emit in unit tests on its own (no frame loop). Address tests that observe flow-driven state call Snapshot.sendApplyNotifications() after each TextFieldState mutation. This is documented in-line so future tests in this VM can copy the pattern.

Out of scope (intentional)

  • Full happy-path EVM submit (Transaction payload assembly → transactionRepository.addTransactionRoute.VerifySend) is not covered. Reaching it requires plumbing accounts → preSelectToken → selectedToken → gasFee flow — that's exactly the seam a follow-up PR will introduce, so the test is easier to write after the split rather than fighting current shape.
  • Per-chain submit branches (bond / unbond / stake / unstake / mint / redeem / withDrawUSDCCircle), gas settings, and Asset/Network selection — these get their own test suites alongside the matching extraction PRs.

Test plan

  • ./gradlew :app:testDebugUnitTest --tests "com.vultisig.wallet.ui.models.send.SendFormViewModel*Test" — all 47 tests pass (42 new + 5 existing SendFormViewModelTronStakingTest)
  • ./gradlew :app:ktfmtFormat — formatted
  • CI green

Summary by CodeRabbit

  • Tests
    • Added comprehensive unit tests for send flows: address and provider field updates, QR-scan parsing/handling, address-book gating, amount validation and input type toggles, section expand/focus logic during submission, form initialization from navigation params (amount, memo, token/address), slippage seeding for a specific redeem type, amount-selection percentages/loading states, navigation/back behavior, and submission validation cases.

Adds a 42-test safety net before splitting the 3415-LOC SendFormViewModel
into focused sub-components. These lock down the public surface that
SendFormScreen and its section composables depend on, so the upcoming
extractions cannot regress observable behavior unnoticed.

- SendFormViewModelInitTest (14): loadData arg parsing, vault name,
  fiat currency, expandedSection rules, slippage seed for REDEEM_YRUNE.
- SendFormViewModelAddressTest (11): setOutputAddress, setProviderAddress,
  isDstAddressComplete flow, scanAddress / scanProviderAddress,
  setAddressFromQrCode, openAddressBook no-token gate.
- SendFormViewModelAmountTest (13): validateTokenAmount branches,
  toggleAmountInputType, expandSection, dismissError, back,
  chooseMaxTokenAmount, choosePercentageAmount F25/F50/F75/F100.
- SendFormViewModelSubmitEvmTest (4): send() validation gates and
  onClickContinue dispatch.

No production code changes.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

Adds four coroutine-based unit test suites for SendFormViewModel (address, amount, init, EVM submit). Each test file configures a test Main dispatcher, statically mocks SavedStateHandle route-to-Route.Send, stubs currency/vault flows, and provides a buildViewModel() test helper.

Changes

SendFormViewModel Unit Tests

Layer / File(s) Summary
Test Infra & Shared Helpers
app/src/test/java/.../SendFormViewModelAddressTest.kt, .../SendFormViewModelAmountTest.kt, .../SendFormViewModelInitTest.kt, .../SendFormViewModelSubmitEvmTest.kt
Common setup/teardown: Dispatchers.setMain(UnconfinedTestDispatcher) in setUp() and Dispatchers.resetMain() in tearDown(), static mock SavedStateHandleKt.toRoute<Route.Send>() returning a constant VAULT_ID, stubs for AppCurrencyRepository and vaultRepository flows, and a buildViewModel() helper constructing SendFormViewModel with mocked dependencies and an AccountToTokenBalanceUiModelMapper stub.
Initialization Mapping
app/src/test/java/.../SendFormViewModelInitTest.kt
Tests mapping Route.Send args into uiState: defiType parsing (including null default), isAutocompound for specific types, seeding tokenAmountFieldState and memoFieldState from args, expandedSection selection based on preSelectedTokenId/address presence, loading srcVaultName from vaultRepository, deriving fiatCurrency from appCurrencyRepository, and seeding slippageFieldState for REDEEM_YRUNE.
Address Input Behavior
app/src/test/java/.../SendFormViewModelAddressTest.kt
Tests for setOutputAddress and setProviderAddress field updates, uiState.isDstAddressComplete toggling for non-blank vs blank/whitespace, QR scan behavior (scanAddress, scanProviderAddress, setAddressFromQrCode) handling blank/non-blank payloads, and openAddressBook(AddressBookType.OUTPUT) no-op when no token selected.
Amount & Token Input Behavior
app/src/test/java/.../SendFormViewModelAmountTest.kt
Tests validateTokenAmount() for empty/"0"/non-numeric/positive inputs (setting/clearing tokenAmountError), toggleAmountInputType(), expandSection() semantics, dismissError() clearing errorText, back() delegating to navigator, chooseMaxTokenAmount() and choosePercentageAmount() selection and loading flags, and default amountFractionEntries.
Submission / Focus Flow
app/src/test/java/.../SendFormViewModelSubmitEvmTest.kt
Tests submission validation flow: blank address expands Address section and emits ADDRESS focus, non-blank address with blank amount expands Amount section and emits AMOUNT focus, missing selected account sets an error without navigation/transaction calls, and onClickContinue() follows the send() validation path for defiType = null.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Four test files hop in, neat and bright,
They sniff out addresses, amounts, and init light.
QR beeps, focus jumps, and slippage takes a bow,
SendForm rests easier — the rabbit nods now.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: Phase A characterization tests for SendFormViewModel. It is specific, concise, and clearly indicates this PR adds unit tests before planned refactoring.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aminsato/sendform_phase_a_tests

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt (1)

199-215: ⚡ Quick win

Rename this test to match the asserted no-op behavior.

The current name says address is routed into addressFieldState, but the assertion verifies it remains empty when no token is selected.

Suggested rename
-    fun `openAddressBook OUTPUT routes the address into addressFieldState`() = runTest {
+    fun `openAddressBook OUTPUT is a no-op when selectedToken is missing`() = runTest {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt`
around lines 199 - 215, The test name is inaccurate: rename the test function
currently named `openAddressBook OUTPUT routes the address into
addressFieldState` to reflect the asserted no-op behavior when no token is
selected (e.g., mention "no-op" or "keeps addressFieldState empty"); update the
test declaration (the function name) that contains the calls to
`buildViewModel()`, `vm.openAddressBook(AddressBookType.OUTPUT)`, and the
assertion against `vm.addressFieldState.text` so the name clearly describes that
the address book flow is gated on token selection and leaves the address field
empty when `selectedTokenValue` is null.
app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelInitTest.kt (1)

20-23: Use Kotest assertions in these new unit tests for project consistency.

These tests currently use kotlin.test assertions, while repo test guidelines require Kotest assertions in app/src/test/**/*.kt. This pattern appears across multiple test files in this directory (SendFormViewModelInitTest.kt, SendFormViewModelTronStakingTest.kt, SendFormViewModelSubmitEvmTest.kt, SendFormViewModelAmountTest.kt, and SendFormViewModelAddressTest.kt).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelInitTest.kt`
around lines 20 - 23, Tests in SendFormViewModelInitTest (and the other
SendFormViewModel* test files) import and use kotlin.test assertions
(assertEquals, assertFalse, assertNull, assertTrue); replace those with the
Kotest assertion functions to match project conventions: update the imports to
use Kotest (e.g., shouldBe, shouldBeTrue/shouldBeFalse, shouldBeNull or
shouldBe) and change usages inside the tests in SendFormViewModelInitTest,
SendFormViewModelTronStakingTest, SendFormViewModelSubmitEvmTest,
SendFormViewModelAmountTest, and SendFormViewModelAddressTest to the
corresponding Kotest matchers (keeping the same semantics) so all assertions use
Kotest APIs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt`:
- Around line 199-215: The test name is inaccurate: rename the test function
currently named `openAddressBook OUTPUT routes the address into
addressFieldState` to reflect the asserted no-op behavior when no token is
selected (e.g., mention "no-op" or "keeps addressFieldState empty"); update the
test declaration (the function name) that contains the calls to
`buildViewModel()`, `vm.openAddressBook(AddressBookType.OUTPUT)`, and the
assertion against `vm.addressFieldState.text` so the name clearly describes that
the address book flow is gated on token selection and leaves the address field
empty when `selectedTokenValue` is null.

In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelInitTest.kt`:
- Around line 20-23: Tests in SendFormViewModelInitTest (and the other
SendFormViewModel* test files) import and use kotlin.test assertions
(assertEquals, assertFalse, assertNull, assertTrue); replace those with the
Kotest assertion functions to match project conventions: update the imports to
use Kotest (e.g., shouldBe, shouldBeTrue/shouldBeFalse, shouldBeNull or
shouldBe) and change usages inside the tests in SendFormViewModelInitTest,
SendFormViewModelTronStakingTest, SendFormViewModelSubmitEvmTest,
SendFormViewModelAmountTest, and SendFormViewModelAddressTest to the
corresponding Kotest matchers (keeping the same semantics) so all assertions use
Kotest APIs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b368e03b-5dfa-4f72-b5db-dfd66eb9bd1b

📥 Commits

Reviewing files that changed from the base of the PR and between fcb373b and 8baf42f.

📒 Files selected for processing (4)
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAmountTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelInitTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelSubmitEvmTest.kt

…avior

Per nit on PR #4377: the test asserts that openAddressBook bails early
when selectedTokenValue is null, not that the address routes through.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@aminsato
Copy link
Copy Markdown
Collaborator Author

aminsato commented May 3, 2026

Addressing CodeRabbit nits:

Fixed (db8cd31): Renamed openAddressBook OUTPUT routes the address into addressFieldStateopenAddressBook is a no-op when no token is selected. The previous name described the asserted-against intent rather than the actual no-op assertion.

Deferred — Kotest assertions: CLAUDE.md does state "Use Kotest for assertions", but in practice:

  • 0 of 28 existing test files in app/src/test/ import io.kotest
  • All existing tests (including SendFormViewModelTronStakingTest.kt that this PR's buildViewModel() pattern was modeled on) use kotlin.test
  • Kotest is not declared as a dependency in app/build.gradle.kts, gradle/libs.versions.toml, or any module — the matchers aren't even on the test classpath

Adding the Kotest dependency and migrating one PR's worth of tests would create the lone Kotest island in the repo. That's a separate codebase-wide convention decision (either migrate everything or update CLAUDE.md to match reality), out of scope for a test-only safety-net PR. Happy to file a follow-up if there's appetite to standardize.

@aminsato
Copy link
Copy Markdown
Collaborator Author

aminsato commented May 3, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt`:
- Around line 199-214: The test openAddressBook is a no-op when no token is
selected should additionally assert that the address-book use case is not
called: after calling vm.openAddressBook(AddressBookType.OUTPUT) and
advanceUntilIdle(), add a coroutine verify against the mocked
requestAddressBookEntry to ensure it was never invoked (e.g., coVerify {
requestAddressBookEntry.invoke(any(), any()) wasNot Called } or coVerify(exactly
= 0) { requestAddressBookEntry.invoke(any(), any()) }); this guarantees
openAddressBook returned early before consulting the use case while keeping the
existing assertions on vm.addressFieldState.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d91ce53-d52e-4bf2-9570-5f2e35eea72b

📥 Commits

Reviewing files that changed from the base of the PR and between fcb373b and db8cd31.

📒 Files selected for processing (4)
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAmountTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelInitTest.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelSubmitEvmTest.kt

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt (1)

42-44: Replace UnconfinedTestDispatcher with StandardTestDispatcher for deterministic test scheduling.

The test suite extensively uses advanceUntilIdle() (lines 71, 74, 82, 85, etc.) to control coroutine execution order. UnconfinedTestDispatcher bypasses task queuing and can make advanceUntilIdle() ineffective for scheduling control, leading to unpredictable test behavior. StandardTestDispatcher(scheduler) ensures tasks are queued on the scheduler and allows advanceUntilIdle() to execute them deterministically, providing more reliable test stability and aligning with the test's reliance on explicit advancement calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt`
around lines 42 - 44, Replace the non-deterministic UnconfinedTestDispatcher
with a queued StandardTestDispatcher so tests that call advanceUntilIdle()
behave deterministically: update the definition of mainDispatcher (which
currently uses UnconfinedTestDispatcher(scheduler)) to use
StandardTestDispatcher(scheduler) and ensure any test setup that sets
Dispatchers.setMain(mainDispatcher) continues to use that mainDispatcher; this
keeps TestCoroutineScheduler + advanceUntilIdle() semantics intact for
SendFormViewModelAddressTest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt`:
- Around line 42-44: Replace the non-deterministic UnconfinedTestDispatcher with
a queued StandardTestDispatcher so tests that call advanceUntilIdle() behave
deterministically: update the definition of mainDispatcher (which currently uses
UnconfinedTestDispatcher(scheduler)) to use StandardTestDispatcher(scheduler)
and ensure any test setup that sets Dispatchers.setMain(mainDispatcher)
continues to use that mainDispatcher; this keeps TestCoroutineScheduler +
advanceUntilIdle() semantics intact for SendFormViewModelAddressTest.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9686f9f9-beba-4264-af4f-9b632e98d248

📥 Commits

Reviewing files that changed from the base of the PR and between db8cd31 and dd310d5.

📒 Files selected for processing (1)
  • app/src/test/java/com/vultisig/wallet/ui/models/send/SendFormViewModelAddressTest.kt

@aminsato
Copy link
Copy Markdown
Collaborator Author

aminsato commented May 3, 2026

@coderabbitai Skipping this one.

UnconfinedTestDispatcher is the established pattern across all 5 new SendFormViewModel*Test.kt files in this PR (and other ViewModel tests in the repo). It runs coroutines eagerly up to the first suspension, which is what these tests rely on for ViewModel init {} side effects to materialize before assertions. The trailing advanceUntilIdle() calls flush any remaining suspended work — they're a defensive belt-and-braces, not the primary scheduling mechanism.

Switching to StandardTestDispatcher would change semantics (init-block work no longer runs eagerly) and likely require additional advanceUntilIdle() calls before each action under test, not just after. Doing it for one file out of five would also break consistency. This is out of scope for a Phase A characterization PR — happy to revisit as a separate test-infra change if the team wants to standardize on StandardTestDispatcher repo-wide.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@aminsato: Understood — the reasoning is solid. UnconfinedTestDispatcher's eager execution is the right fit here since the ViewModel init {} effects need to have run before assertions, and consistency across all five test files matters more than a one-off change. I'll note the established pattern for future reviews.


✏️ Learnings added
Learnt from: aminsato
URL: https://github.com/vultisig/vultisig-android/pull/4377

Timestamp: 2026-05-03T11:45:58.441Z
Learning: In the Vultisig Android repo (`app/src/test/java/com/vultisig/wallet/ui/models/send/`), all `SendFormViewModel*Test.kt` files intentionally use `UnconfinedTestDispatcher` (not `StandardTestDispatcher`) as the main test dispatcher. This is the established repo-wide pattern for ViewModel unit tests. `UnconfinedTestDispatcher` is required so that ViewModel `init {}` coroutine side-effects run eagerly before assertions; trailing `advanceUntilIdle()` calls are defensive belt-and-braces to flush remaining suspended work, not the primary scheduling mechanism. Do not suggest switching to `StandardTestDispatcher` for these tests.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@johnnyluo johnnyluo added this pull request to the merge queue May 3, 2026
Merged via the queue into main with commit e6f11a4 May 3, 2026
2 checks passed
@johnnyluo johnnyluo deleted the aminsato/sendform_phase_a_tests branch May 3, 2026 22:30
y4motion pushed a commit to y4motion/vultisig-android that referenced this pull request May 9, 2026
…ultisig#4412)

* refactor(send): split submit functions into per-defiType strategies

Phase 3 of the SendFormViewModel refactor (PR 1 vultisig#4377, PR 2 vultisig#4393 already
merged). Each defiType branch in SendFormViewModel now lives behind a
dedicated SendSubmitStrategy implementation, so the VM no longer carries
the 8 submit functions, the 5 create*DepositTransaction helpers, the
inline accountValidation/awaitGasFee/getFeesFiatValue/getBitcoinTransactionPlan
helpers, or the AccountValidation data class. SendFormViewModel.kt drops
from 3261 → 1879 LOC.

The strategies are plain classes manually constructed in the VM init block
(matching the AddressManager / AmountManager precedent from PR 2). No Hilt
multibinding — defiType is a closed enum and one-class-per-concern keeps
the diff lift-and-shift. Public surface preserved: send() stays as a
one-line passthrough so the existing screen bindings and characterization
tests keep working; the four previously-private submit functions
(stake/mint/redeem/withDrawUSDCCircle) and the unused public bond/unbond/
unstake passthroughs are deleted because nothing outside the VM ever
called them. AccountValidator and BitcoinPlanService come out as small
shared collaborators (the latter still has one VM-side caller in the
collectPlanFee flow).

Adds 29 Phase C tests covering AccountValidator's validate() error
branches plus one error-path test per strategy (no_address, no_amount,
RUNE-balance-insufficient, operator-fee out of range, slippage,
WITHDRAW_RUJI silent early-return, DefaultSend pre-launch
expandSection+focus). Total send.* test count: 47 Phase A → 47, Phase B
managers 25 → 25, Phase C strategies 0 → 29 = 135 tests. All green via
./gradlew :app:testDebugUnitTest --tests "com.vultisig.wallet.ui.models.send.*".

Also clears the IDE warnings the linter surfaced on the trimmed VM:
unused BondAddress enum value, unused calculateGasLimit/enableAdvanceGasUi
helpers, the cascade if-else in loadAccounts (now a when over defiType),
the redundant R.string qualifier on one error-path message, and the
missing @param: use-site target on chainLogo.

Percentage/calculate functions (chooseMaxTokenAmount, choosePercentageAmount,
calculatePercentageWithAccurateFee, fetchPercentageOfAvailableBalance) are
deliberately deferred — they share the defiType switch with submit but
also write to gasFee/specific and orchestrate fee recalculation, a
different concern that would double the size of this PR.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>

* chore(send): drop unused bond/unbond/unstake VM passthroughs

These three public methods previously existed only so the in-VM
onClickContinue() switch could call them by name. After the strategy
extraction, onClickContinue dispatches directly to the strategies and
nothing outside the VM ever called bond/unbond/unstake — confirmed by
grep across app/src/. Keeping send() because the existing
SendFormViewModelSubmitEvmTest characterization tests still call it.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>

* fix(send): address CodeRabbit review on Phase 3 strategies

Five fixes raised in PR vultisig#4412 review:

1. Rethrow CancellationException in every strategy submit catch block (and in
   the inner resolveName/resolveDstAddress catches in BondStrategy,
   UnbondStrategy, DefaultSendStrategy, AccountValidator). The broad
   catch (e: Exception) was converting normal coroutine cancellation into a
   spurious error toast instead of letting it propagate.

2. DefaultSendStrategy: drop the redundant first chainAccountAddressRepository
   .isValid check on the raw input. The check on the resolved dstAddress a few
   lines later already covers the validation, and the raw-input check broke
   the corner case where the user types an ENS/thorname and taps send before
   AddressManager's debounce has run — the first check would reject the name
   before resolveDstAddress could translate it.

3. UnstakeStrategy WITHDRAW_RUJI branch: replace the silent `?: return@launch`
   when the RUJI account lookup misses with `throw InvalidTransactionDataException(
   send_error_no_token)`. Previously tapping Continue with no RUJI account just
   appeared to do nothing — now the user sees an error.

4. UnstakeStrategy: wrap the getSpecific calls in createRUJIRewardsDeposit
   Transaction and createYTCUnstakeDepositTransaction with `withContext(
   Dispatchers.IO)`. Only createRUJIUnstakeDepositTransaction had it
   previously; the other two were running blocking repository calls on the
   caller dispatcher.

5. UnstakeStrategy: replace the BigInteger→Double basis-points calculation in
   createYTCUnstakeDepositTransaction with integer math
   (tokenAmountInt * 10_000 / totalTokenAmount) so token-sized values don't
   lose precision and emit the wrong unstake memo.

Updates UnstakeStrategyTest's WITHDRAW_RUJI test from "returns silently when
no RUJI account exists" to "surfaces no_token error" to match vultisig#3. All 135
send.* tests still green.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>

* refactor(send): address review comments on submit strategies

Code:
- enumerate every DeFiNavActions in onClickContinue so a future variant
  fails to compile instead of silently routing through DefaultSendStrategy
- drop the test-only fun send() passthrough; tests use onClickContinue()
- DefaultSendStrategy now goes through accountValidator.validate() and
  wraps getSpecific in Dispatchers.IO so the regular send path matches
  every other strategy
- guard every strategy submit() with a Job? so a fast double-tap cannot
  launch two concurrent submits and persist a duplicate transaction
- replace error()/RuntimeException paths in Mint/Redeem/Stake/Unstake/
  WithdrawUsdcCircle with InvalidTransactionDataException so the dialog
  no longer leaks raw English strings or enum identifiers
- bind nonDeFiAccount to a non-null val in WithdrawUsdcCircleStrategy
  so the later .token access is safe under any future guard tweak
- swap the UiText.Empty fallback in every catch (e: Exception) for
  R.string.dialog_default_error_body so the dialog body is never blank

Tests:
- add a positive happy-path test to every strategy that reaches
  addTransaction(...) and asserts on the produced Transaction or
  DepositTransaction body — memo, dstAddress, contract address, and
  the ruji/tcy/ytcy/yrune contract selection inside the variant flows
- de-relax the accountsRepository mock in WithdrawUsdcCircleStrategyTest
  and stub loadAddresses per test so future fixtures actually exercise
  the nonDeFiBalance < gasFee guard
- mockkObject(ThorchainFunctions) and mockkObject(EthereumFunction) so
  the JNI-bound encoders don't require Trust Wallet Core's native
  library at JVM unit-test time

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hailhydra <johnnyluo1980@gmail.com>
Co-authored-by: Roman <32820910+rkokhatskyi@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants