PIN : add test for SetupPinPresenter

This commit is contained in:
ganfra 2023-10-23 11:41:08 +02:00
parent 4fbe32a6da
commit 6230d9d48b
7 changed files with 123 additions and 13 deletions

View file

@ -250,8 +250,6 @@ koverMerged {
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
// Some options can't be tested at the moment
excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*"
// Temporary until we have actually something to test.
excludes += "io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter$*"
}
bound {
minValue = 85

View file

@ -51,6 +51,8 @@ dependencies {
testImplementation(projects.tests.testutils)
testImplementation(projects.libraries.cryptography.test)
testImplementation(projects.libraries.cryptography.impl)
testImplementation(projects.libraries.featureflag.test)
ksp(libs.showkase.processor)
}

View file

@ -20,6 +20,6 @@ import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
sealed interface PinUnlockEvents {
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents
data object Unlock : PinUnlockEvents
data object OnForgetPin : PinUnlockEvents
data object ClearSignOutPrompt : PinUnlockEvents
}

View file

@ -53,7 +53,6 @@ class PinUnlockPresenter @Inject constructor(
fun handleEvents(event: PinUnlockEvents) {
when (event) {
PinUnlockEvents.Unlock -> coroutineScope.launch { pinStateService.unlock() }
is PinUnlockEvents.OnPinKeypadPressed -> {
pinEntry = pinEntry.process(event.pinKeypadModel)
if (pinEntry.isComplete()) {
@ -61,6 +60,7 @@ class PinUnlockPresenter @Inject constructor(
}
}
PinUnlockEvents.OnForgetPin -> showSignOutPrompt = true
PinUnlockEvents.ClearSignOutPrompt -> showSignOutPrompt = false
}
}
return PinUnlockState(

View file

@ -0,0 +1,28 @@
/*
* 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.pin.model
import com.google.common.truth.Truth.assertThat
fun PinEntry.assertText(text: String) {
assertThat(toText()).isEqualTo(text)
}
fun PinEntry.assertEmpty() {
val isEmpty = digits.all { it is PinDigit.Empty }
assertThat(isEmpty).isTrue()
}

View file

@ -22,6 +22,8 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.lockscreen.impl.pin.model.PinDigit
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.pin.model.assertEmpty
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.setup.validation.PinValidator
import io.element.android.features.lockscreen.impl.setup.validation.SetupPinFailure
import io.element.android.libraries.matrix.test.core.aBuildMeta
@ -99,15 +101,6 @@ class SetupPinPresenterTest {
}
}
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 createSetupPinPresenter(): SetupPinPresenter {
return SetupPinPresenter(PinValidator(), aBuildMeta())
}

View file

@ -0,0 +1,89 @@
/*
* 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.unlock
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.pin.model.assertEmpty
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.state.DefaultLockScreenStateService
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class PinUnlockPresenterTest {
private val halfCompletePin = "12"
private val completePin = "1235"
@Test
fun `present - complete flow`() = runTest {
val presenter = createPinUnlockPresenter(this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().also { state ->
state.pinEntry.assertEmpty()
assertThat(state.showWrongPinTitle).isFalse()
assertThat(state.showSignOutPrompt).isFalse()
assertThat(state.remainingAttempts).isEqualTo(3)
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('1')))
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('2')))
}
awaitLastSequentialItem().also { state ->
state.pinEntry.assertText(halfCompletePin)
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('3')))
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Back))
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Empty))
}
awaitLastSequentialItem().also { state ->
state.pinEntry.assertText(halfCompletePin)
state.eventSink(PinUnlockEvents.OnForgetPin)
}
awaitLastSequentialItem().also { state ->
assertThat(state.showSignOutPrompt).isEqualTo(true)
assertThat(state.isSignOutPromptCancellable).isEqualTo(true)
state.eventSink(PinUnlockEvents.ClearSignOutPrompt)
}
awaitLastSequentialItem().also { state ->
assertThat(state.showSignOutPrompt).isEqualTo(false)
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('3')))
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(PinKeypadModel.Number('5')))
}
awaitLastSequentialItem().also { state ->
state.pinEntry.assertText(completePin)
}
}
}
private suspend fun createPinUnlockPresenter(scope: CoroutineScope): PinUnlockPresenter {
val featureFlagService = FakeFeatureFlagService().apply {
setFeatureEnabled(FeatureFlags.PinUnlock, true)
}
val lockScreenStateService = DefaultLockScreenStateService(featureFlagService)
return PinUnlockPresenter(
lockScreenStateService,
scope,
)
}
}