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 number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.matchers

import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.view.View
import android.widget.ImageView
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

/**
* @author Denis Bondarenko
* Date: 8/9/22
* Time: 11:07 AM
* E-mail: DenBond7@gmail.com
*/
class BitmapMatcher(private val expectedBitmap: Bitmap) :
TypeSafeMatcher<View>(View::class.java) {

override fun describeTo(description: Description) {
description.appendText("with expected bitmap size: ")
description.appendValue(expectedBitmap.byteCount)
}

override fun matchesSafely(target: View): Boolean {
if (target !is ImageView) {
return false
}

val targetBitmap = (target.drawable as? BitmapDrawable)?.bitmap
return targetBitmap?.sameAs(expectedBitmap) == true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.flowcrypt.email.matchers

import android.content.Context
import android.graphics.Bitmap
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
Expand All @@ -29,17 +30,18 @@ import org.hamcrest.Matcher

class CustomMatchers {
companion object {
@JvmStatic
fun withDrawable(resourceId: Int): Matcher<View> {
return DrawableMatcher(resourceId)
}

@JvmStatic
fun withBitmap(bitmap: Bitmap): Matcher<View> {
return BitmapMatcher(bitmap)
}

fun emptyDrawable(): Matcher<View> {
return DrawableMatcher(DrawableMatcher.EMPTY)
}

@JvmStatic
fun withToolBarText(textMatcher: String): Matcher<View> {
return ToolBarTitleMatcher.withText(textMatcher)
}
Expand All @@ -49,7 +51,6 @@ class CustomMatchers {
*
* @param option An input {@link SecurityType.Option}.
*/
@JvmStatic
fun withSecurityTypeOption(option: SecurityType.Option): Matcher<SecurityType.Option> {
return SecurityTypeOptionMatcher(option)
}
Expand All @@ -60,23 +61,20 @@ class CustomMatchers {
* @param color An input color value.
* @return true if matched, otherwise false
*/
@JvmStatic
fun withAppBarLayoutBackgroundColor(color: Int): BoundedMatcher<View, AppBarLayout> {
return AppBarLayoutBackgroundColorMatcher(color)
}

/**
* Match is [androidx.recyclerview.widget.RecyclerView] empty.
*/
@JvmStatic
fun withEmptyRecyclerView(): BaseMatcher<View> {
return EmptyRecyclerViewMatcher()
}

/**
* Match is an items count of [RecyclerView] empty.
*/
@JvmStatic
fun withRecyclerViewItemCount(itemCount: Int): BaseMatcher<View> {
return RecyclerViewItemCountMatcher(itemCount)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.ui

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
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.TestConstants
import com.flowcrypt.email.api.retrofit.ApiHelper
import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse
import com.flowcrypt.email.api.retrofit.response.base.ApiError
import com.flowcrypt.email.model.Screenshot
import com.flowcrypt.email.rules.AddAccountToDatabaseRule
import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule
import com.flowcrypt.email.rules.ClearAppSettingsRule
import com.flowcrypt.email.rules.FlowCryptMockWebServerRule
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import com.flowcrypt.email.ui.activity.MainActivity
import com.flowcrypt.email.ui.activity.fragment.FeedbackFragmentArgs
import com.flowcrypt.email.ui.base.BaseFeedbackFragmentTest
import com.flowcrypt.email.util.TestGeneralUtil
import com.flowcrypt.email.util.exception.ApiException
import com.google.gson.Gson
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestName
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import java.net.HttpURLConnection
import java.util.UUID

/**
* @author Denis Bondarenko
* Date: 8/9/22
* Time: 11:29 AM
* E-mail: DenBond7@gmail.com
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
class SendFeedbackHasAccountFlowTest : BaseFeedbackFragmentTest() {
override val activityScenarioRule = activityScenarioRule<MainActivity>(
TestGeneralUtil.genIntentForNavigationComponent(
destinationId = R.id.feedbackFragment,
extras = FeedbackFragmentArgs(
screenshot = Screenshot(SCREENSHOT_BYTE_ARRAY)
).toBundle()
)
)

@get:Rule
val testNameRule = TestName()

private val mockWebServerRule = FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT,
object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val gson = ApiHelper.getInstance(getTargetContext()).gson

if (request.path?.startsWith("/help/feedback") == true) {
return handlePostFeedbackRequest(gson)
}

return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
})

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

@Test
fun testHandleApiErrorWhenSendingFeedback() {
onView(withId(R.id.editTextUserMessage))
.perform(typeText(UUID.randomUUID().toString()))

onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())

val exception = ApiException(API_ERROR)
val errorMsg = if (exception.message.isNullOrEmpty()) {
exception.javaClass.simpleName
} else exception.message

val dialogText = getResString(
R.string.send_feedback_failed_hint,
getResString(R.string.support_email),
errorMsg ?: ""
)

onView(withText(dialogText))
.check(matches(isDisplayed()))
}

@Test
fun testSendingFeedbackSuccess() {
onView(withId(R.id.editTextUserMessage))
.perform(typeText(UUID.randomUUID().toString()))

onView(withId(R.id.menuActionSend))
.check(matches(isDisplayed()))
.perform(click())

onView(withId(R.id.tVStatusMessage))
.check(doesNotExist())
}

@Test
fun testNavigateToImageEditor() {
onView(withId(R.id.checkBoxScreenshot))
.check(matches(isDisplayed()))
.perform(click())

onView(withId(R.id.imageButtonScreenshot))
.check(matches(isDisplayed()))
.perform(click())

onView(withId(R.id.photoEditorView))
.check(matches(isDisplayed()))
}

private fun handlePostFeedbackRequest(gson: Gson): MockResponse {
return when (testNameRule.methodName) {
"testHandleApiErrorWhenSendingFeedback" -> {
MockResponse()
.setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST)
.setBody(gson.toJson(PostHelpFeedbackResponse(apiError = API_ERROR)))
}

"testSendingFeedbackSuccess" -> {
MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(gson.toJson(PostHelpFeedbackResponse(isSent = true, text = "text")))
}

else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
}

companion object {
private val API_ERROR = ApiError(
code = HttpURLConnection.HTTP_BAD_REQUEST,
msg = "Wrong request received"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.ui.base

import android.util.Base64
import com.flowcrypt.email.base.BaseTest

/**
* @author Denis Bondarenko
* Date: 8/9/22
* Time: 11:24 AM
* E-mail: DenBond7@gmail.com
*/
abstract class BaseFeedbackFragmentTest : BaseTest() {
companion object {
private const val SCREENSHOT_BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAA8klEQVQ" +
"4y8WUsQrCMBCGv076BFLppiK02t2u0ocSEdqO+hIKPoegi7g7F0HxBdTZOBjSpNbYzdz23/+Ru+QS+McaspQR1" +
"gNmCBlJPWClgLXNNmZBAIw4K+BCBPjMicv2BjkCwZGnsgsET44IBCeaJjAxbFUx0+0uNyO5JyVlb2gPvALocNF" +
"SGQ4ADpmmXunpe7TYyMRB2t/IQapb3HLbfZlKDTWVqv95rL4VGJTtLltrSTuzpB7XGk13C8Dj8fNY77SrB+5bT" +
"M0empyso5HTKLcdM8cHIu0Sz4yAgAVj29yuFbCq9x6S6oH7vkL1RIf/+CFeP17HNVfX5IMAAAAASUVORK5CYII="

val SCREENSHOT_BYTE_ARRAY: ByteArray = Base64.decode(SCREENSHOT_BASE64, Base64.DEFAULT)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
*/

package com.flowcrypt.email.ui.fragment.isolation.incontainer

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.flowcrypt.email.R
import com.flowcrypt.email.model.Screenshot
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.fragment.FeedbackFragment
import com.flowcrypt.email.ui.activity.fragment.FeedbackFragmentArgs
import com.flowcrypt.email.ui.base.BaseFeedbackFragmentTest
import org.hamcrest.Matchers.not
import org.junit.Before
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: 8/9/22
* Time: 11:20 AM
* E-mail: DenBond7@gmail.com
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
class FeedbackFragmentHasAccountInIsolationTest : BaseFeedbackFragmentTest() {
@get:Rule
var ruleChain: TestRule = RuleChain
.outerRule(RetryRule.DEFAULT)
.around(ClearAppSettingsRule())
.around(AddAccountToDatabaseRule())
.around(ScreenshotTestRule())

@Before
fun launchFragmentInContainerWithPredefinedArgs() {
launchFragmentInContainer<FeedbackFragment>(
fragmentArgs = FeedbackFragmentArgs(
screenshot = Screenshot(SCREENSHOT_BYTE_ARRAY)
).toBundle()
)
}

@Test
fun testUserEmailVisibility() {
onView(withId(R.id.editTextUserEmail))
.check(matches(not(isDisplayed())))
}
}
Loading