LoginPasswordEvents -> LoginPasswordEvent

This commit is contained in:
Benoit Marty
2026-08-25 10:02:25 +02:00
parent 873843b669
commit 835690a1c2
7 changed files with 51 additions and 51 deletions
@@ -8,9 +8,9 @@
package io.element.android.features.login.impl.screens.loginpassword package io.element.android.features.login.impl.screens.loginpassword
sealed interface LoginPasswordEvents { sealed interface LoginPasswordEvent {
data class SetLogin(val login: String) : LoginPasswordEvents data class SetLogin(val login: String) : LoginPasswordEvent
data class SetPassword(val password: String) : LoginPasswordEvents data class SetPassword(val password: String) : LoginPasswordEvent
data object Submit : LoginPasswordEvents data object Submit : LoginPasswordEvent
data object ClearError : LoginPasswordEvents data object ClearError : LoginPasswordEvent
} }
@@ -58,18 +58,18 @@ class LoginPasswordPresenter(
} }
val accountProvider by accountProviderDataSource.flow.collectAsState() val accountProvider by accountProviderDataSource.flow.collectAsState()
fun handleEvent(event: LoginPasswordEvents) { fun handleEvent(event: LoginPasswordEvent) {
when (event) { when (event) {
is LoginPasswordEvents.SetLogin -> updateFormState(formState) { is LoginPasswordEvent.SetLogin -> updateFormState(formState) {
copy(login = event.login) copy(login = event.login)
} }
is LoginPasswordEvents.SetPassword -> updateFormState(formState) { is LoginPasswordEvent.SetPassword -> updateFormState(formState) {
copy(password = event.password) copy(password = event.password)
} }
LoginPasswordEvents.Submit -> { LoginPasswordEvent.Submit -> {
localCoroutineScope.submit(formState.value, loginAction) localCoroutineScope.submit(formState.value, loginAction)
} }
LoginPasswordEvents.ClearError -> loginAction.value = AsyncData.Uninitialized LoginPasswordEvent.ClearError -> loginAction.value = AsyncData.Uninitialized
} }
} }
@@ -18,7 +18,7 @@ data class LoginPasswordState(
val accountProvider: AccountProvider, val accountProvider: AccountProvider,
val formState: LoginFormState, val formState: LoginFormState,
val loginAction: AsyncData<SessionId>, val loginAction: AsyncData<SessionId>,
val eventSink: (LoginPasswordEvents) -> Unit val eventSink: (LoginPasswordEvent) -> Unit
) { ) {
val submitEnabled: Boolean val submitEnabled: Boolean
get() = loginAction !is AsyncData.Failure && get() = loginAction !is AsyncData.Failure &&
@@ -29,7 +29,7 @@ fun aLoginPasswordState(
accountProvider: AccountProvider = anAccountProvider(), accountProvider: AccountProvider = anAccountProvider(),
formState: LoginFormState = LoginFormState.Default, formState: LoginFormState = LoginFormState.Default,
loginAction: AsyncData<SessionId> = AsyncData.Uninitialized, loginAction: AsyncData<SessionId> = AsyncData.Uninitialized,
eventSink: (LoginPasswordEvents) -> Unit = {}, eventSink: (LoginPasswordEvent) -> Unit = {},
) = LoginPasswordState( ) = LoginPasswordState(
accountProvider = accountProvider, accountProvider = accountProvider,
formState = formState, formState = formState,
@@ -97,7 +97,7 @@ fun LoginPasswordView(
autofillManager?.commit() autofillManager?.commit()
state.eventSink(LoginPasswordEvents.Submit) state.eventSink(LoginPasswordEvent.Submit)
} }
Scaffold( Scaffold(
@@ -166,7 +166,7 @@ fun LoginPasswordView(
if (state.loginAction is AsyncData.Failure) { if (state.loginAction is AsyncData.Failure) {
LoginErrorDialog(error = state.loginAction.error, onDismiss = { LoginErrorDialog(error = state.loginAction.error, onDismiss = {
state.eventSink(LoginPasswordEvents.ClearError) state.eventSink(LoginPasswordEvent.ClearError)
}) })
} }
} }
@@ -202,7 +202,7 @@ private fun LoginForm(
onValueChange = { onValueChange = {
val sanitized = it.sanitize() val sanitized = it.sanitize()
loginFieldState = sanitized loginFieldState = sanitized
eventSink(LoginPasswordEvents.SetLogin(sanitized)) eventSink(LoginPasswordEvent.SetLogin(sanitized))
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Email,
@@ -220,7 +220,7 @@ private fun LoginForm(
role = Role.Button, role = Role.Button,
) { ) {
loginFieldState = "" loginFieldState = ""
eventSink(LoginPasswordEvents.SetLogin("")) eventSink(LoginPasswordEvent.SetLogin(""))
} }
) { ) {
Icon( Icon(
@@ -254,7 +254,7 @@ private fun LoginForm(
onValueChange = { onValueChange = {
val sanitized = it.sanitize() val sanitized = it.sanitize()
passwordFieldState = sanitized passwordFieldState = sanitized
eventSink(LoginPasswordEvents.SetPassword(sanitized)) eventSink(LoginPasswordEvent.SetPassword(sanitized))
}, },
placeholder = stringResource(CommonStrings.common_password), placeholder = stringResource(CommonStrings.common_password),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
@@ -54,7 +54,7 @@ class LoginPasswordPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.formState.login).isEqualTo(A_USER_NAME) assertThat(initialState.formState.login).isEqualTo(A_USER_NAME)
// Login can be changed // Login can be changed
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME_2)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME_2))
val loginChangedState = awaitItem() val loginChangedState = awaitItem()
assertThat(loginChangedState.formState.login).isEqualTo(A_USER_NAME_2) assertThat(loginChangedState.formState.login).isEqualTo(A_USER_NAME_2)
} }
@@ -71,11 +71,11 @@ class LoginPasswordPresenterTest {
authenticationService = authenticationService, authenticationService = authenticationService,
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME))
val loginState = awaitItem() val loginState = awaitItem()
assertThat(loginState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = "")) assertThat(loginState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = ""))
assertThat(loginState.submitEnabled).isFalse() assertThat(loginState.submitEnabled).isFalse()
initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD))
val loginAndPasswordState = awaitItem() val loginAndPasswordState = awaitItem()
assertThat(loginAndPasswordState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = A_PASSWORD)) assertThat(loginAndPasswordState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = A_PASSWORD))
assertThat(loginAndPasswordState.submitEnabled).isTrue() assertThat(loginAndPasswordState.submitEnabled).isTrue()
@@ -93,11 +93,11 @@ class LoginPasswordPresenterTest {
authenticationService = authenticationService, authenticationService = authenticationService,
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME))
initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD))
skipItems(1) skipItems(1)
val loginAndPasswordState = awaitItem() val loginAndPasswordState = awaitItem()
loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit)
val submitState = awaitItem() val submitState = awaitItem()
assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java)
val loggedInState = awaitItem() val loggedInState = awaitItem()
@@ -118,11 +118,11 @@ class LoginPasswordPresenterTest {
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME))
initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD))
skipItems(1) skipItems(1)
val loginAndPasswordState = awaitItem() val loginAndPasswordState = awaitItem()
loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit)
skipItems(1) skipItems(1)
val loggedInState = awaitItem() val loggedInState = awaitItem()
assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID)) assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID))
@@ -142,12 +142,12 @@ class LoginPasswordPresenterTest {
authenticationService = authenticationService, authenticationService = authenticationService,
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME))
initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD))
skipItems(1) skipItems(1)
val loginAndPasswordState = awaitItem() val loginAndPasswordState = awaitItem()
authenticationService.givenLoginError(AN_EXCEPTION) authenticationService.givenLoginError(AN_EXCEPTION)
loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit)
val submitState = awaitItem() val submitState = awaitItem()
assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java)
val loggedInState = awaitItem() val loggedInState = awaitItem()
@@ -166,19 +166,19 @@ class LoginPasswordPresenterTest {
authenticationService = authenticationService, authenticationService = authenticationService,
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME))
initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD))
skipItems(1) skipItems(1)
val loginAndPasswordState = awaitItem() val loginAndPasswordState = awaitItem()
authenticationService.givenLoginError(AN_EXCEPTION) authenticationService.givenLoginError(AN_EXCEPTION)
loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit)
val submitState = awaitItem() val submitState = awaitItem()
assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java)
val loggedInState = awaitItem() val loggedInState = awaitItem()
// Check an error was returned // Check an error was returned
assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure<SessionId>(AN_EXCEPTION)) assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure<SessionId>(AN_EXCEPTION))
// Assert the error is then cleared // Assert the error is then cleared
loggedInState.eventSink(LoginPasswordEvents.ClearError) loggedInState.eventSink(LoginPasswordEvent.ClearError)
val clearedState = awaitItem() val clearedState = awaitItem()
assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized) assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized)
} }
@@ -39,7 +39,7 @@ import org.robolectric.annotation.Config
class LoginPasswordViewTest : RobolectricTest() { class LoginPasswordViewTest : RobolectricTest() {
@Test @Test
fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<LoginPasswordEvent>(expectEvents = false)
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
@@ -53,7 +53,7 @@ class LoginPasswordViewTest : RobolectricTest() {
@Test @Test
fun `changing login invokes the expected event`() = runAndroidComposeUiTest { fun `changing login invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@@ -62,13 +62,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val userNameHint = activity!!.getString(CommonStrings.common_username) val userNameHint = activity!!.getString(CommonStrings.common_username)
onNodeWithText(userNameHint).performTextInput(A_USER_NAME) onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin(A_USER_NAME) LoginPasswordEvent.SetLogin(A_USER_NAME)
) )
} }
@Test @Test
fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest { fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@@ -77,13 +77,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val userNameHint = activity!!.getString(CommonStrings.common_username) val userNameHint = activity!!.getString(CommonStrings.common_username)
onNodeWithText(userNameHint).performTextInput("a\nb") onNodeWithText(userNameHint).performTextInput("a\nb")
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin("ab") LoginPasswordEvent.SetLogin("ab")
) )
} }
@Test @Test
fun `changing login removes carriage returns the expected event`() = runAndroidComposeUiTest { fun `changing login removes carriage returns the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@@ -92,13 +92,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val userNameHint = activity!!.getString(CommonStrings.common_username) val userNameHint = activity!!.getString(CommonStrings.common_username)
onNodeWithText(userNameHint).performTextInput("a\r\nb") onNodeWithText(userNameHint).performTextInput("a\r\nb")
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin("ab") LoginPasswordEvent.SetLogin("ab")
) )
} }
@Test @Test
fun `clearing login invokes the expected event`() = runAndroidComposeUiTest { fun `clearing login invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
formState = aLoginFormState(A_USER_NAME), formState = aLoginFormState(A_USER_NAME),
@@ -108,13 +108,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val a11yClear = activity!!.getString(CommonStrings.action_clear) val a11yClear = activity!!.getString(CommonStrings.action_clear)
onNodeWithContentDescription(a11yClear).performClick() onNodeWithContentDescription(a11yClear).performClick()
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetLogin("") LoginPasswordEvent.SetLogin("")
) )
} }
@Test @Test
fun `changing password invokes the expected event`() = runAndroidComposeUiTest { fun `changing password invokes the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@@ -123,13 +123,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val userNameHint = activity!!.getString(CommonStrings.common_password) val userNameHint = activity!!.getString(CommonStrings.common_password)
onNodeWithText(userNameHint).performTextInput(A_PASSWORD) onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetPassword(A_PASSWORD) LoginPasswordEvent.SetPassword(A_PASSWORD)
) )
} }
@Test @Test
fun `changing password removes carriage returns the expected event`() = runAndroidComposeUiTest { fun `changing password removes carriage returns the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@@ -138,13 +138,13 @@ class LoginPasswordViewTest : RobolectricTest() {
val passwordHint = activity!!.getString(CommonStrings.common_password) val passwordHint = activity!!.getString(CommonStrings.common_password)
onNodeWithText(passwordHint).performTextInput("a\r\nb") onNodeWithText(passwordHint).performTextInput("a\r\nb")
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.SetPassword("ab") LoginPasswordEvent.SetPassword("ab")
) )
} }
@Test @Test
fun `reveal password makes the password visible`() = runAndroidComposeUiTest { fun `reveal password makes the password visible`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<LoginPasswordEvent>(expectEvents = false)
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
formState = aLoginFormState(password = A_PASSWORD), formState = aLoginFormState(password = A_PASSWORD),
@@ -165,7 +165,7 @@ class LoginPasswordViewTest : RobolectricTest() {
@Test @Test
fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest { fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<LoginPasswordEvent>(expectEvents = false)
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
formState = aLoginFormState(password = A_PASSWORD), formState = aLoginFormState(password = A_PASSWORD),
@@ -178,7 +178,7 @@ class LoginPasswordViewTest : RobolectricTest() {
@Test @Test
fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest { fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<LoginPasswordEvent>(expectEvents = false)
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
formState = aLoginFormState(login = A_USER_NAME), formState = aLoginFormState(login = A_USER_NAME),
@@ -192,7 +192,7 @@ class LoginPasswordViewTest : RobolectricTest() {
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
@Test @Test
fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest { fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<LoginPasswordEvents>() val eventsRecorder = EventsRecorder<LoginPasswordEvent>()
setLoginPasswordView( setLoginPasswordView(
aLoginPasswordState( aLoginPasswordState(
formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD), formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD),
@@ -203,7 +203,7 @@ class LoginPasswordViewTest : RobolectricTest() {
onNodeWithText(continueStr).assertIsEnabled() onNodeWithText(continueStr).assertIsEnabled()
clickOn(CommonStrings.action_continue) clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LoginPasswordEvents.Submit LoginPasswordEvent.Submit
) )
} }
} }