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)