Use a shared text entry field for TOTP entry.

This commit is contained in:
Greyson Parrelli
2026-09-11 13:48:17 -04:00
parent 7e569e02c3
commit af920d71bb
25 changed files with 826 additions and 434 deletions
@@ -6,6 +6,7 @@
package org.signal.appsettings.totpcodeentry
import android.app.Application
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
@@ -19,41 +20,65 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.signal.uicomponents.codeentryfield.CodeEntryFieldEvents
import org.signal.uicomponents.codeentryfield.CodeEntryFieldState
import org.signal.uicomponents.codeentryfield.CodeEntryFieldTestTags
@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class)
class TotpCodeEntryScreenTest {
companion object {
private val FULL_CODE = CodeEntryFieldState(digits = listOf("1", "2", "3", "4", "5", "6"))
private val PARTIAL_CODE = CodeEntryFieldState(digits = listOf("1", "2", "3", "", "", ""))
}
@get:Rule
val composeTestRule = createComposeRule()
private val events = mutableListOf<TotpCodeEntryEvent>()
@Test
fun givenAPartialCode_whenScreenDisplayed_thenDoneIsDisabled() {
setContent(TotpCodeEntryState(code = "123"))
fun givenAPartialCode_whenScreenDisplayed_thenNextIsDisabled() {
setContent(TotpCodeEntryState(codeEntry = PARTIAL_CODE))
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.BUTTON_DONE).assertIsNotEnabled()
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.BUTTON_NEXT).assertIsNotEnabled()
}
@Test
fun givenAFullCode_whenIClickDone_thenIExpectDoneEvent() {
setContent(TotpCodeEntryState(code = "123456"))
fun givenAFullCode_whenIClickNext_thenIExpectNextEvent() {
setContent(TotpCodeEntryState(codeEntry = FULL_CODE))
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.BUTTON_DONE)
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.BUTTON_NEXT)
.assertIsEnabled()
.performClick()
assertThat(events).contains(TotpCodeEntryEvent.DoneClicked)
assertThat(events).contains(TotpCodeEntryEvent.NextClicked)
}
@Test
fun whenITypeInTheCodeField_thenIExpectCodeChangedEvent() {
fun whenITypeInTheCodeField_thenIExpectAForwardedCodeEntryEvent() {
setContent(TotpCodeEntryState())
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.CODE_INPUT).performTextInput("123456")
composeTestRule.onNodeWithTag(CodeEntryFieldTestTags.digit(0)).performTextInput("1")
composeTestRule.waitForIdle()
assertThat(events).contains(TotpCodeEntryEvent.CodeChanged("123456"))
assertThat(events).contains(TotpCodeEntryEvent.CodeEntryEvent(CodeEntryFieldEvents.DigitChanged(0, "1")))
}
@Test
fun givenAnIncorrectCode_whenScreenDisplayed_thenIExpectAnError() {
setContent(TotpCodeEntryState(codeEntry = FULL_CODE, error = TotpCodeEntryState.Error.IncorrectCode))
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.ERROR).assertIsDisplayed()
}
@Test
fun givenASubmissionInFlight_whenScreenDisplayed_thenTheCodeFieldIsDisabled() {
setContent(TotpCodeEntryState(codeEntry = FULL_CODE, submitting = true))
composeTestRule.onNodeWithTag(CodeEntryFieldTestTags.digit(0)).assertIsNotEnabled()
composeTestRule.onNodeWithTag(TotpCodeEntryTestTags.BUTTON_NEXT).assertIsNotEnabled()
}
private fun setContent(state: TotpCodeEntryState) {