Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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