Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ import org.hamcrest.Matcher
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.emptyString
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.isEmptyString
import org.hamcrest.Matchers.not
import org.junit.Assert
import org.junit.BeforeClass
Expand Down Expand Up @@ -123,7 +123,7 @@ class CreateMessageActivityTest : BaseCreateMessageActivityTest() {
activeActivityRule?.launch(intent)
registerAllIdlingResources()
onView(withId(R.id.editTextRecipientTo))
.check(matches(withText(isEmptyString())))
.check(matches(withText(`is`(emptyString()))))
onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())
Expand All @@ -146,7 +146,7 @@ class CreateMessageActivityTest : BaseCreateMessageActivityTest() {
.perform(typeText(TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER))
onView(withId(R.id.editTextEmailSubject))
.perform(scrollTo(), click(), typeText("subject"), clearText())
.check(matches(withText(isEmptyString())))
.check(matches(withText(`is`(emptyString()))))
onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())
Expand Down Expand Up @@ -175,7 +175,7 @@ class CreateMessageActivityTest : BaseCreateMessageActivityTest() {
.perform(scrollTo(), click(), typeText(EMAIL_SUBJECT))
onView(withId(R.id.editTextEmailMessage))
.perform(scrollTo())
.check(matches(withText(isEmptyString())))
.check(matches(withText(`is`(emptyString()))))
onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())
Expand Down Expand Up @@ -250,14 +250,14 @@ class CreateMessageActivityTest : BaseCreateMessageActivityTest() {
.check(matches(isDisplayed()))
onView(withId(R.id.editTextFrom))
.perform(scrollTo())
.check(matches(withText(not(isEmptyString()))))
.check(matches(withText(not(`is`(emptyString())))))
onView(withId(R.id.layoutTo))
.perform(scrollTo())
onView(withId(R.id.editTextRecipientTo))
.check(matches(withText(isEmptyString())))
.check(matches(withText(`is`(emptyString()))))
onView(withId(R.id.editTextEmailSubject))
.perform(scrollTo())
.check(matches(withText(isEmptyString())))
.check(matches(withText(`is`(emptyString()))))
}

@Test
Expand Down Expand Up @@ -715,7 +715,7 @@ class CreateMessageActivityTest : BaseCreateMessageActivityTest() {

private fun checkIsDisplayedEncryptedAttributes() {
onView(withId(R.id.underToolbarTextTextView))
.check(doesNotExist())
.check(matches(not(isDisplayed())))
onView(withId(R.id.appBarLayout))
.check(
matches(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.flowcrypt.email.ui.activity

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
Expand Down Expand Up @@ -107,4 +108,17 @@ class CreateMessageFragmentPasswordProtectedTest : BaseCreateMessageActivityTest
onView(withId(R.id.btnSetWebPortalPassword))
.check(matches(not(isDisplayed())))
}

@Test
fun testHideWebPortalPasswordButtonWhenUseStandardMsgType() {
testShowWebPortalPasswordButton()

openActionBarOverflowOrOptionsMenu(getTargetContext())
onView(withText(R.string.switch_to_standard_email))
.check(matches(isDisplayed()))
.perform(click())

onView(withId(R.id.btnSetWebPortalPassword))
.check(matches(not(isDisplayed())))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.ui.activity.fragment

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasTextColor
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.flowcrypt.email.R
import com.flowcrypt.email.base.BaseTest
import com.flowcrypt.email.rules.AddAccountToDatabaseRule
import com.flowcrypt.email.rules.ClearAppSettingsRule
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import com.flowcrypt.email.ui.activity.settings.SettingsActivity
import com.flowcrypt.email.util.TestGeneralUtil
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith

/**
* @author Denis Bondarenko
* Date: 1/22/22
* Time: 6:32 PM
* E-mail: DenBond7@gmail.com
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
class ProvidePasswordToProtectMsgFragmentTest : BaseTest() {
override val activityScenarioRule = activityScenarioRule<SettingsActivity>(
TestGeneralUtil.genIntentForNavigationComponent(
uri = "flowcrypt://email.flowcrypt.com/compose/web-portal-password"
)
)
private val accountRule = AddAccountToDatabaseRule()

@get:Rule
var ruleChain: TestRule = RuleChain
.outerRule(RetryRule.DEFAULT)
.around(ClearAppSettingsRule())
.around(accountRule)
.around(activityScenarioRule)
.around(ScreenshotTestRule())

@Test
fun testPasswordStrength() {
onView(withId(R.id.btSetPassword))
.check(matches(not(isEnabled())))

//type one uppercase
checkConditionItemState(R.id.checkedTVOneUppercase, false)
onView(withId(R.id.eTPassphrase))
.perform(
typeText("A"),
closeSoftKeyboard()
)
checkConditionItemState(R.id.checkedTVOneUppercase, true)

//type one lowercase
checkConditionItemState(R.id.checkedTVOneLowercase, false)
onView(withId(R.id.eTPassphrase))
.perform(
typeText("a"),
closeSoftKeyboard()
)
checkConditionItemState(R.id.checkedTVOneLowercase, true)

//type one number
checkConditionItemState(R.id.checkedTVOneNumber, false)
onView(withId(R.id.eTPassphrase))
.perform(
typeText("1"),
closeSoftKeyboard()
)
checkConditionItemState(R.id.checkedTVOneNumber, true)

//type one special character
checkConditionItemState(R.id.checkedTVOneSpecialCharacter, false)
onView(withId(R.id.eTPassphrase))
.perform(
typeText("@"),
closeSoftKeyboard()
)
checkConditionItemState(R.id.checkedTVOneSpecialCharacter, true)

//type one special character
checkConditionItemState(R.id.checkedTVMinLength, false)
onView(withId(R.id.eTPassphrase))
.perform(
typeText("more than 8 symbols"),
closeSoftKeyboard()
)
checkConditionItemState(R.id.checkedTVMinLength, true)

//check that button is enabled
onView(withId(R.id.btSetPassword))
.check(matches(isEnabled()))
}

private fun checkConditionItemState(id: Int, isChecked: Boolean) {
onView(withId(id))
.check(
matches(
allOf(
if (isChecked) isChecked() else isNotChecked(),
hasTextColor(if (isChecked) R.color.colorPrimary else R.color.orange),
)
)
)
}
}
103 changes: 103 additions & 0 deletions FlowCrypt/src/devTest/res/navigation/create_msg_graph.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
~ Contributors: DenBond7
-->

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/create_msg_graph"
app:startDestination="@id/createMessageFragment">

<fragment
android:id="@+id/createMessageFragment"
android:name="com.flowcrypt.email.ui.activity.fragment.CreateMessageFragment"
android:label="CreateMessageFragment"
tools:layout="@layout/fragment_create_message">
<argument
android:name="encryptedByDefault"
android:defaultValue="true"
app:argType="boolean" />
<argument
android:name="serviceInfo"
android:defaultValue="@null"
app:argType="com.flowcrypt.email.api.email.model.ServiceInfo"
app:nullable="true" />
<argument
android:name="incomingMessageInfo"
android:defaultValue="@null"
app:argType="com.flowcrypt.email.api.email.model.IncomingMessageInfo"
app:nullable="true" />
<argument
android:name="messageType"
android:defaultValue="0"
app:argType="integer" />
<action
android:id="@+id/action_createMessageFragment_to_providePasswordToProtectMsgFragment"
app:destination="@id/providePasswordToProtectMsgFragment" />
</fragment>

<fragment
android:id="@+id/providePasswordToProtectMsgFragment"
android:name="com.flowcrypt.email.ui.activity.fragment.ProvidePasswordToProtectMsgFragment"
tools:layout="@layout/fragment_provide_password_to_protect_msg">

<deepLink
android:id="@+id/deepLink"
app:uri="flowcrypt://email.flowcrypt.com/compose/web-portal-password" />

<argument
android:name="defaultPassword"
android:defaultValue=""
app:argType="string" />
</fragment>

<dialog
android:id="@+id/infoDialogFragment"
android:name="com.flowcrypt.email.ui.activity.fragment.dialog.InfoDialogFragment"
android:label="InfoDialogFragment">
<argument
android:name="requestCode"
android:defaultValue="0"
app:argType="integer" />
<argument
android:name="dialogTitle"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="dialogMsg"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="buttonTitle"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="isCancelable"
android:defaultValue="true"
app:argType="boolean" />
<argument
android:name="useNavigationUp"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="hasHtml"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="useWebViewToRender"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="useLinkify"
android:defaultValue="false"
app:argType="boolean" />
</dialog>

<action
android:id="@+id/action_global_infoDialogFragment"
app:destination="@id/infoDialogFragment" />
</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class ComposeMsgViewModel(isCandidateToEncrypt: Boolean, application: Applicatio

data class Recipient(
val recipientType: Message.RecipientType,
val recipientWithPubKeys: RecipientWithPubKeys
val recipientWithPubKeys: RecipientWithPubKeys,
val creationTime: Long = System.currentTimeMillis()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.jetpack.viewmodel

import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.flowcrypt.email.util.coroutines.runners.ControlledRunner
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*

/**
* This [ViewModel] implementation can be used to check the web portal strength
*
* @author Denis Bondarenko
* Date: 1/21/22
* Time: 08:15 AM
* E-mail: DenBond7@gmail.com
*/
class WebPortalPasswordStrengthViewModel(application: Application) :
BaseAndroidViewModel(application) {
private val controlledRunnerForChecking = ControlledRunner<List<RequirementItem>>()

private val pwdStrengthResultMutableStateFlow: MutableStateFlow<List<RequirementItem>> =
MutableStateFlow(emptyList())
val pwdStrengthResultStateFlow: StateFlow<List<RequirementItem>> =
pwdStrengthResultMutableStateFlow.asStateFlow()

fun check(password: CharSequence) {
viewModelScope.launch {
pwdStrengthResultMutableStateFlow.value = controlledRunnerForChecking.cancelPreviousThenRun {
return@cancelPreviousThenRun checkInternal(password)
}
}
}

private suspend fun checkInternal(password: CharSequence): List<RequirementItem> =
withContext(Dispatchers.Default) {
return@withContext Requirement.values().map {
RequirementItem(it, it.regex.containsMatchIn(password))
}
}

data class RequirementItem(
val requirement: Requirement,
val isMatching: Boolean
)

enum class Requirement constructor(val regex: Regex) {
MIN_LENGTH(".{8,}".toRegex(RegexOption.MULTILINE)),
ONE_UPPERCASE("\\p{Lu}".toRegex(RegexOption.MULTILINE)),
ONE_LOWERCASE("\\p{Ll}".toRegex(RegexOption.MULTILINE)),
ONE_NUMBER("\\d".toRegex(RegexOption.MULTILINE)),
ONE_SPECIAL_CHARACTER("[&\"#-'_%-/@,;:!*()]".toRegex(RegexOption.MULTILINE));
}
}
Loading