Skip to content

Commit 32d164d

Browse files
bmc08gtclaude
andcommitted
test: expand CashScreen, Swap, and OnRamp ViewModel state coverage
Add 18 tests covering currency change reducers, OnSwapIdChanged, Solflare/Backpack provider selection, loading state reset roundtrips, and purpose-specific computed properties (netTransferAmount, feeAmount, transactionLimit, maxAvailableToSwap). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b963eea commit 32d164d

3 files changed

Lines changed: 174 additions & 0 deletions

File tree

apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelStateTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.flipcash.app.cash.internal
22

3+
import com.getcode.opencode.model.financial.Currency
34
import com.getcode.opencode.model.financial.CurrencyCode
45
import com.getcode.opencode.model.financial.Fiat
56
import com.getcode.solana.keys.Mint
67
import com.getcode.ui.components.text.AmountAnimatedInputUiModel
78
import kotlin.test.Test
89
import kotlin.test.assertEquals
910
import kotlin.test.assertFalse
11+
import kotlin.test.assertNotNull
1012
import kotlin.test.assertNull
1113
import kotlin.test.assertTrue
1214

@@ -77,6 +79,26 @@ class CashScreenViewModelStateTest {
7779
assertNull(updated.limits)
7880
}
7981

82+
@Test
83+
fun `OnCurrencyChanged sets currencyModel`() {
84+
val currency = Currency(code = "EUR", name = "Euro", symbol = "", rate = 0.92)
85+
val updated = reduce(
86+
CashScreenViewModel.Event.OnCurrencyChanged(currency)
87+
)(CashScreenViewModel.State())
88+
assertNotNull(updated.currencyModel.selected)
89+
assertEquals("EUR", updated.currencyModel.selected?.code)
90+
assertEquals(CurrencyCode.EUR, updated.currencyModel.code)
91+
}
92+
93+
@Test
94+
fun `OnCurrencyChanged preserves fractionUnits`() {
95+
val currency = Currency(code = "JPY", name = "Japanese Yen", symbol = "¥", fractionUnits = 0)
96+
val updated = reduce(
97+
CashScreenViewModel.Event.OnCurrencyChanged(currency)
98+
)(CashScreenViewModel.State())
99+
assertEquals(0, updated.currencyModel.fractionUnits)
100+
}
101+
80102
// --- No-op events ---
81103

82104
@Test

apps/flipcash/features/onramp/src/test/kotlin/com/flipcash/app/onramp/internal/OnRampViewModelStateTest.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.flipcash.app.onramp.internal
33
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
44
import com.flipcash.services.internal.model.thirdparty.OnRampType
55
import com.getcode.opencode.exchange.VerifiedFiat
6+
import com.getcode.opencode.model.financial.Currency
67
import com.getcode.opencode.model.financial.CurrencyCode
78
import com.getcode.opencode.model.financial.Fiat
89
import com.getcode.opencode.model.financial.LocalFiat
@@ -11,6 +12,7 @@ import com.getcode.ui.components.text.AmountAnimatedInputUiModel
1112
import kotlin.test.Test
1213
import kotlin.test.assertEquals
1314
import kotlin.test.assertFalse
15+
import kotlin.test.assertNotNull
1416
import kotlin.test.assertNull
1517
import kotlin.test.assertTrue
1618

@@ -91,6 +93,35 @@ class OnRampViewModelStateTest {
9193
assertTrue(updated.canChangeCurrency)
9294
}
9395

96+
@Test
97+
fun `OnProviderSelected with Solflare enables currency change`() {
98+
val updated = reduce(
99+
OnRampViewModel.Event.OnProviderSelected(OnRampProvider.Solflare)
100+
)(OnRampViewModel.State())
101+
assertTrue(updated.canChangeCurrency)
102+
assertEquals(OnRampProvider.Solflare, updated.selectedProvider)
103+
}
104+
105+
@Test
106+
fun `OnProviderSelected with Backpack enables currency change`() {
107+
val updated = reduce(
108+
OnRampViewModel.Event.OnProviderSelected(OnRampProvider.Backpack)
109+
)(OnRampViewModel.State())
110+
assertTrue(updated.canChangeCurrency)
111+
assertEquals(OnRampProvider.Backpack, updated.selectedProvider)
112+
}
113+
114+
@Test
115+
fun `OnCurrencyChanged sets currencyModel`() {
116+
val currency = Currency(code = "CAD", name = "Canadian Dollar", symbol = "$", rate = 1.36)
117+
val updated = reduce(
118+
OnRampViewModel.Event.OnCurrencyChanged(currency)
119+
)(OnRampViewModel.State())
120+
assertNotNull(updated.amountEntryState.currencyModel.selected)
121+
assertEquals("CAD", updated.amountEntryState.currencyModel.selected?.code)
122+
assertEquals(CurrencyCode.CAD, updated.amountEntryState.currencyModel.code)
123+
}
124+
94125
@Test
95126
fun `OnAmountAccepted sets selectedAmount`() {
96127
val amount = VerifiedFiat(LocalFiat.Zero, null)
@@ -142,6 +173,21 @@ class OnRampViewModelStateTest {
142173
assertFalse(updated.amountEntryState.confirmingAmount.loading)
143174
}
144175

176+
@Test
177+
fun `UpdateConfirmingAmountState resets to idle`() {
178+
// First set loading, then reset with defaults
179+
val loading = reduce(
180+
OnRampViewModel.Event.UpdateConfirmingAmountState(loading = true)
181+
)(OnRampViewModel.State())
182+
assertTrue(loading.amountEntryState.confirmingAmount.loading)
183+
184+
val reset = reduce(
185+
OnRampViewModel.Event.UpdateConfirmingAmountState()
186+
)(loading)
187+
assertFalse(reset.amountEntryState.confirmingAmount.loading)
188+
assertFalse(reset.amountEntryState.confirmingAmount.success)
189+
}
190+
145191
@Test
146192
fun `UpdateOrderLookupState sets loading and success`() {
147193
val loading = reduce(
@@ -155,6 +201,20 @@ class OnRampViewModelStateTest {
155201
assertTrue(success.orderLookup.success)
156202
}
157203

204+
@Test
205+
fun `UpdateOrderLookupState resets to idle`() {
206+
val loading = reduce(
207+
OnRampViewModel.Event.UpdateOrderLookupState(loading = true)
208+
)(OnRampViewModel.State())
209+
assertTrue(loading.orderLookup.loading)
210+
211+
val reset = reduce(
212+
OnRampViewModel.Event.UpdateOrderLookupState()
213+
)(loading)
214+
assertFalse(reset.orderLookup.loading)
215+
assertFalse(reset.orderLookup.success)
216+
}
217+
158218
// --- No-op events ---
159219

160220
@Test

apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.flipcash.app.tokens.ui
22

33
import com.flipcash.app.core.tokens.SwapPurpose
44
import com.getcode.opencode.exchange.VerifiedFiat
5+
import com.getcode.opencode.internal.solana.model.SwapId
6+
import com.getcode.opencode.model.financial.Currency
57
import com.getcode.opencode.model.financial.CurrencyCode
68
import com.getcode.opencode.model.financial.Fiat
79
import com.getcode.opencode.model.financial.LocalFiat
@@ -11,6 +13,7 @@ import com.getcode.view.LoadingSuccessState
1113
import kotlin.test.Test
1214
import kotlin.test.assertEquals
1315
import kotlin.test.assertFalse
16+
import kotlin.test.assertNotNull
1417
import kotlin.test.assertNull
1518
import kotlin.test.assertTrue
1619

@@ -113,6 +116,26 @@ class SwapViewModelStateTest {
113116
assertNull(updated.amountEntryState.limits)
114117
}
115118

119+
@Test
120+
fun `OnCurrencyChanged sets currencyModel in entry state`() {
121+
val currency = Currency(code = "GBP", name = "British Pound", symbol = "£", rate = 0.79)
122+
val updated = reduce(
123+
SwapViewModel.Event.OnCurrencyChanged(currency)
124+
)(SwapViewModel.State())
125+
assertNotNull(updated.amountEntryState.currencyModel.selected)
126+
assertEquals("GBP", updated.amountEntryState.currencyModel.selected?.code)
127+
assertEquals(CurrencyCode.GBP, updated.amountEntryState.currencyModel.code)
128+
}
129+
130+
@Test
131+
fun `OnSwapIdChanged sets swapId`() {
132+
val swapId = SwapId(ByteArray(32) { 2 }.toList())
133+
val updated = reduce(
134+
SwapViewModel.Event.OnSwapIdChanged(swapId)
135+
)(SwapViewModel.State())
136+
assertEquals(swapId, updated.swapId)
137+
}
138+
116139
// --- No-op events ---
117140

118141
@Test
@@ -211,6 +234,75 @@ class SwapViewModelStateTest {
211234
assertEquals(confirmed, state.netTransferAmount)
212235
}
213236

237+
// --- Computed: netTransferAmount ---
238+
239+
@Test
240+
fun `netTransferAmount falls back to enteredAmount for BalanceIncrease purpose`() {
241+
// Buy is a BalanceIncrease, so netTransferAmount = enteredAmount when confirmedNetTransferAmount is null
242+
val state = SwapViewModel.State(purpose = SwapPurpose.Buy(mint()))
243+
// Default enteredAmount is Fiat(0.0, CurrencyCode.USD) since tokenBalance is Zero
244+
assertEquals(state.enteredAmount, state.netTransferAmount)
245+
}
246+
247+
@Test
248+
fun `netTransferAmount subtracts fee for non-BalanceIncrease purpose`() {
249+
// Sell is BalanceDecrease, so netTransferAmount = enteredAmount - feeAmount
250+
// With no tokenWithBalance, sellFee is null, feeAmount = Zero, so net = entered - 0 = entered
251+
val state = SwapViewModel.State(purpose = SwapPurpose.Sell(mint()))
252+
assertEquals(state.enteredAmount.decimalValue - state.feeAmount.decimalValue, state.netTransferAmount.decimalValue, 0.001)
253+
}
254+
255+
// --- Computed: enteredAmount ---
256+
257+
@Test
258+
fun `enteredAmount defaults to zero with USD currency`() {
259+
val state = SwapViewModel.State()
260+
assertEquals(0.0, state.enteredAmount.decimalValue, 0.001)
261+
assertEquals(CurrencyCode.USD, state.enteredAmount.currencyCode)
262+
}
263+
264+
// --- Computed: feeAmount ---
265+
266+
@Test
267+
fun `feeAmount is Zero when sellFee is null`() {
268+
assertEquals(Fiat.Zero, SwapViewModel.State().feeAmount)
269+
}
270+
271+
// --- Computed: maxAvailableToSwap per purpose ---
272+
273+
@Test
274+
fun `maxAvailableToSwap for FundWithWallet uses maxToAdd`() {
275+
val state = SwapViewModel.State(
276+
purpose = SwapPurpose.FundWithWallet(mint()),
277+
amountEntryState = AmountEntryState(maxToAdd = 200.0 to CurrencyCode.USD)
278+
)
279+
assertTrue(state.maxAvailableToSwap.isNotEmpty())
280+
}
281+
282+
@Test
283+
fun `maxAvailableToSwap for FundWithWallet is empty when maxToAdd is null`() {
284+
val state = SwapViewModel.State(
285+
purpose = SwapPurpose.FundWithWallet(mint()),
286+
)
287+
assertEquals("", state.maxAvailableToSwap)
288+
}
289+
290+
// --- Computed: transactionLimit per purpose ---
291+
292+
@Test
293+
fun `transactionLimit for Buy is reservesBalance`() {
294+
val state = SwapViewModel.State(purpose = SwapPurpose.Buy(mint()))
295+
// reservesWithBalance is null → reservesBalance = Fiat.Zero
296+
assertEquals(Fiat.Zero, state.transactionLimit)
297+
}
298+
299+
@Test
300+
fun `transactionLimit for Sell is tokenBalance`() {
301+
val state = SwapViewModel.State(purpose = SwapPurpose.Sell(mint()))
302+
// tokenWithBalance is null → tokenBalance = Fiat.Zero
303+
assertEquals(Fiat.Zero, state.transactionLimit)
304+
}
305+
214306
// --- Computed: isError ---
215307

216308
@Test

0 commit comments

Comments
 (0)