From 167c3f81c4697c2587daaec20910fa32618cf2a9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 30 Dec 2022 16:38:49 +0000 Subject: [PATCH] Add beta version usecase tests --- .../dapk/st/domain/ApplicationPreferences.kt | 3 +- .../dapk/st/home/BetaVersionUpgradeUseCase.kt | 3 +- .../st/home/BetaVersionUpgradeUseCaseTest.kt | 68 ++++++ .../app/dapk/st/home/state/HomeReducerTest.kt | 224 ++++++++++++++++++ 4 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 features/home/src/test/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCaseTest.kt create mode 100644 features/home/src/test/kotlin/app/dapk/st/home/state/HomeReducerTest.kt diff --git a/domains/store/src/main/kotlin/app/dapk/st/domain/ApplicationPreferences.kt b/domains/store/src/main/kotlin/app/dapk/st/domain/ApplicationPreferences.kt index a0a4d59..dfddea9 100644 --- a/domains/store/src/main/kotlin/app/dapk/st/domain/ApplicationPreferences.kt +++ b/domains/store/src/main/kotlin/app/dapk/st/domain/ApplicationPreferences.kt @@ -16,6 +16,5 @@ class ApplicationPreferences( } -@JvmInline -value class ApplicationVersion(val value: Int) +data class ApplicationVersion(val value: Int) diff --git a/features/home/src/main/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCase.kt b/features/home/src/main/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCase.kt index 565e810..2ba36a4 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCase.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCase.kt @@ -20,7 +20,8 @@ class BetaVersionUpgradeUseCase( } private suspend fun hasChangedVersion(): Boolean { - val previousVersion = applicationPreferences.readVersion()?.value + val readVersion = applicationPreferences.readVersion() + val previousVersion = readVersion?.value val currentVersion = buildMeta.versionCode return when (previousVersion) { null -> false diff --git a/features/home/src/test/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCaseTest.kt b/features/home/src/test/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCaseTest.kt new file mode 100644 index 0000000..5a7d592 --- /dev/null +++ b/features/home/src/test/kotlin/app/dapk/st/home/BetaVersionUpgradeUseCaseTest.kt @@ -0,0 +1,68 @@ +package app.dapk.st.home + +import app.dapk.st.core.BuildMeta +import app.dapk.st.domain.ApplicationPreferences +import app.dapk.st.domain.ApplicationVersion +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.async +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import test.delegateReturn +import test.expect + +class BetaVersionUpgradeUseCaseTest { + + private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100, isDebug = false) + private val fakeApplicationPreferences = FakeApplicationPreferences() + + private val useCase = BetaVersionUpgradeUseCase( + fakeApplicationPreferences.instance, + buildMeta + ) + + @Test + fun `given same stored version, when hasVersionChanged then is false`() = runTest { + fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode)) + + val result = useCase.hasVersionChanged() + + result shouldBeEqualTo false + } + + // Should be impossible + @Test + fun `given higher stored version, when hasVersionChanged then is false`() = runTest { + fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode + 1)) + + val result = useCase.hasVersionChanged() + + result shouldBeEqualTo false + } + + @Test + fun `given lower stored version, when hasVersionChanged then is true`() = runTest { + fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode - 1)) + + val result = useCase.hasVersionChanged() + + result shouldBeEqualTo true + } + + @Test + fun `given version has changed, when waiting, then blocks until notified of upgrade`() = runTest { + fakeApplicationPreferences.givenVersion().returns(ApplicationVersion(buildMeta.versionCode - 1)) + fakeApplicationPreferences.instance.expect { it.setVersion(ApplicationVersion(buildMeta.versionCode)) } + + val waitUntilReady = async { useCase.waitUnitReady() } + async { useCase.notifyUpgraded() } + waitUntilReady.await() + } +} + +private class FakeApplicationPreferences { + val instance = mockk() + + fun givenVersion() = coEvery { instance.readVersion() }.delegateReturn() +} \ No newline at end of file diff --git a/features/home/src/test/kotlin/app/dapk/st/home/state/HomeReducerTest.kt b/features/home/src/test/kotlin/app/dapk/st/home/state/HomeReducerTest.kt new file mode 100644 index 0000000..4c25f0c --- /dev/null +++ b/features/home/src/test/kotlin/app/dapk/st/home/state/HomeReducerTest.kt @@ -0,0 +1,224 @@ +package app.dapk.st.home.state + +import app.dapk.st.directory.state.ComponentLifecycle +import app.dapk.st.directory.state.DirectorySideEffect +import app.dapk.st.domain.StoreCleaner +import app.dapk.st.engine.Me +import app.dapk.st.home.BetaVersionUpgradeUseCase +import app.dapk.st.matrix.common.HomeServerUrl +import app.dapk.st.profile.state.ProfileAction +import fake.FakeChatEngine +import fake.FakeJobBag +import fixture.aRoomId +import fixture.aRoomInvite +import fixture.aUserId +import io.mockk.mockk +import org.junit.Test +import test.* + +private val A_ME = Me(aUserId(), displayName = null, avatarUrl = null, homeServerUrl = HomeServerUrl("ignored")) +private val A_SIGNED_IN_STATE = HomeScreenState.SignedIn( + HomeScreenState.Page.Directory, + me = A_ME, + invites = 0, +) + +class HomeReducerTest { + + private val fakeStoreCleaner = FakeStoreCleaner() + private val fakeChatEngine = FakeChatEngine() + private val fakeBetaVersionUpgradeUseCase = FakeBetaVersionUpgradeUseCase() + private val fakeJobBag = FakeJobBag() + + private val runReducerTest = testReducer { fakeEventSource -> + homeReducer( + fakeChatEngine, + fakeStoreCleaner, + fakeBetaVersionUpgradeUseCase.instance, + fakeJobBag.instance, + fakeEventSource, + ) + } + + @Test + fun `initial state is loading`() = runReducerTest { + assertInitialState(HomeScreenState.Loading) + } + + @Test + fun `when UpdateState, then replaces state`() = runReducerTest { + reduce(HomeAction.UpdateState(HomeScreenState.SignedOut)) + + assertOnlyStateChange(HomeScreenState.SignedOut) + } + + @Test + fun `given SignedIn, when UpdateInviteCount, then updates invite count`() = runReducerTest { + setState(A_SIGNED_IN_STATE) + + reduce(HomeAction.UpdateInvitesCount(invitesCount = 90)) + + assertOnlyStateChange(A_SIGNED_IN_STATE.copy(invites = 90)) + } + + @Test + fun `when ScrollToTop, then forwards to directory scroll event`() = runReducerTest { + reduce(HomeAction.ScrollToTop) + + assertOnlyDispatches(DirectorySideEffect.ScrollToTop) + } + + @Test + fun `when ClearCache, then clears store cache, upgrades and relaunches`() = runReducerTest { + fakeStoreCleaner.expect { it.cleanCache(removeCredentials = false) } + fakeBetaVersionUpgradeUseCase.instance.expect { it.notifyUpgraded() } + + reduce(HomeAction.ClearCache) + + assertOnlyEvents(HomeEvent.Relaunch) + } + + @Test + fun `given SignedIn and invites update, when Visible, then show content and update on invite changes`() = runReducerTest { + fakeChatEngine.givenIsSignedIn().returns(true) + + reduce(HomeAction.LifecycleVisible) + + assertEvents(HomeEvent.OnShowContent) + assertDispatches(HomeAction.InitialHome) + assertNoStateChange() + } + + @Test + fun `given SignedOut and invites update, when Visible, then show content and update on invite changes`() = runReducerTest { + fakeChatEngine.givenIsSignedIn().returns(false) + + reduce(HomeAction.LifecycleVisible) + + assertOnlyDispatches(HomeAction.UpdateState(HomeScreenState.SignedOut)) + } + + @Test + fun `given SignedIn, when InitialHome, then updates me state and listens to invite changes`() = runReducerTest { + setState(A_SIGNED_IN_STATE) + fakeChatEngine.givenMe(forceRefresh = false).returns(A_ME) + givenInvites(count = 5) + + reduce(HomeAction.InitialHome) + + assertOnlyDispatches( + HomeAction.UpdateToSignedIn(A_ME), + HomeAction.UpdateInvitesCount(5) + ) + } + + @Test + fun `given SignedIn, when UpdateToSignedIn, then updates me state`() = runReducerTest { + setState(A_SIGNED_IN_STATE) + val expectedMe = A_ME.copy(aUserId("another-user")) + + reduce(HomeAction.UpdateToSignedIn(expectedMe)) + + assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe)) + } + + @Test + fun `given Loading, when UpdateToSignedIn, then set SignedIn and updates me state`() = runReducerTest { + setState(HomeScreenState.Loading) + val expectedMe = A_ME.copy(aUserId("another-user")) + + reduce(HomeAction.UpdateToSignedIn(expectedMe)) + + assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe)) + } + + @Test + fun `given SignedOut, when UpdateToSignedIn, then set SignedIn and updates me state`() = runReducerTest { + setState(HomeScreenState.SignedOut) + val expectedMe = A_ME.copy(aUserId("another-user")) + + reduce(HomeAction.UpdateToSignedIn(expectedMe)) + + assertOnlyStateChange(A_SIGNED_IN_STATE.copy(me = expectedMe)) + } + + @Test + fun `when LoggedIn, then emit show content and fetch initial home`() = runReducerTest { + setState(HomeScreenState.SignedOut) + givenInvites(count = 0) + + reduce(HomeAction.LoggedIn) + + assertDispatches(HomeAction.InitialHome) + assertEvents(HomeEvent.OnShowContent) + assertNoStateChange() + } + + @Test + fun `given SignedOut, when ChangePage, then does nothing`() = runReducerTest { + setState(HomeScreenState.SignedOut) + + reduce(HomeAction.ChangePage(HomeScreenState.Page.Directory)) + + assertNoChanges() + } + + @Test + fun `given Loading, when ChangePage, then does nothing`() = runReducerTest { + setState(HomeScreenState.Loading) + + reduce(HomeAction.ChangePage(HomeScreenState.Page.Directory)) + + assertNoChanges() + } + + @Test + fun `given SignedIn, when ChangePage to same page, then does nothing`() = runReducerTest { + val page = HomeScreenState.Page.Directory + setState(A_SIGNED_IN_STATE.copy(page = page)) + + reduce(HomeAction.ChangePage(page)) + + assertNoChanges() + } + + @Test + fun `given SignedIn, when ChangePage to different page, then updates page and emits side effect`() = runReducerTest { + val expectedPage = HomeScreenState.Page.Profile + setState(A_SIGNED_IN_STATE.copy(page = HomeScreenState.Page.Directory)) + + reduce(HomeAction.ChangePage(expectedPage)) + + assertStateChange(A_SIGNED_IN_STATE.copy(page = expectedPage)) + assertDispatches(HomeAction.ChangePageSideEffect(expectedPage)) + } + + @Test + fun `when ChangePageSide is Directory, then does nothing`() = runReducerTest { + reduce(HomeAction.ChangePageSideEffect(HomeScreenState.Page.Directory)) + + assertNoChanges() + } + + @Test + fun `when ChangePageSide is Profile, then mark directory gone and resets profile`() = runReducerTest { + reduce(HomeAction.ChangePageSideEffect(HomeScreenState.Page.Profile)) + + assertOnlyDispatches( + ComponentLifecycle.OnGone, + ProfileAction.Reset + ) + } + + private fun givenInvites(count: Int) { + fakeJobBag.instance.expect { it.replace("invites-count", any()) } + val invites = List(count) { aRoomInvite(roomId = aRoomId(it.toString())) } + fakeChatEngine.givenInvites().emits(invites) + } +} + +class FakeStoreCleaner : StoreCleaner by mockk() + +class FakeBetaVersionUpgradeUseCase { + val instance = mockk() +} \ No newline at end of file