Fix login flow (#4813)

* Also clear the data, to let the next screen be able to go back

* Disable the change account provider button when the form is loading the data to prevent double navigation

* Improve OnboardingViewTest, ensure that no Event are emitted.

* OnboardingViewTest: add tests to cover the change.
This commit is contained in:
Benoit Marty 2025-06-04 10:02:58 +02:00 committed by GitHub
parent 58a3ea8b1f
commit cb9c6776e1
3 changed files with 69 additions and 3 deletions

View file

@ -81,6 +81,8 @@ fun LoginModeView(
LoginMode.PasswordLogin -> onNeedLoginPassword() LoginMode.PasswordLogin -> onNeedLoginPassword()
is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url) is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url)
} }
// Also clear the data, to let the next screen be able to go back
onClearError()
} }
AsyncData.Uninitialized -> Unit AsyncData.Uninitialized -> Unit
} }

View file

@ -88,7 +88,7 @@ fun ConfirmAccountProviderView(
TextButton( TextButton(
text = stringResource(id = R.string.screen_account_provider_change), text = stringResource(id = R.string.screen_account_provider_change),
onClick = onChange, onClick = onChange,
enabled = true, enabled = !isLoading,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.testTag(TestTags.loginChangeServer) .testTag(TestTags.loginChangeServer)

View file

@ -14,6 +14,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.login.LoginMode
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
@ -36,9 +37,13 @@ class OnboardingViewTest {
@Test @Test
fun `when can create account - clicking on create account calls the expected callback`() { fun `when can create account - clicking on create account calls the expected callback`() {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState(canCreateAccount = true), state = anOnBoardingState(
canCreateAccount = true,
eventSink = eventSink,
),
onCreateAccount = callback, onCreateAccount = callback,
) )
rule.clickOn(R.string.screen_onboarding_sign_up) rule.clickOn(R.string.screen_onboarding_sign_up)
@ -47,9 +52,13 @@ class OnboardingViewTest {
@Test @Test
fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() { fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState(canLoginWithQrCode = true), state = anOnBoardingState(
canLoginWithQrCode = true,
eventSink = eventSink,
),
onSignInWithQrCode = callback, onSignInWithQrCode = callback,
) )
rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code) rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code)
@ -73,11 +82,13 @@ class OnboardingViewTest {
private fun `when can login with QR code - clicking on sign in manually calls the expected callback`( private fun `when can login with QR code - clicking on sign in manually calls the expected callback`(
mustChooseAccountProvider: Boolean, mustChooseAccountProvider: Boolean,
) { ) {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState( state = anOnBoardingState(
canLoginWithQrCode = true, canLoginWithQrCode = true,
mustChooseAccountProvider = mustChooseAccountProvider, mustChooseAccountProvider = mustChooseAccountProvider,
eventSink = eventSink,
), ),
onSignIn = callback, onSignIn = callback,
) )
@ -102,12 +113,14 @@ class OnboardingViewTest {
private fun `when cannot login with QR code or create account - clicking on continue calls the sign in callback`( private fun `when cannot login with QR code or create account - clicking on continue calls the sign in callback`(
mustChooseAccountProvider: Boolean, mustChooseAccountProvider: Boolean,
) { ) {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState( state = anOnBoardingState(
canLoginWithQrCode = false, canLoginWithQrCode = false,
canCreateAccount = false, canCreateAccount = false,
mustChooseAccountProvider = mustChooseAccountProvider, mustChooseAccountProvider = mustChooseAccountProvider,
eventSink = eventSink,
), ),
onSignIn = callback, onSignIn = callback,
) )
@ -145,10 +158,12 @@ class OnboardingViewTest {
@Test @Test
fun `clicking on report a problem calls the sign in callback`() { fun `clicking on report a problem calls the sign in callback`() {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState( state = anOnBoardingState(
canReportBug = true, canReportBug = true,
eventSink = eventSink,
), ),
onReportProblem = callback, onReportProblem = callback,
) )
@ -160,15 +175,64 @@ class OnboardingViewTest {
@Test @Test
fun `cannot report a problem when the feature is disabled`() { fun `cannot report a problem when the feature is disabled`() {
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
rule.setOnboardingView( rule.setOnboardingView(
state = anOnBoardingState( state = anOnBoardingState(
canReportBug = false, canReportBug = false,
eventSink = eventSink,
), ),
) )
val text = rule.activity.getString(CommonStrings.common_report_a_problem) val text = rule.activity.getString(CommonStrings.common_report_a_problem)
rule.onNodeWithText(text).assertDoesNotExist() rule.onNodeWithText(text).assertDoesNotExist()
} }
@Test
fun `when success PasswordLogin - the expected callback is invoked and the event is received`() {
val eventSink = EventsRecorder<OnBoardingEvents>()
ensureCalledOnce { callback ->
rule.setOnboardingView(
state = anOnBoardingState(
loginMode = AsyncData.Success(LoginMode.PasswordLogin),
eventSink = eventSink,
),
onNeedLoginPassword = callback,
)
}
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
@Test
fun `when success Oidc - the expected callback is invoked and the event is received`() {
val eventSink = EventsRecorder<OnBoardingEvents>()
val oidcDetails = OidcDetails("aUrl")
ensureCalledOnceWithParam(oidcDetails) { callback ->
rule.setOnboardingView(
state = anOnBoardingState(
loginMode = AsyncData.Success(LoginMode.Oidc(oidcDetails)),
eventSink = eventSink,
),
onOidcDetails = callback,
)
}
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
@Test
fun `when success AccountCreation - the expected callback is invoked and the event is received`() {
val eventSink = EventsRecorder<OnBoardingEvents>()
val oidcDetails = OidcDetails("aUrl")
ensureCalledOnceWithParam(oidcDetails.url) { callback ->
rule.setOnboardingView(
state = anOnBoardingState(
loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl")),
eventSink = eventSink,
),
onCreateAccountContinue = callback,
)
}
eventSink.assertSingle(OnBoardingEvents.ClearError)
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setOnboardingView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setOnboardingView(
state: OnBoardingState, state: OnBoardingState,
onSignInWithQrCode: () -> Unit = EnsureNeverCalled(), onSignInWithQrCode: () -> Unit = EnsureNeverCalled(),