nitrog42
04/29/2022, 3:39 PMdelay() in a LaunchedEffect
code in replynitrog42
04/29/2022, 3:39 PM@Test
fun test() {
    val testDispatcher = StandardTestDispatcher()
    composeTestRule.setContent {
        Box {
            var text by remember {
                mutableStateOf("")
            }
            LaunchedEffect(Unit) {
                var index = 0
                while (isActive) {
                    withContext(testDispatcher) {
                        delay(1000)
                    }
                    text = "Hello $index"
                }
            }
            Text(text = text)
        }
    }
    composeTestRule.waitUntil {
        composeTestRule.onAllNodesWithText("Hello 1").fetchSemanticsNodes().size == 1
    }
    composeTestRule.waitUntil {
        composeTestRule.onAllNodesWithText("Hello 2").fetchSemanticsNodes().size == 1
    }
}nitrog42
04/29/2022, 3:42 PMadvanceUntilIdle works, but the issue I have is that in my screen the LaunchedEffect/delay can run multiple time, and my test will not be aware of that
what I'm struggling to understand is that :
LaunchedEffect(Unit) {
    withContext(testDispatcher) {
        delay(1)
    }
}
will lock the test unless I call testDispatcher.scheduler.advanceTime() 
when I just want to "always" skip the delaysnitrog42
04/29/2022, 3:48 PMcomposeTestRule.onNodeWithText(phoneNumberHintField).performTextInput(validPhoneNumber)
which should trigger a LaunchedEffect :
LaunchedEffect(text) {
    withContext(debounceDispatcher) {
        delay(600) //Debounce
        //heavy operation on text
        text = newText
    }
}
and
composeTestRule.onNodeWithText(phoneNumberHintField).performTextInput(validPhoneNumber)
debounceDispatcher.scheduler.advanceUntilIdle()
Is basically not working, probably running too soon ? not sure why but it seems there is a delay on the performtextinput and Launchedeffect run...nitrog42
05/02/2022, 12:16 PMcomposeTestRule.waitUntil {
        advanceUntilIdle()
        composeTestRule.onAllNodesWithText("Hello").fetchSemanticsNodes().size == 1
    }Zach Klippenstein (he/him) [MOD]
05/17/2022, 11:14 PMadvanceUntilIdle() in that block? I thought waitUntil did that implicitly.nitrog42
05/18/2022, 8:07 AMadvanceUntilIdle() because I run the composeTest with runTest { }
like this :
@Test
    fun testFormatValidNumber() = runTest {
        //The testdispatcher is mandatory here as we want to avoid the delay of a debounce for the test
        val testDispatcher = StandardTestDispatcher(this.testScheduler)
        composeTestRule.setContent {
            
                EnterPhoneScreen(onNavigateUp = { }, onValidateClicked = {}, parseDispatcher = testDispatcher)
            
        }
        val enteredPhoneNumber = "0612345678"
        val displayedPhoneNumber = "06 12 34 56 78"
        val formattedPhoneNumber = "+33 6 12 34 56 78"
        composeTestRule.onNodeWithText(phoneNumberHintField).performTextInput(enteredPhoneNumber)
        composeTestRule.waitUntil {
            composeTestRule.onAllNodesWithText(displayedPhoneNumber).fetchSemanticsNodes().size == 1
        }
        composeTestRule.waitUntil {
            advanceUntilIdle()
            composeTestRule.onAllNodesWithText(formattedPhoneNumber).fetchSemanticsNodes().size == 1
        }
        composeTestRule.onNodeWithText(validateButtonText).assertIsDisplayed().assertIsEnabled()
    }nitrog42
05/18/2022, 8:07 AMnitrog42
05/18/2022, 8:09 AMLaunchedEffect(phoneNumberText.value) {
            Timber.d("parsing number $phoneNumberText")
            phoneNumber = null
            // just a simple debounce
            delay(600L)
            withContext(parseDispatcher) {
                try {
                    // We parse the number entered by the user only if it's detected as a mobile phone number
                    phoneNumber = phoneNumberUtil.parseNumberIfMobile(phoneNumberText.value.text)?.also {
                        //We reformat the number as E164
                        val newFormattedNumber = phoneNumberUtil.format(it, PhoneNumberUtil.PhoneNumberFormat.E164)
                        //We update the visual with the new formatted number (E164) ; fromUser = false
                        phoneNumberText = TextFieldWithChangeSource(phoneNumberText.value.updatePhone(newFormattedNumber))
                    }
                } catch (e: Exception) {
                    Timber.e(e)
                }
                Timber.d("parsed number $phoneNumberText")
            }
        }
(phoneNumber is the PhoneNumber class, phoneNumberText is the standard String that I use in the field ; TextFieldWithChangeSource is a specific class I made to know if the user wrote the change or it comes from the “parser/reformatter” that takes a few hundreds of millisec)