Remove Wait list screen #3293
This commit is contained in:
@@ -19,7 +19,6 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
||||
import dagger.assisted.Assisted
|
||||
@@ -31,10 +30,8 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
|
||||
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
||||
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
||||
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
||||
import io.element.android.features.login.impl.screens.loginpassword.LoginFormState
|
||||
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
|
||||
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
|
||||
import io.element.android.features.login.impl.screens.waitlistscreen.WaitListNode
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
@@ -112,9 +109,6 @@ class LoginFlowNode @AssistedInject constructor(
|
||||
@Parcelize
|
||||
data object LoginPassword : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class WaitList(val loginFormState: LoginFormState) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class OidcView(val oidcDetails: OidcDetails) : NavTarget
|
||||
}
|
||||
@@ -181,27 +175,11 @@ class LoginFlowNode @AssistedInject constructor(
|
||||
createNode<SearchAccountProviderNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
NavTarget.LoginPassword -> {
|
||||
val callback = object : LoginPasswordNode.Callback {
|
||||
override fun onWaitListError(loginFormState: LoginFormState) {
|
||||
backstack.newRoot(NavTarget.WaitList(loginFormState))
|
||||
}
|
||||
}
|
||||
createNode<LoginPasswordNode>(buildContext, plugins = listOf(callback))
|
||||
createNode<LoginPasswordNode>(buildContext)
|
||||
}
|
||||
is NavTarget.OidcView -> {
|
||||
oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.oidcDetails.url)
|
||||
}
|
||||
is NavTarget.WaitList -> {
|
||||
val inputs = WaitListNode.Inputs(
|
||||
loginFormState = navTarget.loginFormState,
|
||||
)
|
||||
val callback = object : WaitListNode.Callback {
|
||||
override fun onCancelClick() {
|
||||
navigateUp()
|
||||
}
|
||||
}
|
||||
createNode<WaitListNode>(buildContext, plugins = listOf(callback, inputs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.error
|
||||
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
|
||||
fun Throwable.isWaitListError(): Boolean {
|
||||
return message?.contains("IO_ELEMENT_X_WAIT_LIST").orFalse()
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
@@ -24,14 +23,6 @@ class LoginPasswordNode @AssistedInject constructor(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: LoginPasswordPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onWaitListError(loginFormState: LoginFormState)
|
||||
}
|
||||
|
||||
private fun onWaitListError(loginFormState: LoginFormState) {
|
||||
plugins<Callback>().forEach { it.onWaitListError(loginFormState) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
@@ -39,7 +30,6 @@ class LoginPasswordNode @AssistedInject constructor(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackClick = ::navigateUp,
|
||||
onWaitListError = ::onWaitListError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.features.login.impl.error.isWaitListError
|
||||
import io.element.android.features.login.impl.error.loginError
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
@@ -72,7 +71,6 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
fun LoginPasswordView(
|
||||
state: LoginPasswordState,
|
||||
onBackClick: () -> Unit,
|
||||
onWaitListError: (LoginFormState) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isLoading by remember(state.loginAction) {
|
||||
@@ -149,16 +147,9 @@ fun LoginPasswordView(
|
||||
}
|
||||
|
||||
if (state.loginAction is AsyncData.Failure) {
|
||||
when {
|
||||
state.loginAction.error.isWaitListError() -> {
|
||||
onWaitListError(state.formState)
|
||||
}
|
||||
else -> {
|
||||
LoginErrorDialog(error = state.loginAction.error, onDismiss = {
|
||||
state.eventSink(LoginPasswordEvents.ClearError)
|
||||
})
|
||||
}
|
||||
}
|
||||
LoginErrorDialog(error = state.loginAction.error, onDismiss = {
|
||||
state.eventSink(LoginPasswordEvents.ClearError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +293,5 @@ internal fun LoginPasswordViewPreview(@PreviewParameter(LoginPasswordStateProvid
|
||||
LoginPasswordView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
onWaitListError = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
sealed interface WaitListEvents {
|
||||
data object AttemptLogin : WaitListEvents
|
||||
data object ClearError : WaitListEvents
|
||||
data object Continue : WaitListEvents
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.login.impl.screens.loginpassword.LoginFormState
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
class WaitListNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: WaitListPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
data class Inputs(val loginFormState: LoginFormState) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
private val presenter = presenterFactory.create(inputs.loginFormState)
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onCancelClick()
|
||||
}
|
||||
|
||||
private fun onCancelClick() {
|
||||
plugins<Callback>().forEach { it.onCancelClick() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
WaitListView(
|
||||
state = state,
|
||||
onCancelClick = ::onCancelClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.screens.loginpassword.LoginFormState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class WaitListPresenter @AssistedInject constructor(
|
||||
@Assisted private val formState: LoginFormState,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||
) : Presenter<WaitListState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(loginFormState: LoginFormState): WaitListPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): WaitListState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val homeserverUrl = remember {
|
||||
authenticationService.getHomeserverDetails().value?.url ?: "server"
|
||||
}
|
||||
|
||||
val loginAction: MutableState<AsyncData<SessionId>> = remember {
|
||||
mutableStateOf(AsyncData.Uninitialized)
|
||||
}
|
||||
|
||||
val attemptNumber = remember { mutableIntStateOf(0) }
|
||||
|
||||
fun handleEvents(event: WaitListEvents) {
|
||||
when (event) {
|
||||
WaitListEvents.AttemptLogin -> {
|
||||
// Do not attempt to login on first resume of the View.
|
||||
attemptNumber.intValue++
|
||||
if (attemptNumber.intValue > 1) {
|
||||
coroutineScope.loginAttempt(formState, loginAction)
|
||||
}
|
||||
}
|
||||
WaitListEvents.ClearError -> loginAction.value = AsyncData.Uninitialized
|
||||
WaitListEvents.Continue -> defaultLoginUserStory.setLoginFlowIsDone(true)
|
||||
}
|
||||
}
|
||||
|
||||
return WaitListState(
|
||||
appName = buildMeta.applicationName,
|
||||
serverName = homeserverUrl,
|
||||
loginAction = loginAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.loginAttempt(formState: LoginFormState, loggedInState: MutableState<AsyncData<SessionId>>) = launch {
|
||||
Timber.w("Attempt to login...")
|
||||
loggedInState.value = AsyncData.Loading()
|
||||
authenticationService.login(formState.login.trim(), formState.password)
|
||||
.onSuccess { sessionId ->
|
||||
loggedInState.value = AsyncData.Success(sessionId)
|
||||
}
|
||||
.onFailure { failure ->
|
||||
loggedInState.value = AsyncData.Failure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class WaitListState(
|
||||
val appName: String,
|
||||
val serverName: String,
|
||||
val loginAction: AsyncData<SessionId>,
|
||||
val eventSink: (WaitListEvents) -> Unit
|
||||
)
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
open class WaitListStateProvider : PreviewParameterProvider<WaitListState> {
|
||||
override val values: Sequence<WaitListState>
|
||||
get() = sequenceOf(
|
||||
aWaitListState(loginAction = AsyncData.Uninitialized),
|
||||
aWaitListState(loginAction = AsyncData.Loading()),
|
||||
aWaitListState(loginAction = AsyncData.Failure(Throwable("error"))),
|
||||
aWaitListState(loginAction = AsyncData.Failure(Throwable(message = "IO_ELEMENT_X_WAIT_LIST"))),
|
||||
aWaitListState(loginAction = AsyncData.Success(SessionId("@alice:element.io"))),
|
||||
// Add other state here
|
||||
)
|
||||
}
|
||||
|
||||
fun aWaitListState(
|
||||
appName: String = "Element X",
|
||||
serverName: String = "server.org",
|
||||
loginAction: AsyncData<SessionId> = AsyncData.Uninitialized,
|
||||
) = WaitListState(
|
||||
appName = appName,
|
||||
serverName = serverName,
|
||||
loginAction = loginAction,
|
||||
eventSink = {}
|
||||
)
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.features.login.impl.error.isWaitListError
|
||||
import io.element.android.features.login.impl.error.loginError
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.pages.SunsetPage
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
// Ref: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=6761-148425
|
||||
// Only the first screen can be displayed, since once logged in, this Node will be remove by the RootNode.
|
||||
@Composable
|
||||
fun WaitListView(
|
||||
state: WaitListState,
|
||||
onCancelClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> state.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
WaitListContent(state, onCancelClick, modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WaitListError(state: WaitListState) {
|
||||
// Display a dialog for error other than the waitlist error
|
||||
state.loginAction.errorOrNull()?.let { error ->
|
||||
if (error.isWaitListError().not()) {
|
||||
RetryDialog(
|
||||
content = stringResource(id = loginError(error)),
|
||||
onRetry = {
|
||||
state.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
},
|
||||
onDismiss = {
|
||||
state.eventSink.invoke(WaitListEvents.ClearError)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WaitListContent(
|
||||
state: WaitListState,
|
||||
onCancelClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
val title = stringResource(
|
||||
when (state.loginAction) {
|
||||
is AsyncData.Success -> R.string.screen_waitlist_title_success
|
||||
else -> R.string.screen_waitlist_title
|
||||
}
|
||||
)
|
||||
val subtitle = when (state.loginAction) {
|
||||
is AsyncData.Success -> stringResource(
|
||||
id = R.string.screen_waitlist_message_success,
|
||||
state.appName,
|
||||
)
|
||||
else -> stringResource(
|
||||
id = R.string.screen_waitlist_message,
|
||||
state.appName,
|
||||
state.serverName,
|
||||
)
|
||||
}
|
||||
SunsetPage(
|
||||
isLoading = state.loginAction.isLoading(),
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
) {
|
||||
OverallContent(state, onCancelClick)
|
||||
}
|
||||
WaitListError(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OverallContent(
|
||||
state: WaitListState,
|
||||
onCancelClick: () -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (state.loginAction !is AsyncData.Success) {
|
||||
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textOnSolidPrimary) {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_cancel),
|
||||
onClick = onCancelClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (state.loginAction is AsyncData.Success) {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_continue),
|
||||
onClick = { state.eventSink.invoke(WaitListEvents.Continue) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun WaitListViewPreview(@PreviewParameter(WaitListStateProvider::class) state: WaitListState) = ElementPreview {
|
||||
WaitListView(
|
||||
state = state,
|
||||
onCancelClick = {},
|
||||
)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.screens.waitlistscreen
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.screens.loginpassword.LoginFormState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class WaitListPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val authenticationService = FakeMatrixAuthenticationService().apply {
|
||||
givenHomeserver(A_HOMESERVER)
|
||||
}
|
||||
val loginUserStory = DefaultLoginUserStory()
|
||||
val presenter = WaitListPresenter(
|
||||
LoginFormState.Default,
|
||||
aBuildMeta(applicationName = "Application Name"),
|
||||
authenticationService,
|
||||
loginUserStory,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.appName).isEqualTo("Application Name")
|
||||
assertThat(initialState.serverName).isEqualTo(A_HOMESERVER_URL)
|
||||
assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - attempt login with error`() = runTest {
|
||||
val authenticationService = FakeMatrixAuthenticationService().apply {
|
||||
givenLoginError(A_THROWABLE)
|
||||
}
|
||||
val loginUserStory = DefaultLoginUserStory()
|
||||
val presenter = WaitListPresenter(
|
||||
LoginFormState.Default,
|
||||
aBuildMeta(),
|
||||
authenticationService,
|
||||
loginUserStory,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
// First usage of AttemptLogin, nothing should happen
|
||||
initialState.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
expectNoEvents()
|
||||
initialState.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
val submitState = awaitItem()
|
||||
assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.loginAction).isEqualTo(AsyncData.Failure<SessionId>(A_THROWABLE))
|
||||
// Assert the error can be cleared
|
||||
errorState.eventSink(WaitListEvents.ClearError)
|
||||
val clearedState = awaitItem()
|
||||
assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - attempt login with success`() = runTest {
|
||||
val authenticationService = FakeMatrixAuthenticationService()
|
||||
val loginUserStory = DefaultLoginUserStory().apply { setLoginFlowIsDone(false) }
|
||||
val presenter = WaitListPresenter(
|
||||
LoginFormState.Default,
|
||||
aBuildMeta(),
|
||||
authenticationService,
|
||||
loginUserStory,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(loginUserStory.loginFlowIsDone.value).isFalse()
|
||||
val initialState = awaitItem()
|
||||
// First usage of AttemptLogin, nothing should happen
|
||||
initialState.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
expectNoEvents()
|
||||
initialState.eventSink.invoke(WaitListEvents.AttemptLogin)
|
||||
val submitState = awaitItem()
|
||||
assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.loginAction).isEqualTo(AsyncData.Success(A_USER_ID))
|
||||
assertThat(loginUserStory.loginFlowIsDone.value).isFalse()
|
||||
successState.eventSink.invoke(WaitListEvents.Continue)
|
||||
assertThat(loginUserStory.loginFlowIsDone.value).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ class LoginScreen(private val authenticationService: MatrixAuthenticationService
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackClick = {},
|
||||
onWaitListError = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user