Create SetMessagePasswordActivity.kt
MAILAND-1672, MAILAND-1671
This commit is contained in:
parent
9b537b1e5e
commit
df320c0ce2
|
@ -26,13 +26,11 @@ import androidx.test.espresso.action.ViewActions.replaceText
|
|||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import androidx.test.espresso.matcher.ViewMatchers.assertThat
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.rules.activityScenarioRule
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import ch.protonmail.android.R
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import ch.protonmail.android.util.withTextInputEditTextId
|
||||
import org.hamcrest.core.AllOf
|
||||
import org.junit.Rule
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -164,19 +162,11 @@ class SetMessageExpirationActivityTest {
|
|||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun setCustomDaysAndHours(days: Int, hours: Int) {
|
||||
onView(
|
||||
AllOf.allOf(
|
||||
withId(R.id.days_and_hours_picker_days_input),
|
||||
withClassName(`is`(TextInputEditText::class.qualifiedName))
|
||||
)
|
||||
).perform(replaceText(days.toString()))
|
||||
onView(withTextInputEditTextId(R.id.days_and_hours_picker_days_input))
|
||||
.perform(replaceText(days.toString()))
|
||||
|
||||
onView(
|
||||
AllOf.allOf(
|
||||
withId(R.id.days_and_hours_picker_hours_input),
|
||||
withClassName(`is`(TextInputEditText::class.qualifiedName))
|
||||
)
|
||||
).perform(replaceText(hours.toString()))
|
||||
onView(withTextInputEditTextId(R.id.days_and_hours_picker_hours_input))
|
||||
.perform(replaceText(hours.toString()))
|
||||
}
|
||||
|
||||
private fun performSetClick(): ViewInteraction =
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import ch.protonmail.android.R
|
||||
import ch.protonmail.android.util.withTextInputEditTextId
|
||||
import org.hamcrest.core.AllOf.allOf
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
|
||||
/**
|
||||
* Test suite for [SetMessagePasswordActivity]
|
||||
*/
|
||||
@RunWith(AndroidJUnit4ClassRunner::class)
|
||||
@Suppress("SameParameterValue")
|
||||
class SetMessagePasswordActivityTest {
|
||||
|
||||
private val baseIntent =
|
||||
Intent(ApplicationProvider.getApplicationContext(), SetMessagePasswordActivity::class.java)
|
||||
|
||||
@BeforeTest
|
||||
fun setup() {
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inputIsSetCorrectly() {
|
||||
|
||||
// given
|
||||
val expectedPassword = "12345"
|
||||
val expectedHint = "hint"
|
||||
|
||||
// when
|
||||
val intent = baseIntent
|
||||
.putExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD, expectedPassword)
|
||||
.putExtra(ARG_SET_MESSAGE_PASSWORD_HINT, expectedHint)
|
||||
ActivityScenario.launch<SetMessagePasswordActivity>(intent)
|
||||
|
||||
// then
|
||||
onPasswordView().check(matches(withText(expectedPassword)))
|
||||
onRepeatView().check(matches(withText(expectedPassword)))
|
||||
onHintView().check(matches(withText(expectedHint)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resultIsSetCorrectly() {
|
||||
|
||||
// given
|
||||
val expectedPassword = "12345"
|
||||
val expectedHint = "hint"
|
||||
|
||||
// when
|
||||
val scenario = ActivityScenario.launch<SetMessagePasswordActivity>(baseIntent)
|
||||
setPassword(expectedPassword)
|
||||
setHint(expectedHint)
|
||||
performApplyClick()
|
||||
|
||||
// then
|
||||
assertResult(scenario, expectedPassword, expectedHint)
|
||||
}
|
||||
|
||||
private fun onPasswordView(): ViewInteraction =
|
||||
onView(withTextInputEditTextId(R.id.set_msg_password_msg_password_input))
|
||||
|
||||
private fun onRepeatView(): ViewInteraction =
|
||||
onView(withTextInputEditTextId(R.id.set_msg_password_repeat_password_input))
|
||||
|
||||
private fun onHintView(): ViewInteraction =
|
||||
onView(withTextInputEditTextId(R.id.set_msg_password_hint_input))
|
||||
|
||||
private fun setPassword(password: String) {
|
||||
onPasswordView().perform(replaceText(password))
|
||||
onRepeatView().perform(replaceText(password))
|
||||
}
|
||||
|
||||
private fun setHint(hint: String) {
|
||||
onHintView().perform(replaceText(hint))
|
||||
}
|
||||
|
||||
private fun performApplyClick() {
|
||||
onView(withId(R.id.set_msg_password_apply_button)).perform(click())
|
||||
}
|
||||
|
||||
private fun assertResult(
|
||||
scenario: ActivityScenario<SetMessagePasswordActivity>,
|
||||
expectedPassword: String,
|
||||
expectedHint: String
|
||||
) {
|
||||
val resultIntent = scenario.result.resultData
|
||||
ViewMatchers.assertThat(
|
||||
resultIntent,
|
||||
allOf(
|
||||
hasExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD, expectedPassword),
|
||||
hasExtra(ARG_SET_MESSAGE_PASSWORD_HINT, expectedHint)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.util
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
import org.hamcrest.core.AllOf.allOf
|
||||
|
||||
/**
|
||||
* Matches a view with given [id], which is a [TextInputEditText]
|
||||
* @see withId
|
||||
*/
|
||||
fun withTextInputEditTextId(id: Int): Matcher<View> =
|
||||
allOf(
|
||||
withId(id),
|
||||
ViewMatchers.withClassName(Matchers.`is`(TextInputEditText::class.qualifiedName))
|
||||
)
|
|
@ -213,6 +213,7 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".compose.presentation.ui.SetMessageExpirationActivity"/>
|
||||
<activity android:name=".compose.presentation.ui.SetMessagePasswordActivity"/>
|
||||
<activity
|
||||
android:name=".activities.SearchActivity"
|
||||
android:exported="false"
|
||||
|
|
|
@ -33,6 +33,11 @@ sealed class ComposeMessageEventUiModel {
|
|||
*/
|
||||
data class OnAttachmentsChange(val attachments: List<ComposerAttachmentUiModel>) : ComposeMessageEventUiModel()
|
||||
|
||||
/**
|
||||
* Password change has been requested and previous password is ready
|
||||
*/
|
||||
data class OnPasswordChangeRequest(val currentPassword: MessagePasswordUiModel) : ComposeMessageEventUiModel()
|
||||
|
||||
/**
|
||||
* Expiration change has been requested and previous expiration is ready
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.model
|
||||
|
||||
sealed class MessagePasswordUiModel {
|
||||
|
||||
data class Set(
|
||||
val password: String,
|
||||
val hint: String?
|
||||
) : MessagePasswordUiModel()
|
||||
|
||||
object Unset : MessagePasswordUiModel()
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.model
|
||||
|
||||
import ch.protonmail.android.compose.presentation.ui.SetMessagePasswordActivity
|
||||
import me.proton.core.util.kotlin.EMPTY_STRING
|
||||
import me.proton.core.util.kotlin.any
|
||||
|
||||
/**
|
||||
* Ui model for [SetMessagePasswordActivity]
|
||||
*/
|
||||
data class SetMessagePasswordUiModel(
|
||||
val passwordInput: Input,
|
||||
val repeatInput: Input,
|
||||
val messagePassword: MessagePasswordUiModel,
|
||||
val hasErrors: Boolean = any(passwordInput, repeatInput) { it.error != null }
|
||||
) {
|
||||
|
||||
data class Input(
|
||||
val text: String,
|
||||
val error: Error?
|
||||
)
|
||||
|
||||
sealed class Error {
|
||||
object Empty : Error()
|
||||
object TooShort : Error()
|
||||
object TooLong : Error()
|
||||
object DoesNotMatch : Error()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val Empty = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(EMPTY_STRING, Error.Empty),
|
||||
repeatInput = Input(EMPTY_STRING, Error.Empty),
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
}
|
||||
}
|
|
@ -30,8 +30,12 @@ import ch.protonmail.android.activities.BaseContactsActivity
|
|||
import ch.protonmail.android.attachments.domain.model.UriPair
|
||||
import ch.protonmail.android.attachments.presentation.model.FilePickerMask
|
||||
import ch.protonmail.android.compose.ComposeMessageViewModel
|
||||
import ch.protonmail.android.compose.presentation.model.ComposeMessageEventUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.ComposeMessageEventUiModel.OnAttachmentsChange
|
||||
import ch.protonmail.android.compose.presentation.model.ComposeMessageEventUiModel.OnExpirationChangeRequest
|
||||
import ch.protonmail.android.compose.presentation.model.ComposeMessageEventUiModel.OnPasswordChangeRequest
|
||||
import ch.protonmail.android.compose.presentation.model.ComposeMessageEventUiModel.OnPhotoUriReady
|
||||
import ch.protonmail.android.compose.presentation.model.ComposerAttachmentUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.MessagePasswordUiModel
|
||||
import ch.protonmail.android.databinding.ActivityComposeMessage2Binding
|
||||
import ch.protonmail.android.ui.actionsheet.AddAttachmentsActionSheet
|
||||
import ch.protonmail.android.ui.actionsheet.AddAttachmentsActionSheet.Action
|
||||
|
@ -52,6 +56,13 @@ abstract class ComposeMessageKotlinActivity : BaseContactsActivity() {
|
|||
protected lateinit var binding: ActivityComposeMessage2Binding
|
||||
|
||||
// region activity results
|
||||
// region password
|
||||
private val setPasswordLauncher =
|
||||
registerForActivityResult(SetMessagePasswordActivity.ResultContract()) { messagePassword ->
|
||||
// TODO set message password
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region expiration
|
||||
private val setExpirationLauncher =
|
||||
registerForActivityResult(SetMessageExpirationActivity.ResultContract()) { daysHoursPair ->
|
||||
|
@ -88,6 +99,9 @@ abstract class ComposeMessageKotlinActivity : BaseContactsActivity() {
|
|||
|
||||
// region setup UI
|
||||
binding.composerBottomAppBar.apply {
|
||||
onPasswordClick {
|
||||
// TODO ask current expiration to ViewModel
|
||||
}
|
||||
onExpirationClick {
|
||||
// TODO ask current expiration to ViewModel
|
||||
}
|
||||
|
@ -101,12 +115,10 @@ abstract class ComposeMessageKotlinActivity : BaseContactsActivity() {
|
|||
composeViewModel.events
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
is ComposeMessageEventUiModel.OnAttachmentsChange ->
|
||||
onAttachmentsChanged(event.attachments)
|
||||
is ComposeMessageEventUiModel.OnExpirationChangeRequest ->
|
||||
openSetExpiration(event.currentExpiration)
|
||||
is ComposeMessageEventUiModel.OnPhotoUriReady ->
|
||||
takePhotoFromCamera(event.uri)
|
||||
is OnAttachmentsChange -> onAttachmentsChanged(event.attachments)
|
||||
is OnPasswordChangeRequest -> openSetPassword(event.currentPassword)
|
||||
is OnExpirationChangeRequest -> openSetExpiration(event.currentExpiration)
|
||||
is OnPhotoUriReady -> takePhotoFromCamera(event.uri)
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
|
@ -121,6 +133,12 @@ abstract class ComposeMessageKotlinActivity : BaseContactsActivity() {
|
|||
// endregion
|
||||
}
|
||||
|
||||
// region password
|
||||
private fun openSetPassword(currentPassword: MessagePasswordUiModel) {
|
||||
setPasswordLauncher.launch(currentPassword)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region expiration
|
||||
private fun openSetExpiration(currentExpiration: DaysHoursPair) {
|
||||
setExpirationLauncher.launch(currentExpiration)
|
||||
|
|
|
@ -31,6 +31,7 @@ import androidx.activity.result.contract.ActivityResultContract
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import ch.protonmail.android.R
|
||||
import ch.protonmail.android.compose.presentation.ui.SetMessageExpirationActivity.Expiration.Custom
|
||||
import ch.protonmail.android.compose.presentation.ui.SetMessageExpirationActivity.Expiration.None
|
||||
|
@ -40,6 +41,8 @@ import ch.protonmail.android.compose.presentation.ui.SetMessageExpirationActivit
|
|||
import ch.protonmail.android.compose.presentation.ui.SetMessageExpirationActivity.Expiration.ThreeDays
|
||||
import ch.protonmail.android.databinding.ActivitySetMessageExpirationBinding
|
||||
import ch.protonmail.android.ui.view.DaysHoursPair
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -88,6 +91,9 @@ class SetMessageExpirationActivity : AppCompatActivity() {
|
|||
threeDaysEntry.first.onClick { setExpiration(ThreeDays) }
|
||||
oneWeekEntry.first.onClick { setExpiration(OneWeek) }
|
||||
customEntry.first.onClick { setExpiration(Custom(0, 0)) }
|
||||
binding.setMsgExpirationPickerView.onChange
|
||||
.onEach { setExpiration(Custom(it.days, it.hours)) }
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import ch.protonmail.android.R
|
||||
import ch.protonmail.android.compose.presentation.model.MessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.viewmodel.SetMessagePasswordViewModel
|
||||
import ch.protonmail.android.databinding.ActivitySetMessagePasswordBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.presentation.ui.view.ProtonInput
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
import me.proton.core.presentation.utils.onTextChange
|
||||
import timber.log.Timber
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
const val ARG_SET_MESSAGE_PASSWORD_PASSWORD = "arg.password"
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
const val ARG_SET_MESSAGE_PASSWORD_HINT = "arg.hint"
|
||||
|
||||
/**
|
||||
* Activity for set a password for a given Message
|
||||
* @see ComposeMessageKotlinActivity
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class SetMessagePasswordActivity : AppCompatActivity() {
|
||||
|
||||
private val viewModel: SetMessagePasswordViewModel by viewModels()
|
||||
private val binding by lazy {
|
||||
ActivitySetMessagePasswordBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
private lateinit var messagePassword: MessagePasswordUiModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setContentView(binding.root)
|
||||
super.onCreate(savedInstanceState)
|
||||
setSupportActionBar(binding.setMsgPasswordToolbar)
|
||||
|
||||
val password = intent.getStringExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD)
|
||||
val hint = intent.getStringExtra(ARG_SET_MESSAGE_PASSWORD_HINT)
|
||||
viewModel.validate(password, password, hint)
|
||||
|
||||
with(binding) {
|
||||
listOf(
|
||||
setMsgPasswordMsgPasswordInput,
|
||||
setMsgPasswordRepeatPasswordInput,
|
||||
setMsgPasswordHintInput
|
||||
).forEach {
|
||||
it.onTextChange { validateInput() }
|
||||
}
|
||||
setMsgPasswordApplyButton.onClick {
|
||||
setResultAndFinish()
|
||||
}
|
||||
}
|
||||
viewModel.result
|
||||
.flowWithLifecycle(lifecycle)
|
||||
.onEach(::updateUi)
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
}
|
||||
|
||||
private fun updateUi(model: SetMessagePasswordUiModel) {
|
||||
messagePassword = model.messagePassword
|
||||
setPasswordInput(model.passwordInput)
|
||||
setRepeatInput(model.repeatInput)
|
||||
setHintInput((messagePassword as? MessagePasswordUiModel.Set)?.hint)
|
||||
setApplyButton(model.hasErrors)
|
||||
}
|
||||
|
||||
private fun setPasswordInput(input: SetMessagePasswordUiModel.Input) {
|
||||
binding.setMsgPasswordMsgPasswordInput.apply {
|
||||
setTextIfChanged(input.text)
|
||||
getErrorString(input.error)
|
||||
?.let(::setInputError)
|
||||
?: clearInputError()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRepeatInput(input: SetMessagePasswordUiModel.Input) {
|
||||
binding.setMsgPasswordRepeatPasswordInput.apply {
|
||||
setTextIfChanged(input.text)
|
||||
getErrorString(input.error)
|
||||
?.let(::setInputError)
|
||||
?: clearInputError()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setHintInput(hint: String?) {
|
||||
binding.setMsgPasswordHintInput.setTextIfChanged(hint)
|
||||
}
|
||||
|
||||
private fun setApplyButton(hasErrors: Boolean) {
|
||||
binding.setMsgPasswordApplyButton.isEnabled = hasErrors.not()
|
||||
}
|
||||
|
||||
private fun setResultAndFinish() {
|
||||
Timber.v("Set password $messagePassword")
|
||||
val messagePassword = messagePassword
|
||||
val resultIntent = Intent().apply {
|
||||
if (messagePassword is MessagePasswordUiModel.Set) {
|
||||
putExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD, messagePassword.password)
|
||||
putExtra(ARG_SET_MESSAGE_PASSWORD_HINT, messagePassword.hint)
|
||||
}
|
||||
}
|
||||
setResult(Activity.RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun getErrorString(error: SetMessagePasswordUiModel.Error?): String? {
|
||||
return when (error) {
|
||||
SetMessagePasswordUiModel.Error.TooShort -> getString(R.string.set_msg_password_error_too_short)
|
||||
SetMessagePasswordUiModel.Error.TooLong -> getString(R.string.set_msg_password_error_too_long)
|
||||
SetMessagePasswordUiModel.Error.DoesNotMatch -> getString(R.string.set_msg_password_error_mismatch)
|
||||
SetMessagePasswordUiModel.Error.Empty, null -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateInput() {
|
||||
with(binding) {
|
||||
val password = setMsgPasswordMsgPasswordInput.text
|
||||
val repeat = setMsgPasswordRepeatPasswordInput.text
|
||||
val hint = setMsgPasswordHintInput.text
|
||||
viewModel.validate(password, repeat, hint)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ProtonInput.setTextIfChanged(charSequence: CharSequence?) {
|
||||
if (text.toString() != charSequence.toString()) {
|
||||
text = charSequence
|
||||
}
|
||||
}
|
||||
|
||||
class ResultContract : ActivityResultContract<MessagePasswordUiModel, MessagePasswordUiModel>() {
|
||||
|
||||
private lateinit var input: MessagePasswordUiModel
|
||||
|
||||
override fun createIntent(context: Context, input: MessagePasswordUiModel): Intent {
|
||||
this.input = input
|
||||
return Intent(context, SetMessagePasswordActivity::class.java).apply {
|
||||
if (input is MessagePasswordUiModel.Set) {
|
||||
putExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD, input.password)
|
||||
putExtra(ARG_SET_MESSAGE_PASSWORD_HINT, input.hint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): MessagePasswordUiModel {
|
||||
return intent?.let {
|
||||
val password = intent.getStringExtra(ARG_SET_MESSAGE_PASSWORD_PASSWORD)
|
||||
val hint = intent.getStringExtra(ARG_SET_MESSAGE_PASSWORD_HINT)
|
||||
if (password != null) {
|
||||
MessagePasswordUiModel.Set(password, hint)
|
||||
} else {
|
||||
MessagePasswordUiModel.Unset
|
||||
}
|
||||
} ?: input
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.viewmodel
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import ch.protonmail.android.compose.presentation.model.MessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel.Error
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel.Input
|
||||
import ch.protonmail.android.compose.presentation.ui.SetMessagePasswordActivity
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.util.kotlin.DispatcherProvider
|
||||
import me.proton.core.util.kotlin.EMPTY_STRING
|
||||
import javax.inject.Inject
|
||||
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
const val MESSAGE_PASSWORD_MIN_LENGTH = 4
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
const val MESSAGE_PASSWORD_MAX_LENGTH = 21
|
||||
|
||||
/**
|
||||
* ViewModel for [SetMessagePasswordActivity]
|
||||
*/
|
||||
@HiltViewModel
|
||||
class SetMessagePasswordViewModel @Inject constructor(
|
||||
private val dispatchers: DispatcherProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val _result: MutableStateFlow<SetMessagePasswordUiModel> =
|
||||
MutableStateFlow(SetMessagePasswordUiModel.Empty)
|
||||
|
||||
val result: StateFlow<SetMessagePasswordUiModel> =
|
||||
_result.asStateFlow()
|
||||
|
||||
fun validate(password: CharSequence?, repeat: CharSequence?, hint: CharSequence?) {
|
||||
viewModelScope.launch(dispatchers.Comp) {
|
||||
|
||||
val passwordInput = validatePassword(password)
|
||||
val repeatInput = validateRepeatPassword(password, repeat)
|
||||
val hasErrors = passwordInput.error ?: repeatInput.error != null
|
||||
val passwordUiModel = if (hasErrors) {
|
||||
MessagePasswordUiModel.Unset
|
||||
} else {
|
||||
MessagePasswordUiModel.Set(
|
||||
checkNotNull(password).toString(),
|
||||
hint.toString()
|
||||
)
|
||||
}
|
||||
_result.emit(SetMessagePasswordUiModel(passwordInput, repeatInput, passwordUiModel))
|
||||
}
|
||||
}
|
||||
|
||||
private fun validatePassword(password: CharSequence?): Input {
|
||||
val error = when {
|
||||
password.isNullOrBlank() -> Error.Empty
|
||||
password.length < MESSAGE_PASSWORD_MIN_LENGTH -> Error.TooShort
|
||||
password.length > MESSAGE_PASSWORD_MAX_LENGTH -> Error.TooLong
|
||||
else -> null
|
||||
}
|
||||
return Input(password?.toString() ?: EMPTY_STRING, error)
|
||||
}
|
||||
|
||||
private fun validateRepeatPassword(
|
||||
password: CharSequence?,
|
||||
repeat: CharSequence?
|
||||
): Input {
|
||||
val error = when {
|
||||
repeat.isNullOrBlank() -> Error.Empty
|
||||
repeat.toString() != password.toString() -> Error.DoesNotMatch
|
||||
else -> null
|
||||
}
|
||||
return Input(repeat?.toString() ?: EMPTY_STRING, error)
|
||||
}
|
||||
}
|
|
@ -69,13 +69,15 @@ class DaysAndHoursPickerView @JvmOverloads constructor(
|
|||
|
||||
daysInput.onTextChange { text ->
|
||||
val hours = hoursInput.text?.toString()?.toIntOrNull()
|
||||
if (normaliseDays(text) == HasChanged.False && hours != null)
|
||||
if (normaliseDays(text) == HasChanged.False && hours != null) {
|
||||
changesBuffer.offer(DaysHoursPair(text.toString().toInt(), hours))
|
||||
}
|
||||
}
|
||||
hoursInput.onTextChange { text ->
|
||||
val days = daysInput.text?.toString()?.toIntOrNull()
|
||||
if (normaliseHours(text) == HasChanged.False && days != null)
|
||||
if (normaliseHours(text) == HasChanged.False && days != null) {
|
||||
changesBuffer.offer(DaysHoursPair(days, text.toString().toInt()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
~ Copyright (c) 2020 Proton Technologies AG
|
||||
~
|
||||
~ This file is part of ProtonMail.
|
||||
~
|
||||
~ ProtonMail is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ ProtonMail is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12.75,16.5V9H9.75V10.5H11.25V16.5H9.75V18H14.25V16.5H12.75Z"
|
||||
android:fillColor="@color/icon_norm"/>
|
||||
<path
|
||||
android:pathData="M12.75,7.5V6H11.25V7.5H12.75Z"
|
||||
android:fillColor="@color/icon_norm"/>
|
||||
<path
|
||||
android:pathData="M1.5,12C1.5,6.201 6.201,1.5 12,1.5C14.7848,1.5 17.4555,2.6063 19.4246,4.5754C21.3938,6.5445 22.5,9.2152 22.5,12C22.5,17.799 17.799,22.5 12,22.5C6.201,22.5 1.5,17.799 1.5,12ZM3,12C3,16.9706 7.0294,21 12,21C14.3869,21 16.6761,20.0518 18.364,18.364C20.0518,16.6761 21,14.3869 21,12C21,7.0294 16.9706,3 12,3C7.0294,3 3,7.0294 3,12Z"
|
||||
android:fillColor="@color/icon_norm"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Proton Technologies AG
|
||||
~
|
||||
~ This file is part of ProtonMail.
|
||||
~
|
||||
~ ProtonMail is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ ProtonMail is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Toolbar -->
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/set_msg_password_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/set_msg_password_title" />
|
||||
|
||||
<!-- Body -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/padding_l"
|
||||
android:paddingVertical="@dimen/padding_xl">
|
||||
|
||||
<!-- Info -->
|
||||
<ImageView
|
||||
android:id="@+id/set_msg_password_info_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_xs"
|
||||
android:contentDescription="@string/set_msg_password_info_icon_description"
|
||||
android:src="@drawable/ic_info_circle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/set_msg_password_info_text_view"
|
||||
app:tint="@color/shade_80" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/set_msg_password_info_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/padding_m"
|
||||
android:autoLink="web"
|
||||
android:text="@string/set_msg_password_info"
|
||||
android:textAppearance="@style/Proton.Text.DefaultSmall.Weak"
|
||||
android:textColor="@color/text_weak"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/set_msg_password_info_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Message password -->
|
||||
<me.proton.core.presentation.ui.view.ProtonInput
|
||||
android:id="@+id/set_msg_password_msg_password_input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_xl"
|
||||
android:hint="@string/set_msg_password_msg_password_hint"
|
||||
android:inputType="textPassword"
|
||||
app:actionMode="password_toggle"
|
||||
app:label="@string/set_msg_password_msg_password_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/set_msg_password_info_text_view" />
|
||||
|
||||
<!-- Repeat password -->
|
||||
<me.proton.core.presentation.ui.view.ProtonInput
|
||||
android:id="@+id/set_msg_password_repeat_password_input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_m"
|
||||
android:hint="@string/set_msg_password_repeat_password_hint"
|
||||
android:inputType="textPassword"
|
||||
app:actionMode="password_toggle"
|
||||
app:label="@string/set_msg_password_repeat_password_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/set_msg_password_msg_password_input" />
|
||||
|
||||
<!-- Hint -->
|
||||
<me.proton.core.presentation.ui.view.ProtonInput
|
||||
android:id="@+id/set_msg_password_hint_input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_m"
|
||||
android:hint="@string/set_msg_password_password_hint_hint"
|
||||
android:inputType="text"
|
||||
app:actionMode="clear_text"
|
||||
app:label="@string/set_msg_password_password_hint_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/set_msg_password_repeat_password_input" />
|
||||
|
||||
<!-- Apply -->
|
||||
<me.proton.core.presentation.ui.view.ProtonButton
|
||||
android:id="@+id/set_msg_password_apply_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/set_msg_password_hint_input"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="@dimen/padding_m"
|
||||
android:text="@string/set_msg_password_apply_password"
|
||||
android:enabled="false"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
|
@ -58,6 +58,9 @@
|
|||
<string name="set_msg_password_password_hint_title">Password hint</string>
|
||||
<string name="set_msg_password_password_hint_hint">Define hint (Optional)</string>
|
||||
<string name="set_msg_password_apply_password">Apply password</string>
|
||||
<string name="set_msg_password_error_too_short">Password too short</string>
|
||||
<string name="set_msg_password_error_too_long">Password too long</string>
|
||||
<string name="set_msg_password_error_mismatch">Password does not match</string>
|
||||
|
||||
<string name="set_msg_expiration_title">Message expiration</string>
|
||||
<string name="set_msg_expiration_none">None</string>
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonMail is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.compose.presentation.viewmodel
|
||||
|
||||
import ch.protonmail.android.compose.presentation.model.MessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel.Error
|
||||
import ch.protonmail.android.compose.presentation.model.SetMessagePasswordUiModel.Input
|
||||
import kotlinx.coroutines.flow.first
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import me.proton.core.util.kotlin.EMPTY_STRING
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Test suite for [SetMessagePasswordViewModel]
|
||||
*/
|
||||
class SetMessagePasswordViewModelTest : CoroutinesTest {
|
||||
|
||||
private val viewModel = SetMessagePasswordViewModel(dispatchers)
|
||||
private val testHint = "hint"
|
||||
|
||||
@Test
|
||||
fun emptyPasswordAndEmptyRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = EMPTY_STRING
|
||||
val repeat = EMPTY_STRING
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.Empty),
|
||||
repeatInput = Input(repeat, Error.Empty),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyPasswordAnd5CharsRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = EMPTY_STRING
|
||||
val repeat = "hello"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.Empty),
|
||||
repeatInput = Input(repeat, Error.DoesNotMatch),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortPasswordAndEmptyRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = "123"
|
||||
val repeat = EMPTY_STRING
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.TooShort),
|
||||
repeatInput = Input(repeat, Error.Empty),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortPasswordAndNotMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = "123"
|
||||
val repeat = "1234"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.TooShort),
|
||||
repeatInput = Input(repeat, Error.DoesNotMatch),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortPasswordAndMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = "123"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.TooShort),
|
||||
repeatInput = Input(password, null),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = password,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun longPasswordAndNotMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = (0..50).joinToString { it.toString() }
|
||||
val repeat = "1234"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.TooLong),
|
||||
repeatInput = Input(repeat, Error.DoesNotMatch),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun longPasswordAndMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = (0..50).joinToString { it.toString() }
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, Error.TooLong),
|
||||
repeatInput = Input(password, null),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = password,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun okPasswordAndNotMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = "12345"
|
||||
val repeat = "1234"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, null),
|
||||
repeatInput = Input(repeat, Error.DoesNotMatch),
|
||||
hasErrors = true,
|
||||
messagePassword = MessagePasswordUiModel.Unset
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = repeat,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun okPasswordAndMatchRepeat() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val password = "12345"
|
||||
val expected = SetMessagePasswordUiModel(
|
||||
passwordInput = Input(password, null),
|
||||
repeatInput = Input(password, null),
|
||||
hasErrors = false,
|
||||
messagePassword = MessagePasswordUiModel.Set(password, testHint)
|
||||
)
|
||||
|
||||
// when
|
||||
viewModel.validate(
|
||||
password = password,
|
||||
repeat = password,
|
||||
hint = testHint
|
||||
)
|
||||
val result = viewModel.result.first()
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue