Skip to content

Commit

Permalink
Add automated test for keys in scope bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
sebaslogen committed Jul 16, 2024
1 parent e65885d commit fa4a3c8
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ class ComposeActivityRecreationTests : ComposeTestUtils {

@Before
fun setUp() {
showSingleScopedViewModel = null
scenario = ActivityScenario.launch(
Intent(ApplicationProvider.getApplicationContext(), ComposeActivity::class.java).apply {
putExtra(ComposeActivity.START_DESTINATION, viewModelScopedDestination)
})
}

@Test
fun whenISwitchFromLightModeToNightMode_thenTheOneAndOnlyScopedViewModelThatSOnlyUsedInLightModeIsGone() {
fun whenActivityRecreates_thenTheOneAndOnlyScopedViewModelThatSOnlyUsedInLightModeIsGone() {
// Given the starting screen with ViewModel scoped that is ONLY shown in light mode
composeTestRule.waitForIdle()
// Find the scoped text fields and grab their texts
Expand All @@ -54,7 +55,7 @@ class ComposeActivityRecreationTests : ComposeTestUtils {
onNodeWithTestTag("FakeInjectedViewModel Scoped", assertDisplayed = false).assertDoesNotExist()
assert(finalAmountOfViewModelsCleared == initialAmountOfViewModelsCleared + 1) {
"The amount of FakeInjectedViewModel that were cleared after key change ($finalAmountOfViewModelsCleared) " +
"was not higher that the amount before the key change ($initialAmountOfViewModelsCleared)"
"was not higher that the amount before the key change ($initialAmountOfViewModelsCleared)"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.sebaslogen.resacaapp.sample

import android.content.Intent
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performScrollToIndex
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.sebaslogen.resaca.COMPOSITION_RESUMED_TIMEOUT_IN_SECONDS
import com.sebaslogen.resacaapp.sample.ui.main.ComposeActivity
import com.sebaslogen.resacaapp.sample.ui.main.viewModelScopedWithKeysDestination
import com.sebaslogen.resacaapp.sample.utils.ComposeTestUtils
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith


@RunWith(AndroidJUnit4::class)
@LargeTest
class KeysInScopeActivityRecreationTests : ComposeTestUtils {

private lateinit var scenario: ActivityScenario<ComposeActivity>

@get:Rule
override val composeTestRule = createEmptyComposeRule()

@Before
fun setUp() {
scenario = ActivityScenario.launch(
Intent(ApplicationProvider.getApplicationContext(), ComposeActivity::class.java).apply {
putExtra(ComposeActivity.START_DESTINATION, viewModelScopedWithKeysDestination)
})
}

@Test
fun givenAListOfKeysAndALazyListOfScopedViewModelsScrolledToTheMiddle_whenActivityRecreatesAndIScrollBackToTop_thenTheOriginalScopedViewModelIsStillPresent() {
// Given the starting screen with ViewModels scoped
composeTestRule.waitForIdle()
// Find the scoped text fields and grab their texts
val initialFakeScopedViewModelText = retrieveTextFromNodeWithTestTag("FakeScopedViewModel 1 Scoped")
printComposeUiTreeToLog()
// Scroll away from first item
composeTestRule.onNodeWithTag("LazyList").performScrollToIndex(50)

// When we trigger a configuration change by recreating the Activity and scroll back to the top
scenario.recreate()
printComposeUiTreeToLog()
Thread.sleep(COMPOSITION_RESUMED_TIMEOUT_IN_SECONDS * 1000) // Wait for the ViewModel to be cleared
composeTestRule.onNodeWithTag("LazyList").performScrollToIndex(0)

// Then the scoped ViewModel disappears
onNodeWithTestTag("FakeScopedViewModel 1 Scoped").assertIsDisplayed().assertTextEquals(initialFakeScopedViewModelText)
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
package com.sebaslogen.resacaapp.sample.utils

import android.content.Intent
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToLog
import androidx.compose.ui.text.AnnotatedString
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import com.sebaslogen.resacaapp.sample.ui.main.ComposeActivity
import com.sebaslogen.resacaapp.sample.ui.main.koinViewModelScopedDestination
import com.sebaslogen.resacaapp.sample.ui.main.showSingleScopedViewModel
import org.junit.After
import org.junit.Before
import org.koin.core.context.stopKoin

interface ComposeTestUtils {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.sebaslogen.resaca.rememberKeysInScope
Expand All @@ -34,7 +35,7 @@ fun ComposeScreenWithSingleViewModelScopedWithKeys(navController: NavHostControl
text = "The list below contains one ViewModel per row that will stay in memory due to KeysInScope as long as the list and screen are displayed"
)
val keys = rememberKeysInScope(inputListOfKeys = listItems)
LazyColumn(modifier = Modifier.fillMaxHeight()) {
LazyColumn(modifier = Modifier.fillMaxHeight().testTag("LazyList")) {
items(items = listItems, key = { it.number }) { item ->
val fakeScopedVM: FakeScopedViewModel = viewModelScoped(key = item, keyInScopeResolver = keys)
DemoComposable(inputObject = fakeScopedVM, objectType = "FakeScopedViewModel $item", scoped = true)
Expand Down

0 comments on commit fa4a3c8

Please sign in to comment.