Browse Source

Pin create: add test for presenter

pull/1608/head
ganfra 11 months ago
parent
commit
e88a5fc858
  1. 1
      build.gradle.kts
  2. 1
      features/lockscreen/impl/build.gradle.kts
  3. 6
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenter.kt
  4. 4
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinStateProvider.kt
  5. 8
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinView.kt
  6. 4
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/CreatePinFailure.kt
  7. 10
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinValidator.kt
  8. 113
      features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenterTest.kt
  9. 10
      tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt

1
build.gradle.kts

@ -253,7 +253,6 @@ koverMerged { @@ -253,7 +253,6 @@ koverMerged {
// Temporary until we have actually something to test.
excludes += "io.element.android.features.lockscreen.impl.auth.PinAuthenticationPresenter"
excludes += "io.element.android.features.lockscreen.impl.auth.PinAuthenticationPresenter$*"
excludes += "io.element.android.features.lockscreen.impl.create.CreatePinPresenter"
}
bound {
minValue = 85

1
features/lockscreen/impl/build.gradle.kts

@ -47,6 +47,7 @@ dependencies { @@ -47,6 +47,7 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.libraries.cryptography.test)
testImplementation(projects.libraries.cryptography.impl)

6
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenter.kt

@ -57,7 +57,7 @@ class CreatePinPresenter @Inject constructor( @@ -57,7 +57,7 @@ class CreatePinPresenter @Inject constructor(
if (confirmPinEntry == choosePinEntry) {
//TODO save in db and navigate to next screen
} else {
createPinFailure = CreatePinFailure.ConfirmationPinNotMatching
createPinFailure = CreatePinFailure.PinsDontMatch
}
}
} else {
@ -74,11 +74,11 @@ class CreatePinPresenter @Inject constructor( @@ -74,11 +74,11 @@ class CreatePinPresenter @Inject constructor(
}
CreatePinEvents.ClearFailure -> {
when (createPinFailure) {
is CreatePinFailure.ConfirmationPinNotMatching -> {
is CreatePinFailure.PinsDontMatch -> {
choosePinEntry = PinEntry.empty(PIN_SIZE)
confirmPinEntry = PinEntry.empty(PIN_SIZE)
}
is CreatePinFailure.ChosenPinBlacklisted -> {
is CreatePinFailure.PinBlacklisted -> {
choosePinEntry = PinEntry.empty(PIN_SIZE)
}
null -> Unit

4
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinStateProvider.kt

@ -35,11 +35,11 @@ open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> { @@ -35,11 +35,11 @@ open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> {
choosePinEntry = PinEntry.empty(4).fillWith("1789"),
confirmPinEntry = PinEntry.empty(4).fillWith("1788"),
isConfirmationStep = true,
creationFailure = CreatePinFailure.ConfirmationPinNotMatching
creationFailure = CreatePinFailure.PinsDontMatch
),
aCreatePinState(
choosePinEntry = PinEntry.empty(4).fillWith("1111"),
creationFailure = CreatePinFailure.ChosenPinBlacklisted
creationFailure = CreatePinFailure.PinBlacklisted
),
)

8
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinView.kt

@ -125,16 +125,16 @@ private fun CreatePinContent( @@ -125,16 +125,16 @@ private fun CreatePinContent(
@Composable
private fun CreatePinFailure.content(): String {
return when (this) {
CreatePinFailure.ChosenPinBlacklisted -> "You cannot choose this as your PIN code for security reasons"
CreatePinFailure.ConfirmationPinNotMatching -> "Please enter the same PIN twice"
CreatePinFailure.PinBlacklisted -> "You cannot choose this as your PIN code for security reasons"
CreatePinFailure.PinsDontMatch -> "Please enter the same PIN twice"
}
}
@Composable
private fun CreatePinFailure.title(): String {
return when (this) {
CreatePinFailure.ChosenPinBlacklisted -> "Choose a different PIN"
CreatePinFailure.ConfirmationPinNotMatching -> "PINs don't match"
CreatePinFailure.PinBlacklisted -> "Choose a different PIN"
CreatePinFailure.PinsDontMatch -> "PINs don't match"
}
}

4
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/CreatePinFailure.kt

@ -17,6 +17,6 @@ @@ -17,6 +17,6 @@
package io.element.android.features.lockscreen.impl.create.validation
sealed interface CreatePinFailure {
data object ChosenPinBlacklisted : CreatePinFailure
data object ConfirmationPinNotMatching : CreatePinFailure
data object PinBlacklisted : CreatePinFailure
data object PinsDontMatch : CreatePinFailure
}

10
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinValidator.kt

@ -16,13 +16,17 @@ @@ -16,13 +16,17 @@
package io.element.android.features.lockscreen.impl.create.validation
import androidx.annotation.VisibleForTesting
import io.element.android.features.lockscreen.impl.create.model.PinEntry
import javax.inject.Inject
private val BLACKLIST = listOf("0000", "1234")
class PinValidator @Inject constructor() {
companion object {
@VisibleForTesting
val BLACKLIST = listOf("0000", "1234")
}
sealed interface Result {
data object Valid : Result
data class Invalid(val failure: CreatePinFailure) : Result
@ -32,7 +36,7 @@ class PinValidator @Inject constructor() { @@ -32,7 +36,7 @@ class PinValidator @Inject constructor() {
val pinAsText = pinEntry.toText()
val isBlacklisted = BLACKLIST.any { it == pinAsText }
return if (isBlacklisted) {
Result.Invalid(CreatePinFailure.ChosenPinBlacklisted)
Result.Invalid(CreatePinFailure.PinBlacklisted)
} else {
Result.Valid
}

113
features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenterTest.kt

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.create
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.lockscreen.impl.create.model.PinDigit
import io.element.android.features.lockscreen.impl.create.model.PinEntry
import io.element.android.features.lockscreen.impl.create.validation.CreatePinFailure
import io.element.android.features.lockscreen.impl.create.validation.PinValidator
import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.test.runTest
import org.junit.Test
class CreatePinPresenterTest {
private val blacklistedPin = PinValidator.BLACKLIST.first()
private val halfCompletePin = "12"
private val completePin = "1235"
private val mismatchedPin = "1236"
@Test
fun `present - complete flow`() = runTest {
val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().also { state ->
state.choosePinEntry.assertEmpty()
state.confirmPinEntry.assertEmpty()
assertThat(state.createPinFailure).isNull()
assertThat(state.isConfirmationStep).isFalse()
state.eventSink(CreatePinEvents.OnPinEntryChanged(halfCompletePin))
}
awaitItem().also { state ->
state.choosePinEntry.assertText(halfCompletePin)
state.confirmPinEntry.assertEmpty()
assertThat(state.createPinFailure).isNull()
assertThat(state.isConfirmationStep).isFalse()
state.eventSink(CreatePinEvents.OnPinEntryChanged(blacklistedPin))
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertText(blacklistedPin)
assertThat(state.createPinFailure).isEqualTo(CreatePinFailure.PinBlacklisted)
state.eventSink(CreatePinEvents.ClearFailure)
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertEmpty()
assertThat(state.createPinFailure).isNull()
state.eventSink(CreatePinEvents.OnPinEntryChanged(completePin))
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertText(completePin)
state.confirmPinEntry.assertEmpty()
assertThat(state.isConfirmationStep).isTrue()
state.eventSink(CreatePinEvents.OnPinEntryChanged(mismatchedPin))
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertText(completePin)
state.confirmPinEntry.assertText(mismatchedPin)
assertThat(state.createPinFailure).isEqualTo(CreatePinFailure.PinsDontMatch)
state.eventSink(CreatePinEvents.ClearFailure)
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertEmpty()
state.confirmPinEntry.assertEmpty()
assertThat(state.isConfirmationStep).isFalse()
assertThat(state.createPinFailure).isNull()
state.eventSink(CreatePinEvents.OnPinEntryChanged(completePin))
}
awaitLastSequentialItem().also { state ->
state.choosePinEntry.assertText(completePin)
state.confirmPinEntry.assertEmpty()
assertThat(state.isConfirmationStep).isTrue()
state.eventSink(CreatePinEvents.OnPinEntryChanged(completePin))
}
awaitItem().also { state ->
state.choosePinEntry.assertText(completePin)
state.confirmPinEntry.assertText(completePin)
}
}
}
private fun PinEntry.assertText(text: String) {
assertThat(toText()).isEqualTo(text)
}
private fun PinEntry.assertEmpty() {
val isEmpty = digits.all { it is PinDigit.Empty }
assertThat(isEmpty).isTrue()
}
private fun createPresenter(): CreatePinPresenter {
return CreatePinPresenter(PinValidator())
}
}

10
tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt

@ -32,6 +32,16 @@ suspend fun <T : Any> ReceiveTurbine<T>.consumeItemsUntilTimeout(timeout: Durati @@ -32,6 +32,16 @@ suspend fun <T : Any> ReceiveTurbine<T>.consumeItemsUntilTimeout(timeout: Durati
return consumeItemsUntilPredicate(timeout) { false }
}
/**
* Consume all items which are emitted sequentially.
* Use the smallest timeout possible internally to avoid wasting time.
* Same as calling skipItems(x) and then awaitItem() but without assumption on the number of items.
* @return the last item emitted.
*/
suspend fun <T : Any> ReceiveTurbine<T>.awaitLastSequentialItem(): T {
return consumeItemsUntilTimeout(1.milliseconds).last()
}
/**
* Consume items until predicate is true, or timeout is reached waiting for an event, or we receive terminal event.
* The timeout is applied for each event.

Loading…
Cancel
Save