porting viewmodel tests to reducer
This commit is contained in:
parent
678897e8de
commit
533122f1ab
|
@ -45,7 +45,7 @@ class ReducerTestScope<S, E>(
|
||||||
private val actionCaptures = mutableListOf<Action>()
|
private val actionCaptures = mutableListOf<Action>()
|
||||||
private val reducerScope = object : ReducerScope<S> {
|
private val reducerScope = object : ReducerScope<S> {
|
||||||
override val coroutineScope = CoroutineScope(UnconfinedTestDispatcher())
|
override val coroutineScope = CoroutineScope(UnconfinedTestDispatcher())
|
||||||
override suspend fun dispatch(action: Action) {
|
override fun dispatch(action: Action) {
|
||||||
actionCaptures.add(action)
|
actionCaptures.add(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,3 +124,19 @@ class ReducerTestScope<S, E>(
|
||||||
assertNoDispatches()
|
assertNoDispatches()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <S, E> ReducerTestScope<S, E>.assertOnlyDispatches(vararg action: Action) {
|
||||||
|
this.assertOnlyDispatches(action.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <S, E> ReducerTestScope<S, E>.assertDispatches(vararg action: Action) {
|
||||||
|
this.assertDispatches(action.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <S, E> ReducerTestScope<S, E>.assertEvents(vararg event: E) {
|
||||||
|
this.assertEvents(event.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <S, E> ReducerTestScope<S, E>.assertOnlyEvents(vararg event: E) {
|
||||||
|
this.assertOnlyEvents(event.toList())
|
||||||
|
}
|
|
@ -272,7 +272,6 @@ class MessengerReducerTest {
|
||||||
assertNoStateChange()
|
assertNoStateChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given text composer with reply, when SendMessage, then clear composer and sends text message`() = runReducerTest {
|
fun `given text composer with reply, when SendMessage, then clear composer and sends text message`() = runReducerTest {
|
||||||
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = A_REPLY.message), roomState = Lce.Content(A_MESSENGER_PAGE_STATE)) }
|
setState { it.copy(composerState = ComposerState.Text(A_MESSAGE_CONTENT, reply = A_REPLY.message), roomState = Lce.Content(A_MESSENGER_PAGE_STATE)) }
|
||||||
|
|
|
@ -15,6 +15,7 @@ dependencies {
|
||||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||||
androidImportFixturesWorkaround(project, project(":core"))
|
androidImportFixturesWorkaround(project, project(":core"))
|
||||||
androidImportFixturesWorkaround(project, project(":domains:store"))
|
androidImportFixturesWorkaround(project, project(":domains:store"))
|
||||||
|
androidImportFixturesWorkaround(project, project(":domains:state"))
|
||||||
androidImportFixturesWorkaround(project, project(":domains:android:viewmodel"))
|
androidImportFixturesWorkaround(project, project(":domains:android:viewmodel"))
|
||||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||||
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||||
|
|
|
@ -76,7 +76,6 @@ internal fun settingsReducer(
|
||||||
dispatch(RootActions.FetchProviders)
|
dispatch(RootActions.FetchProviders)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async(RootActions.ImportKeysFromFile::class) { action ->
|
async(RootActions.ImportKeysFromFile::class) { action ->
|
||||||
withPageContext<Page.ImportRoomKey> {
|
withPageContext<Page.ImportRoomKey> {
|
||||||
pageDispatch(PageStateChange.UpdatePage(it.copy(importProgress = ImportResult.Update(0))))
|
pageDispatch(PageStateChange.UpdatePage(it.copy(importProgress = ImportResult.Update(0))))
|
||||||
|
@ -95,7 +94,6 @@ internal fun settingsReducer(
|
||||||
.launchIn(coroutineScope)
|
.launchIn(coroutineScope)
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
|
|
||||||
withPageContext<Page.ImportRoomKey> {
|
withPageContext<Page.ImportRoomKey> {
|
||||||
pageDispatch(PageStateChange.UpdatePage(it.copy(importProgress = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile))))
|
pageDispatch(PageStateChange.UpdatePage(it.copy(importProgress = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile))))
|
||||||
}
|
}
|
||||||
|
@ -115,6 +113,10 @@ internal fun settingsReducer(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async(ScreenAction.OpenImportRoom::class) {
|
||||||
|
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.importRoomKeys, "Import room keys", Page.Routes.encryption, Page.ImportRoomKey())))
|
||||||
|
},
|
||||||
|
|
||||||
multi(ScreenAction.OnClick::class) { action ->
|
multi(ScreenAction.OnClick::class) { action ->
|
||||||
val item = action.item
|
val item = action.item
|
||||||
when (item.id) {
|
when (item.id) {
|
||||||
|
@ -133,10 +135,6 @@ internal fun settingsReducer(
|
||||||
eventEmitter.invoke(Toast(message = "Cache deleted"))
|
eventEmitter.invoke(Toast(message = "Cache deleted"))
|
||||||
}
|
}
|
||||||
|
|
||||||
EventLog -> sideEffect {
|
|
||||||
eventEmitter.invoke(OpenEventLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
Encryption -> async {
|
Encryption -> async {
|
||||||
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.encryption, "Encryption", Page.Routes.root, Page.Security)))
|
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.encryption, "Encryption", Page.Routes.root, Page.Security)))
|
||||||
}
|
}
|
||||||
|
@ -164,16 +162,16 @@ internal fun settingsReducer(
|
||||||
dispatch(ComponentLifecycle.Visible)
|
dispatch(ComponentLifecycle.Visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventLog -> sideEffect {
|
||||||
|
eventEmitter.invoke(OpenEventLog)
|
||||||
|
}
|
||||||
|
|
||||||
ToggleSendReadReceipts -> async {
|
ToggleSendReadReceipts -> async {
|
||||||
messageOptionsStore.setReadReceiptsDisabled(!messageOptionsStore.isReadReceiptsDisabled())
|
messageOptionsStore.setReadReceiptsDisabled(!messageOptionsStore.isReadReceiptsDisabled())
|
||||||
dispatch(ComponentLifecycle.Visible)
|
dispatch(ComponentLifecycle.Visible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async(ScreenAction.OpenImportRoom::class) {
|
|
||||||
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.importRoomKeys, "Import room keys", Page.Routes.encryption, Page.ImportRoomKey())))
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -78,5 +78,6 @@ class FakePushRegistrars {
|
||||||
val instance = mockk<PushTokenRegistrars>()
|
val instance = mockk<PushTokenRegistrars>()
|
||||||
|
|
||||||
fun givenCurrentSelection() = coEvery { instance.currentSelection() }.delegateReturn()
|
fun givenCurrentSelection() = coEvery { instance.currentSelection() }.delegateReturn()
|
||||||
|
fun givenOptions() = coEvery { instance.options() }.delegateReturn()
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
package app.dapk.st.settings
|
||||||
|
|
||||||
|
import app.dapk.st.core.Lce
|
||||||
|
import app.dapk.st.core.page.PageAction
|
||||||
|
import app.dapk.st.core.page.PageContainer
|
||||||
|
import app.dapk.st.core.page.PageStateChange
|
||||||
|
import app.dapk.st.design.components.SpiderPage
|
||||||
|
import app.dapk.st.engine.ImportResult
|
||||||
|
import app.dapk.st.push.Registrar
|
||||||
|
import app.dapk.st.settings.state.ComponentLifecycle
|
||||||
|
import app.dapk.st.settings.state.RootActions
|
||||||
|
import app.dapk.st.settings.state.ScreenAction
|
||||||
|
import app.dapk.st.settings.state.settingsReducer
|
||||||
|
import app.dapk.state.Combined2
|
||||||
|
import fake.*
|
||||||
|
import fixture.aRoomId
|
||||||
|
import internalfake.FakeSettingsItemFactory
|
||||||
|
import internalfake.FakeUriFilenameResolver
|
||||||
|
import internalfixture.aImportRoomKeysPage
|
||||||
|
import internalfixture.aPushProvidersPage
|
||||||
|
import internalfixture.aSettingTextItem
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import org.junit.Test
|
||||||
|
import test.*
|
||||||
|
|
||||||
|
private const val APP_PRIVACY_POLICY_URL = "https://ouchadam.github.io/small-talk/privacy/"
|
||||||
|
private val A_LIST_OF_ROOT_ITEMS = listOf(aSettingTextItem())
|
||||||
|
private val A_URI = FakeUri()
|
||||||
|
private const val A_FILENAME = "a-filename.jpg"
|
||||||
|
private val AN_INITIAL_IMPORT_ROOM_KEYS_PAGE = aImportRoomKeysPage()
|
||||||
|
private val AN_INITIAL_PUSH_PROVIDERS_PAGE = aPushProvidersPage()
|
||||||
|
private val A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION = aImportRoomKeysPage(
|
||||||
|
state = Page.ImportRoomKey(selectedFile = NamedUri(A_FILENAME, A_URI.instance))
|
||||||
|
)
|
||||||
|
private val A_LIST_OF_ROOM_IDS = listOf(aRoomId())
|
||||||
|
private val AN_IMPORT_SUCCESS = ImportResult.Success(A_LIST_OF_ROOM_IDS.toSet(), totalImportedKeysCount = 5)
|
||||||
|
private val AN_IMPORT_FILE_ERROR = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile)
|
||||||
|
private val AN_INPUT_STREAM = FakeInputStream()
|
||||||
|
private const val A_PASSPHRASE = "passphrase"
|
||||||
|
private val AN_ERROR = RuntimeException()
|
||||||
|
private val A_REGISTRAR = Registrar("a-registrar-id")
|
||||||
|
private val A_PUSH_OPTIONS = listOf(Registrar("a-registrar-id"))
|
||||||
|
|
||||||
|
internal class SettingsReducerTest {
|
||||||
|
|
||||||
|
private val fakeStoreCleaner = FakeStoreCleaner()
|
||||||
|
private val fakeContentResolver = FakeContentResolver()
|
||||||
|
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
|
||||||
|
private val fakePushTokenRegistrars = FakePushRegistrars()
|
||||||
|
private val fakeSettingsItemFactory = FakeSettingsItemFactory()
|
||||||
|
private val fakeThemeStore = FakeThemeStore()
|
||||||
|
private val fakeLoggingStore = FakeLoggingStore()
|
||||||
|
private val fakeMessageOptionsStore = FakeMessageOptionsStore()
|
||||||
|
private val fakeChatEngine = FakeChatEngine()
|
||||||
|
private val fakeJobBag = FakeJobBag()
|
||||||
|
|
||||||
|
private val runReducerTest = testReducer { fakeEventSource ->
|
||||||
|
settingsReducer(
|
||||||
|
fakeChatEngine,
|
||||||
|
fakeStoreCleaner,
|
||||||
|
fakeContentResolver.instance,
|
||||||
|
fakeUriFilenameResolver.instance,
|
||||||
|
fakeSettingsItemFactory.instance,
|
||||||
|
fakePushTokenRegistrars.instance,
|
||||||
|
fakeThemeStore.instance,
|
||||||
|
fakeLoggingStore.instance,
|
||||||
|
fakeMessageOptionsStore.instance,
|
||||||
|
fakeEventSource,
|
||||||
|
fakeJobBag.instance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state is root with loading`() = runReducerTest {
|
||||||
|
assertInitialState(
|
||||||
|
pageState(SpiderPage(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading())))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given root content, when Visible, then goes to root page with content`() = runReducerTest {
|
||||||
|
fakeSettingsItemFactory.givenRoot().returns(A_LIST_OF_ROOT_ITEMS)
|
||||||
|
fakeJobBag.instance.expect { it.replace("page", any()) }
|
||||||
|
|
||||||
|
reduce(ComponentLifecycle.Visible)
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageAction.GoTo(
|
||||||
|
SpiderPage(
|
||||||
|
Page.Routes.root,
|
||||||
|
"Settings",
|
||||||
|
null,
|
||||||
|
Page.Root(Lce.Content(A_LIST_OF_ROOT_ITEMS))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when SelectPushProvider, then selects provider and refreshes`() = runReducerTest {
|
||||||
|
fakePushTokenRegistrars.instance.expect { it.makeSelection(A_REGISTRAR) }
|
||||||
|
|
||||||
|
reduce(RootActions.SelectPushProvider(A_REGISTRAR))
|
||||||
|
|
||||||
|
assertOnlyDispatches(RootActions.FetchProviders)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when FetchProviders, then selects provider and refreshes`() = runReducerTest {
|
||||||
|
setState(pageState(aPushProvidersPage()))
|
||||||
|
fakePushTokenRegistrars.givenOptions().returns(A_PUSH_OPTIONS)
|
||||||
|
fakePushTokenRegistrars.givenCurrentSelection().returns(A_REGISTRAR)
|
||||||
|
|
||||||
|
reduce(RootActions.FetchProviders)
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
aPushProvidersPage().state.copy(options = Lce.Loading())
|
||||||
|
),
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
aPushProvidersPage().state.copy(
|
||||||
|
selection = A_REGISTRAR,
|
||||||
|
options = Lce.Content(A_PUSH_OPTIONS)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when SelectKeysFile, then updates ImportRoomKey page with file`() = runReducerTest {
|
||||||
|
setState(pageState(AN_INITIAL_IMPORT_ROOM_KEYS_PAGE))
|
||||||
|
fakeUriFilenameResolver.givenFilename(A_URI.instance).returns(A_FILENAME)
|
||||||
|
|
||||||
|
reduce(RootActions.SelectKeysFile(A_URI.instance))
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
AN_INITIAL_IMPORT_ROOM_KEYS_PAGE.state.copy(
|
||||||
|
selectedFile = NamedUri(A_FILENAME, A_URI.instance)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click SignOut, then clears store and signs out`() = runReducerTest {
|
||||||
|
fakeStoreCleaner.expectUnit { it.cleanCache(removeCredentials = true) }
|
||||||
|
val aSignOutItem = aSettingTextItem(id = SettingItem.Id.SignOut)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aSignOutItem))
|
||||||
|
|
||||||
|
assertEvents(SettingsEvent.SignedOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click Encryption, then goes to Encryption page`() = runReducerTest {
|
||||||
|
val anEncryptionItem = aSettingTextItem(id = SettingItem.Id.Encryption)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(anEncryptionItem))
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageAction.GoTo(
|
||||||
|
SpiderPage(
|
||||||
|
Page.Routes.encryption,
|
||||||
|
"Encryption",
|
||||||
|
Page.Routes.root,
|
||||||
|
Page.Security
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click PrivacyPolicy, then opens privacy policy url`() = runReducerTest {
|
||||||
|
val aPrivacyPolicyItem = aSettingTextItem(id = SettingItem.Id.PrivacyPolicy)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aPrivacyPolicyItem))
|
||||||
|
|
||||||
|
assertOnlyEvents(SettingsEvent.OpenUrl(APP_PRIVACY_POLICY_URL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click PushProvider, then goes to PushProvider page`() = runReducerTest {
|
||||||
|
val aPushProviderItem = aSettingTextItem(id = SettingItem.Id.PushProvider)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aPushProviderItem))
|
||||||
|
|
||||||
|
assertOnlyDispatches(PageAction.GoTo(aPushProvidersPage()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click Ignored, then does nothing`() = runReducerTest {
|
||||||
|
val anIgnoredItem = aSettingTextItem(id = SettingItem.Id.Ignored)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(anIgnoredItem))
|
||||||
|
|
||||||
|
assertNoChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click ToggleDynamicTheme, then toggles flag, recreates activity and reloads`() = runReducerTest {
|
||||||
|
val aToggleThemeItem = aSettingTextItem(id = SettingItem.Id.ToggleDynamicTheme)
|
||||||
|
fakeThemeStore.givenMaterialYouIsEnabled().returns(true)
|
||||||
|
fakeThemeStore.instance.expect { it.storeMaterialYouEnabled(false) }
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aToggleThemeItem))
|
||||||
|
|
||||||
|
assertEvents(SettingsEvent.RecreateActivity)
|
||||||
|
assertDispatches(ComponentLifecycle.Visible)
|
||||||
|
assertNoStateChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click ToggleEnableLogs, then toggles flag and reloads`() = runReducerTest {
|
||||||
|
val aToggleEnableLogsItem = aSettingTextItem(id = SettingItem.Id.ToggleEnableLogs)
|
||||||
|
fakeLoggingStore.givenLoggingIsEnabled().returns(true)
|
||||||
|
fakeLoggingStore.instance.expect { it.setEnabled(false) }
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aToggleEnableLogsItem))
|
||||||
|
|
||||||
|
assertOnlyDispatches(ComponentLifecycle.Visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click EventLog, then opens event log`() = runReducerTest {
|
||||||
|
val anEventLogItem = aSettingTextItem(id = SettingItem.Id.EventLog)
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(anEventLogItem))
|
||||||
|
|
||||||
|
assertOnlyEvents(SettingsEvent.OpenEventLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when Click ToggleSendReadReceipts, then toggles flag and reloads`() = runReducerTest {
|
||||||
|
val aToggleReadReceiptsItem = aSettingTextItem(id = SettingItem.Id.ToggleSendReadReceipts)
|
||||||
|
fakeMessageOptionsStore.givenReadReceiptsDisabled().returns(true)
|
||||||
|
fakeMessageOptionsStore.instance.expect { it.setReadReceiptsDisabled(false) }
|
||||||
|
|
||||||
|
reduce(ScreenAction.OnClick(aToggleReadReceiptsItem))
|
||||||
|
|
||||||
|
assertOnlyDispatches(ComponentLifecycle.Visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given success, when ImportKeysFromFile, then dispatches progress`() = runReducerTest {
|
||||||
|
setState(pageState(A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION))
|
||||||
|
fakeContentResolver.givenFile(A_URI.instance).returns(AN_INPUT_STREAM.instance)
|
||||||
|
fakeChatEngine.givenImportKeys(AN_INPUT_STREAM.instance, A_PASSPHRASE).returns(flowOf(AN_IMPORT_SUCCESS))
|
||||||
|
|
||||||
|
reduce(RootActions.ImportKeysFromFile(A_URI.instance, A_PASSPHRASE))
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION.state.copy(importProgress = ImportResult.Update(0L))
|
||||||
|
),
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION.state.copy(importProgress = AN_IMPORT_SUCCESS)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given error, when ImportKeysFromFile, then dispatches error`() = runReducerTest {
|
||||||
|
setState(pageState(A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION))
|
||||||
|
fakeContentResolver.givenFile(A_URI.instance).throws(AN_ERROR)
|
||||||
|
|
||||||
|
reduce(RootActions.ImportKeysFromFile(A_URI.instance, A_PASSPHRASE))
|
||||||
|
|
||||||
|
assertOnlyDispatches(
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION.state.copy(importProgress = ImportResult.Update(0L))
|
||||||
|
),
|
||||||
|
PageStateChange.UpdatePage(
|
||||||
|
A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION.state.copy(importProgress = AN_IMPORT_FILE_ERROR)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <P> pageState(page: SpiderPage<out P>) = Combined2(PageContainer(page), Unit)
|
|
@ -1,210 +0,0 @@
|
||||||
package app.dapk.st.settings
|
|
||||||
|
|
||||||
import ViewModelTest
|
|
||||||
import app.dapk.st.core.Lce
|
|
||||||
import app.dapk.st.design.components.SpiderPage
|
|
||||||
import app.dapk.st.engine.ImportResult
|
|
||||||
import fake.*
|
|
||||||
import fixture.aRoomId
|
|
||||||
import internalfake.FakeSettingsItemFactory
|
|
||||||
import internalfake.FakeUriFilenameResolver
|
|
||||||
import internalfixture.aImportRoomKeysPage
|
|
||||||
import internalfixture.aSettingTextItem
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
private const val APP_PRIVACY_POLICY_URL = "https://ouchadam.github.io/small-talk/privacy/"
|
|
||||||
private val A_LIST_OF_ROOT_ITEMS = listOf(aSettingTextItem())
|
|
||||||
private val A_URI = FakeUri()
|
|
||||||
private const val A_FILENAME = "a-filename.jpg"
|
|
||||||
private val AN_INITIAL_IMPORT_ROOM_KEYS_PAGE = aImportRoomKeysPage()
|
|
||||||
private val A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION = aImportRoomKeysPage(
|
|
||||||
state = Page.ImportRoomKey(selectedFile = NamedUri(A_FILENAME, A_URI.instance))
|
|
||||||
)
|
|
||||||
private val A_LIST_OF_ROOM_IDS = listOf(aRoomId())
|
|
||||||
private val AN_IMPORT_SUCCESS = ImportResult.Success(A_LIST_OF_ROOM_IDS.toSet(), totalImportedKeysCount = 5)
|
|
||||||
private val AN_IMPORT_FILE_ERROR = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile)
|
|
||||||
private val AN_INPUT_STREAM = FakeInputStream()
|
|
||||||
private const val A_PASSPHRASE = "passphrase"
|
|
||||||
private val AN_ERROR = RuntimeException()
|
|
||||||
|
|
||||||
internal class SettingsViewModelTest {
|
|
||||||
|
|
||||||
private val runViewModelTest = ViewModelTest()
|
|
||||||
|
|
||||||
private val fakeStoreCleaner = FakeStoreCleaner()
|
|
||||||
private val fakeContentResolver = FakeContentResolver()
|
|
||||||
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
|
|
||||||
private val fakePushTokenRegistrars = FakePushRegistrars()
|
|
||||||
private val fakeSettingsItemFactory = FakeSettingsItemFactory()
|
|
||||||
private val fakeThemeStore = FakeThemeStore()
|
|
||||||
private val fakeLoggingStore = FakeLoggingStore()
|
|
||||||
private val fakeMessageOptionsStore = FakeMessageOptionsStore()
|
|
||||||
private val fakeChatEngine = FakeChatEngine()
|
|
||||||
|
|
||||||
private val viewModel = SettingsViewModel(
|
|
||||||
fakeChatEngine,
|
|
||||||
fakeStoreCleaner,
|
|
||||||
fakeContentResolver.instance,
|
|
||||||
fakeUriFilenameResolver.instance,
|
|
||||||
fakeSettingsItemFactory.instance,
|
|
||||||
fakePushTokenRegistrars.instance,
|
|
||||||
fakeThemeStore.instance,
|
|
||||||
fakeLoggingStore.instance,
|
|
||||||
fakeMessageOptionsStore.instance,
|
|
||||||
runViewModelTest.testMutableStateFactory(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when creating view model then initial state is loading Root`() = runViewModelTest {
|
|
||||||
viewModel.test()
|
|
||||||
|
|
||||||
assertInitialState(
|
|
||||||
SettingsScreenState(SpiderPage(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading())))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when starting, then emits root page with content`() = runViewModelTest {
|
|
||||||
fakeSettingsItemFactory.givenRoot().returns(A_LIST_OF_ROOT_ITEMS)
|
|
||||||
|
|
||||||
viewModel.test().start()
|
|
||||||
|
|
||||||
assertStates(
|
|
||||||
SettingsScreenState(
|
|
||||||
SpiderPage(
|
|
||||||
Page.Routes.root,
|
|
||||||
"Settings",
|
|
||||||
null,
|
|
||||||
Page.Root(Lce.Content(A_LIST_OF_ROOT_ITEMS))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when sign out clicked, then clears store`() = runViewModelTest {
|
|
||||||
fakeStoreCleaner.expectUnit { it.cleanCache(removeCredentials = true) }
|
|
||||||
val aSignOutItem = aSettingTextItem(id = SettingItem.Id.SignOut)
|
|
||||||
|
|
||||||
viewModel.test().onClick(aSignOutItem)
|
|
||||||
|
|
||||||
assertNoStates<SettingsScreenState>()
|
|
||||||
assertEvents(SettingsEvent.SignedOut)
|
|
||||||
verifyExpects()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when event log clicked, then opens event log`() = runViewModelTest {
|
|
||||||
val anEventLogItem = aSettingTextItem(id = SettingItem.Id.EventLog)
|
|
||||||
|
|
||||||
viewModel.test().onClick(anEventLogItem)
|
|
||||||
|
|
||||||
assertNoStates<SettingsScreenState>()
|
|
||||||
assertEvents(SettingsEvent.OpenEventLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when encryption clicked, then emits encryption page`() = runViewModelTest {
|
|
||||||
val anEncryptionItem = aSettingTextItem(id = SettingItem.Id.Encryption)
|
|
||||||
|
|
||||||
viewModel.test().onClick(anEncryptionItem)
|
|
||||||
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
assertStates(
|
|
||||||
SettingsScreenState(
|
|
||||||
SpiderPage(
|
|
||||||
route = Page.Routes.encryption,
|
|
||||||
label = "Encryption",
|
|
||||||
parent = Page.Routes.root,
|
|
||||||
state = Page.Security
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when privacy policy clicked, then opens privacy policy url`() = runViewModelTest {
|
|
||||||
val aPrivacyPolicyItem = aSettingTextItem(id = SettingItem.Id.PrivacyPolicy)
|
|
||||||
|
|
||||||
viewModel.test().onClick(aPrivacyPolicyItem)
|
|
||||||
|
|
||||||
assertNoStates<SettingsScreenState>()
|
|
||||||
assertEvents(SettingsEvent.OpenUrl(APP_PRIVACY_POLICY_URL))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when going to import room, then emits import room keys page`() = runViewModelTest {
|
|
||||||
viewModel.test().goToImportRoom()
|
|
||||||
|
|
||||||
assertStates(
|
|
||||||
SettingsScreenState(
|
|
||||||
SpiderPage(
|
|
||||||
route = Page.Routes.importRoomKeys,
|
|
||||||
label = "Import room keys",
|
|
||||||
parent = Page.Routes.encryption,
|
|
||||||
state = Page.ImportRoomKey()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given on import room keys page, when selecting file, then emits selection`() = runViewModelTest {
|
|
||||||
fakeUriFilenameResolver.givenFilename(A_URI.instance).returns(A_FILENAME)
|
|
||||||
|
|
||||||
viewModel.test(initialState = SettingsScreenState(AN_INITIAL_IMPORT_ROOM_KEYS_PAGE)).fileSelected(A_URI.instance)
|
|
||||||
|
|
||||||
assertStates(
|
|
||||||
SettingsScreenState(
|
|
||||||
AN_INITIAL_IMPORT_ROOM_KEYS_PAGE.copy(
|
|
||||||
state = Page.ImportRoomKey(
|
|
||||||
selectedFile = NamedUri(A_FILENAME, A_URI.instance)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given success when importing room keys, then emits progress`() = runViewModelTest {
|
|
||||||
fakeContentResolver.givenFile(A_URI.instance).returns(AN_INPUT_STREAM.instance)
|
|
||||||
fakeChatEngine.givenImportKeys(AN_INPUT_STREAM.instance, A_PASSPHRASE).returns(flowOf(AN_IMPORT_SUCCESS))
|
|
||||||
|
|
||||||
viewModel
|
|
||||||
.test(initialState = SettingsScreenState(A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION))
|
|
||||||
.importFromFileKeys(A_URI.instance, A_PASSPHRASE)
|
|
||||||
|
|
||||||
assertStates<SettingsScreenState>(
|
|
||||||
{ copy(page = page.updateState<Page.ImportRoomKey> { copy(importProgress = ImportResult.Update(0L)) }) },
|
|
||||||
{ copy(page = page.updateState<Page.ImportRoomKey> { copy(importProgress = AN_IMPORT_SUCCESS) }) },
|
|
||||||
)
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
verifyExpects()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given error when importing room keys, then emits error`() = runViewModelTest {
|
|
||||||
fakeContentResolver.givenFile(A_URI.instance).throws(AN_ERROR)
|
|
||||||
|
|
||||||
viewModel
|
|
||||||
.test(initialState = SettingsScreenState(A_IMPORT_ROOM_KEYS_PAGE_WITH_SELECTION))
|
|
||||||
.importFromFileKeys(A_URI.instance, A_PASSPHRASE)
|
|
||||||
|
|
||||||
assertStates<SettingsScreenState>(
|
|
||||||
{ copy(page = page.updateState<Page.ImportRoomKey> { copy(importProgress = ImportResult.Update(0L)) }) },
|
|
||||||
{ copy(page = page.updateState<Page.ImportRoomKey> { copy(importProgress = AN_IMPORT_FILE_ERROR) }) },
|
|
||||||
)
|
|
||||||
assertNoEvents<SettingsEvent>()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private inline fun <reified S : Page> SpiderPage<out Page>.updateState(crossinline block: S.() -> S): SpiderPage<Page> {
|
|
||||||
require(this.state is S)
|
|
||||||
return (this as SpiderPage<S>).copy(state = block(this.state)) as SpiderPage<Page>
|
|
||||||
}
|
|
|
@ -11,3 +11,12 @@ internal fun aImportRoomKeysPage(
|
||||||
parent = Page.Routes.encryption,
|
parent = Page.Routes.encryption,
|
||||||
state = state
|
state = state
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal fun aPushProvidersPage(
|
||||||
|
state: Page.PushProviders = Page.PushProviders()
|
||||||
|
) = SpiderPage(
|
||||||
|
route = Page.Routes.pushProviders,
|
||||||
|
label = "Push providers",
|
||||||
|
parent = Page.Routes.root,
|
||||||
|
state = state
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue