diff --git a/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt b/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt index 8fddc98..f768093 100644 --- a/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt @@ -1,5 +1,6 @@ package org.fnives.test.showcase.ui +import androidx.compose.ui.test.junit4.StateRestorationTester import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import org.fnives.test.showcase.R @@ -22,6 +23,7 @@ class AuthComposeInstrumentedTest : KoinTest { @get:Rule val composeTestRule = createComposeRule() + private val stateRestorationTester = StateRestorationTester(composeTestRule) private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule() private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup @@ -37,7 +39,7 @@ class AuthComposeInstrumentedTest : KoinTest { @Before fun setup() { - composeTestRule.setContent { + stateRestorationTester.setContent { AppNavigation(isUserLogeInUseCase = IsUserLoggedInUseCase(FakeUserDataLocalStorage())) } robot = ComposeLoginRobot(composeTestRule) @@ -152,4 +154,21 @@ class AuthComposeInstrumentedTest : KoinTest { .assertNotLoading() navigationRobot.assertAuthScreen() } + + /** GIVEN username and password WHEN restoring THEN username and password fields contain the same text */ + @Test + fun restoringContentShowPreviousCredentials() { + composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() } + navigationRobot.assertAuthScreen() + robot.setUsername("alma") + .setPassword("banan") + .assertUsername("alma") + .assertPassword("banan") + + stateRestorationTester.emulateSavedInstanceStateRestore() + + navigationRobot.assertAuthScreen() + robot.assertUsername("alma") + .assertPassword("banan") + } } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt index 92ce536..c0e4840 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt @@ -1,7 +1,9 @@ package org.fnives.test.showcase.compose.screen.auth import androidx.compose.runtime.* -import androidx.compose.ui.platform.AndroidUiDispatcher +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.mapSaver +import androidx.compose.runtime.saveable.rememberSaveable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -17,7 +19,9 @@ fun rememberAuthScreenState( loginUseCase: LoginUseCase = get(), onLoginSuccess: () -> Unit = {}, ): AuthScreenState { - return remember { AuthScreenState(stateScope, loginUseCase, onLoginSuccess) } + return rememberSaveable(saver = AuthScreenState.getSaver(stateScope, loginUseCase, onLoginSuccess)) { + AuthScreenState(stateScope, loginUseCase, onLoginSuccess) + } } class AuthScreenState( @@ -80,4 +84,23 @@ class AuthScreenState( UNSUPPORTED_USERNAME, UNSUPPORTED_PASSWORD } + + companion object { + private const val USERNAME = "USERNAME" + private const val PASSWORD = "PASSWORD" + + fun getSaver( + stateScope: CoroutineScope, + loginUseCase: LoginUseCase, + onLoginSuccess: () -> Unit, + ): Saver = mapSaver( + save = { mapOf(USERNAME to it.username, PASSWORD to it.password) }, + restore = { + AuthScreenState(stateScope, loginUseCase, onLoginSuccess).apply { + onUsernameChanged(it.getOrElse(USERNAME) { "" } as String) + onPasswordChanged(it.getOrElse(PASSWORD) { "" } as String) + } + } + ) + } } \ No newline at end of file diff --git a/codekata/compose.instructionset.md b/codekata/compose.instructionset.md index e927e53..d2c5d8d 100644 --- a/codekata/compose.instructionset.md +++ b/codekata/compose.instructionset.md @@ -324,3 +324,31 @@ robot.assertErrorIsShown(R.string.something_went_wrong) .assertNotLoading() navigationRobot.assertAuthScreen() ``` + +### 6. `restoringContentShowPreviousCredentials` + +Since we're writing apps for Android, we must handle state restoration so let's write a test for it. + +For simulating the recreation of the UI, we first need a `StateRestorationTester`: +```kotlin + private val stateRestorationTester = StateRestorationTester(composeTestRule) +``` + +Then in `setup()`, we need to `setContent` on `stateRestorationTester` instead of on `composeTestRule`. + +Now for the actual test, we first setup the content then we trigger restoration by calling `stateRestorationTester.emulateSavedInstanceStateRestore()`, afterwards we can verify that the content is recreated in the correct way: + +```kotlin +composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() } +navigationRobot.assertAuthScreen() +robot.setUsername("alma") + .setPassword("banan") + .assertUsername("alma") + .assertPassword("banan") + +stateRestorationTester.emulateSavedInstanceStateRestore() + +navigationRobot.assertAuthScreen() +robot.assertUsername("alma") + .assertPassword("banan") +``` \ No newline at end of file