Skip to content

Commit 5f9d8b7

Browse files
committed
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). Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent cc41191 commit 5f9d8b7

2 files changed

Lines changed: 107 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/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt

Lines changed: 85 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,68 @@ 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 Buy uses maxToAdd`() {
275+
val state = SwapViewModel.State(
276+
purpose = SwapPurpose.Buy(mint()),
277+
amountEntryState = AmountEntryState(maxToAdd = 200.0 to CurrencyCode.USD)
278+
)
279+
assertTrue(state.maxAvailableToSwap.isNotEmpty())
280+
}
281+
282+
@Test
283+
fun `maxAvailableToSwap for Buy is empty when maxToAdd is null`() {
284+
val state = SwapViewModel.State(
285+
purpose = SwapPurpose.Buy(mint()),
286+
)
287+
assertEquals("", state.maxAvailableToSwap)
288+
}
289+
290+
// --- Computed: transactionLimit per purpose ---
291+
292+
@Test
293+
fun `transactionLimit for Sell is tokenBalance`() {
294+
val state = SwapViewModel.State(purpose = SwapPurpose.Sell(mint()))
295+
// tokenWithBalance is null → tokenBalance = Fiat.Zero
296+
assertEquals(Fiat.Zero, state.transactionLimit)
297+
}
298+
214299
// --- Computed: isError ---
215300

216301
@Test

0 commit comments

Comments
 (0)