11package to.bitkit.ui.screens.widgets.calculator
22
3+ import androidx.compose.foundation.layout.Box
4+ import androidx.compose.foundation.layout.fillMaxSize
35import androidx.compose.foundation.layout.fillMaxWidth
6+ import androidx.compose.runtime.collectAsState
7+ import androidx.compose.runtime.getValue
8+ import androidx.compose.ui.Alignment
49import androidx.compose.ui.Modifier
5- import androidx.compose.ui.test.assertTextContains
6- import androidx.compose.ui.test.assertIsDisplayed
7- import androidx.compose.ui.test.hasSetTextAction
8- import androidx.compose.ui.test.hasText
910import androidx.compose.ui.test.junit4.createComposeRule
10- import androidx.compose.ui.test.onRoot
11- import androidx.compose.ui.test.performTextClearance
12- import androidx.compose.ui.test.performTextInput
13- import androidx.compose.ui.test.printToString
11+ import androidx.compose.ui.test.onAllNodesWithTag
12+ import androidx.compose.ui.test.onNodeWithTag
13+ import androidx.compose.ui.test.performClick
1414import androidx.lifecycle.ViewModel
1515import androidx.lifecycle.ViewModelProvider
1616import androidx.lifecycle.ViewModelStore
@@ -50,6 +50,7 @@ import to.bitkit.test.annotations.CalculatorWidget
5050import to.bitkit.test.annotations.DeviceIntegration
5151import to.bitkit.test.annotations.DeviceUiIntegration
5252import to.bitkit.ui.screens.widgets.calculator.components.CalculatorCard
53+ import to.bitkit.ui.screens.widgets.calculator.components.CalculatorNumberPadBar
5354import to.bitkit.ui.theme.AppThemeSurface
5455import java.util.Locale
5556import javax.inject.Inject
@@ -62,7 +63,7 @@ import kotlin.test.assertEquals
6263@CalculatorWidget
6364@DeviceIntegration
6465@DeviceUiIntegration
65- class CalculatorCardIntegrationTest {
66+ class CalculatorWidgetInputTest {
6667
6768 @get:Rule
6869 val hiltRule = HiltAndroidRule (this )
@@ -154,41 +155,27 @@ class CalculatorCardIntegrationTest {
154155 }
155156
156157 @Test
157- fun btcInputUpdatesFiatValueAndPersistsWidgetState () {
158- setCalculatorCard ()
158+ fun btcInputViaNumberPadUpdatesFiatAndPersistsWidgetState () {
159+ setCalculatorWidget ()
159160
160- replaceInput(BTC_INPUT_INDEX , " 12340" )
161+ composeTestRule.onNodeWithTag(BTC_INPUT_TAG ).performClick()
162+ awaitNumberPad()
163+ tapKeys(" N1" , " N2" , " N3" , " N4" , " N0" )
161164
162- waitForValues(
163- btcValue = " 12340" ,
164- fiatValue = " 12.34" ,
165- )
166-
167- assertInputText(BTC_INPUT_INDEX , " 12 340" )
168- assertInputText(FIAT_INPUT_INDEX , " 12.34" )
169- assertPersistedValues(
170- btcValue = " 12340" ,
171- fiatValue = " 12.34" ,
172- )
165+ waitForValues(btcValue = " 12340" , fiatValue = " 12.34" )
166+ assertPersistedValues(btcValue = " 12340" , fiatValue = " 12.34" )
173167 }
174168
175169 @Test
176- fun fiatInputUpdatesBtcValueAndPersistsWidgetState () {
177- setCalculatorCard ()
170+ fun fiatInputViaNumberPadUpdatesBtcAndPersistsWidgetState () {
171+ setCalculatorWidget ()
178172
179- replaceInput(FIAT_INPUT_INDEX , " 10.00" )
173+ composeTestRule.onNodeWithTag(FIAT_INPUT_TAG ).performClick()
174+ awaitNumberPad()
175+ tapKeys(" N1" , " N0" , " NDecimal" , " N0" , " N0" )
180176
181- waitForValues(
182- btcValue = " 10000" ,
183- fiatValue = " 10.00" ,
184- )
185-
186- assertInputText(BTC_INPUT_INDEX , " 10 000" )
187- assertInputText(FIAT_INPUT_INDEX , " 10.00" )
188- assertPersistedValues(
189- btcValue = " 10000" ,
190- fiatValue = " 10.00" ,
191- )
177+ waitForValues(btcValue = " 10000" , fiatValue = " 10.00" )
178+ assertPersistedValues(btcValue = " 10000" , fiatValue = " 10.00" )
192179 }
193180
194181 private fun createCalculatorViewModel (): CalculatorViewModel {
@@ -206,80 +193,70 @@ class CalculatorCardIntegrationTest {
206193 )[CalculatorViewModel ::class .java]
207194 }
208195
209- private fun setCalculatorCard () {
196+ private fun setCalculatorWidget () {
210197 composeTestRule.setContent {
211198 AppThemeSurface {
212- CalculatorCard (
213- calculatorViewModel = calculatorViewModel,
214- modifier = Modifier .fillMaxWidth()
215- )
199+ val state by calculatorViewModel.uiState.collectAsState()
200+ Box (modifier = Modifier .fillMaxSize()) {
201+ CalculatorCard (
202+ btcPrimaryDisplayUnit = state.displayUnit,
203+ btcValue = state.btcValue,
204+ fiatSymbol = state.currencySymbol,
205+ fiatName = state.selectedCurrency,
206+ fiatValue = state.fiatValue,
207+ activeInput = state.activeInput,
208+ onSelectInput = calculatorViewModel::onInputSelected,
209+ modifier = Modifier .fillMaxWidth()
210+ )
211+ state.activeInput?.let { active ->
212+ CalculatorNumberPadBar (
213+ activeInput = active,
214+ btcValue = state.btcValue,
215+ fiatValue = state.fiatValue,
216+ btcPrimaryDisplayUnit = state.displayUnit,
217+ onBtcChange = calculatorViewModel::onBtcInputChanged,
218+ onFiatChange = calculatorViewModel::onFiatInputChanged,
219+ modifier = Modifier .align(Alignment .BottomCenter )
220+ )
221+ }
222+ }
216223 }
217224 }
218225 composeTestRule.waitForIdle()
226+ }
227+
228+ private fun awaitNumberPad () {
219229 composeTestRule.waitUntil(timeoutMillis = TIMEOUT_MS ) {
220- composeTestRule.onAllNodes(hasSetTextAction()) .fetchSemanticsNodes().size == INPUT_COUNT
230+ composeTestRule.onAllNodesWithTag( NUMBER_PAD_TAG ) .fetchSemanticsNodes().isNotEmpty()
221231 }
222232 }
223233
224- private fun inputAt (index : Int ) = composeTestRule.onAllNodes(hasSetTextAction())[index]
225-
226- private fun replaceInput (
227- index : Int ,
228- text : String ,
229- ) {
230- inputAt(index).performTextClearance()
231- inputAt(index).performTextInput(text)
234+ private fun tapKeys (vararg keys : String ) {
235+ keys.forEach { key ->
236+ composeTestRule.onNodeWithTag(key).performClick()
237+ composeTestRule.waitForIdle()
238+ }
232239 }
233240
234241 private fun waitForValues (
235242 btcValue : String ,
236243 fiatValue : String ,
237244 ) {
238- runCatching {
239- composeTestRule.waitUntil(timeoutMillis = TIMEOUT_MS ) {
240- calculatorViewModel.uiState.value.btcValue == btcValue &&
241- calculatorViewModel.uiState.value.fiatValue == fiatValue
242- }
243- }.onFailure {
244- throw AssertionError (
245- buildString {
246- append(" Expected calculatorValues btcValue='$btcValue ', fiatValue='$fiatValue ', " )
247- append(" but was '${calculatorViewModel.uiState.value} '. Persisted values were " )
248- append(" '${widgetsRepo.widgetsDataFlow.value.calculatorValues} '. Semantics tree:\n " )
249- append(composeTestRule.onRoot(useUnmergedTree = true ).printToString())
250- },
251- it,
252- )
245+ composeTestRule.waitUntil(timeoutMillis = TIMEOUT_MS ) {
246+ calculatorViewModel.uiState.value.btcValue == btcValue &&
247+ calculatorViewModel.uiState.value.fiatValue == fiatValue
253248 }
254-
255249 val expectedValues = CalculatorValues (
256250 btcValue = btcValue,
257251 fiatValue = fiatValue,
258252 satsValue = btcValue.toLong(),
259253 displayUnit = BitcoinDisplayUnit .MODERN ,
260254 )
261- runCatching {
262- composeTestRule.waitUntil(timeoutMillis = TIMEOUT_MS ) {
263- widgetsRepo.widgetsDataFlow.value.calculatorValues == expectedValues
264- }
265- }.onFailure {
266- throw AssertionError (
267- " Expected persisted values '$expectedValues ', but was " +
268- " '${widgetsRepo.widgetsDataFlow.value.calculatorValues} '" ,
269- it,
270- )
255+ composeTestRule.waitUntil(timeoutMillis = TIMEOUT_MS ) {
256+ widgetsRepo.widgetsDataFlow.value.calculatorValues == expectedValues
271257 }
272258 }
273259
274- private fun assertInputText (
275- inputIndex : Int ,
276- text : String ,
277- ) {
278- inputAt(inputIndex).assertTextContains(text, substring = true )
279- composeTestRule.onNode(hasText(text, substring = true ), useUnmergedTree = true )
280- .assertIsDisplayed()
281- }
282-
283260 private fun assertPersistedValues (
284261 btcValue : String ,
285262 fiatValue : String ,
@@ -296,9 +273,9 @@ class CalculatorCardIntegrationTest {
296273 }
297274
298275 companion object {
299- private const val BTC_INPUT_INDEX = 0
300- private const val FIAT_INPUT_INDEX = 1
301- private const val INPUT_COUNT = 2
276+ private const val BTC_INPUT_TAG = " CalculatorBtcInput "
277+ private const val FIAT_INPUT_TAG = " CalculatorFiatInput "
278+ private const val NUMBER_PAD_TAG = " CalculatorNumberPad "
302279 private const val TIMEOUT_MS = 5_000L
303280 private const val TEST_CREATED_AT = 0L
304281 private const val TEST_USD_RATE = " 100000"
@@ -330,6 +307,7 @@ class CalculatorCardIntegrationTest {
330307
331308 @Provides
332309 @Named(" enablePolling" )
310+ @Suppress(" FunctionOnlyReturningConstant" )
333311 fun provideEnablePolling (): Boolean = false
334312 }
335313}
0 commit comments