mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-20 00:35:47 +01:00
Fix small race condition with username reservation.
This commit is contained in:
committed by
Cody Henthorne
parent
914917c7e9
commit
28c69c8a0a
+4
-1
@@ -126,6 +126,9 @@ class AddUsernameViewModel(
|
||||
* Non-digits are dropped as they're typed, since a discriminator can only ever be digits.
|
||||
*
|
||||
* Emptying the field hands control back to the service once the field loses focus (see [applyDiscriminatorFocusLost]).
|
||||
* Until then we hold onto a service-assigned reservation, since that pairing is still the one the entry is showing.
|
||||
* A user-typed reservation, on the other hand, is dropped as soon as its digits are erased -- the user asked for that
|
||||
* exact number and no longer wants it, so there is nothing left to submit until the field settles.
|
||||
*/
|
||||
private fun applyDiscriminatorChanged(state: AddUsernameState, discriminator: String, stateEmitter: (AddUsernameState) -> Unit) {
|
||||
val digitsOnly = discriminator.filter { it in '0'..'9' }
|
||||
@@ -141,7 +144,7 @@ class AddUsernameViewModel(
|
||||
discriminator = digitsOnly,
|
||||
isDiscriminatorUserSet = isUserSet,
|
||||
validationError = null,
|
||||
reservation = if (isUserSet) null else state.reservation,
|
||||
reservation = state.reservation.takeIf { !isUserSet && !state.isDiscriminatorUserSet },
|
||||
isReserving = false
|
||||
)
|
||||
|
||||
|
||||
+36
@@ -205,6 +205,42 @@ class AddUsernameViewModelTest {
|
||||
assertThat(viewModel.state.value.isSubmittable).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `emptying a user-typed discriminator drops its reservation`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.reserveUsername("maya") } returns RequestResult.Success(Username("maya.45"))
|
||||
coEvery { mockRepository.reserveUsername("maya", "77") } returns RequestResult.Success(Username("maya.77"))
|
||||
|
||||
viewModel.onEvent(AddUsernameScreenEvents.UsernameChanged("maya"))
|
||||
advanceUntilIdle()
|
||||
viewModel.onEvent(AddUsernameScreenEvents.DiscriminatorChanged("77"))
|
||||
advanceUntilIdle()
|
||||
viewModel.onEvent(AddUsernameScreenEvents.DiscriminatorChanged(""))
|
||||
advanceUntilIdle()
|
||||
|
||||
assertThat(viewModel.state.value.discriminator).isEmpty()
|
||||
assertThat(viewModel.state.value.isDiscriminatorUserSet).isFalse()
|
||||
assertThat(viewModel.state.value.reservation).isNull()
|
||||
assertThat(viewModel.state.value.isSubmittable).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NextClicked cannot confirm a discriminator the user just erased`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.reserveUsername("maya") } returns RequestResult.Success(Username("maya.45"))
|
||||
coEvery { mockRepository.reserveUsername("maya", "77") } returns RequestResult.Success(Username("maya.77"))
|
||||
|
||||
viewModel.onEvent(AddUsernameScreenEvents.UsernameChanged("maya"))
|
||||
advanceUntilIdle()
|
||||
viewModel.onEvent(AddUsernameScreenEvents.DiscriminatorChanged("77"))
|
||||
advanceUntilIdle()
|
||||
viewModel.onEvent(AddUsernameScreenEvents.DiscriminatorChanged(""))
|
||||
advanceUntilIdle()
|
||||
viewModel.onEvent(AddUsernameScreenEvents.NextClicked)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { mockRepository.confirmUsername(any()) }
|
||||
assertThat(parentEvents).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `losing focus with an empty discriminator hands it back to the service`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.reserveUsername("maya") } returns RequestResult.Success(Username("maya.45")) andThen RequestResult.Success(Username("maya.99"))
|
||||
|
||||
Reference in New Issue
Block a user