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
Expand Up @@ -12,6 +12,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import android.text.Html
import android.view.View
import android.widget.Toast
Expand Down Expand Up @@ -71,6 +72,7 @@ import org.junit.Before
import org.junit.Rule
import java.io.InputStream
import java.util.Properties
import java.util.concurrent.TimeUnit

/**
* The base test implementation.
Expand All @@ -86,7 +88,7 @@ abstract class BaseTest : BaseActivityTestImplementation {
val flowCryptTestSettingsRule = FlowCryptTestSettingsRule()

private val useIntents
get() = flowCryptTestSettingsRule.flowCryptTestSettings?.useIntents ?: false
get() = flowCryptTestSettingsRule.flowCryptTestSettings?.useIntents == true

protected val decorView: View?
get() {
Expand Down Expand Up @@ -385,6 +387,24 @@ abstract class BaseTest : BaseActivityTestImplementation {
}
}

protected fun waitUntil(
timeoutInMilliseconds: Long = TimeUnit.SECONDS.toMillis(10),
predicate: () -> Boolean
) {
val startTime = SystemClock.uptimeMillis()
val interval = 100L

var elapsedTime: Long = 0
while (!predicate()) {
if (elapsedTime >= timeoutInMilliseconds) {
break
}

SystemClock.sleep(interval)
elapsedTime = SystemClock.uptimeMillis() - startTime
}
}

companion object{
const val NOTIFICATION_RESOURCES_NAME =
"com.android.systemui:id/expandableNotificationRow"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@

package com.flowcrypt.email.ui

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
Expand All @@ -23,119 +25,64 @@ import androidx.test.filters.MediumTest
import com.flowcrypt.email.R
import com.flowcrypt.email.TestConstants
import com.flowcrypt.email.jetpack.viewmodel.DraftViewModel
import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings
import com.flowcrypt.email.matchers.CustomMatchers.Companion.withRecyclerViewItemCount
import com.flowcrypt.email.rules.ClearAppSettingsRule
import com.flowcrypt.email.rules.FlowCryptMockWebServerRule
import com.flowcrypt.email.rules.GrantPermissionRuleChooser
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import com.flowcrypt.email.ui.DraftsGmailAPITestCorrectDeletingFlowTest.Companion.HISTORY_ID_FIRST
import com.flowcrypt.email.ui.base.BaseDraftsGmailAPIFlowTest
import com.google.api.client.json.Json
import com.flowcrypt.email.viewaction.ClickOnViewInRecyclerViewItem
import com.google.api.services.gmail.model.Message
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers
import org.hamcrest.core.AllOf.allOf
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit

/**
* https://github.com/FlowCrypt/flowcrypt-android/issues/2050
*/
@MediumTest
@FlowCryptTestSettings(useCommonIdling = false)
@RunWith(AndroidJUnit4::class)
@Ignore("Should be re-looked after threads will be completed")
class DraftsGmailAPITestCorrectCreatingAndUpdatingFlowTest : BaseDraftsGmailAPIFlowTest() {
override val mockWebServerRule =
FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
when {
return when {
request.method == "PUT" && request.path == "/gmail/v1/users/me/drafts/$DRAFT_ID_FIRST" -> {
val (draft, mimeMessage) = getDraftAndMimeMessageFromRequest(request)
val existingDraftInCache = draftsCache.firstOrNull { it.id == DRAFT_ID_FIRST }

return if (existingDraftInCache != null && mimeMessage.subject == MESSAGE_SUBJECT_FIRST_EDITED) {
val existingMessage = existingDraftInCache.message
existingDraftInCache.message = Message().apply {
id = existingMessage.id
threadId = existingMessage.threadId
labelIds = existingMessage.labelIds
raw = draft.message.raw
val existingDraftInCache = draftsCache[DRAFT_ID_FIRST]

if (existingDraftInCache != null && mimeMessage.subject == MESSAGE_SUBJECT_FIRST_EDITED) {
val updatedDraft = draft.clone().apply {
id = existingDraftInCache.id
message = Message().apply {
id = existingDraftInCache.message.id
threadId = existingDraftInCache.message.threadId
labelIds = existingDraftInCache.message.labelIds
raw = message.raw
}
}

draftsCache.put(DRAFT_ID_FIRST, updatedDraft)
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(existingDraftInCache.toString())
.setBody(updatedDraft.toString())
} else {
MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
}
}

request.method == "POST" && request.path == "/gmail/v1/users/me/drafts" -> {
val (draft, mimeMessage) = getDraftAndMimeMessageFromRequest(request)

return when (mimeMessage.subject) {
MESSAGE_SUBJECT_FIRST -> {
val newDraft = prepareDraft(
draftId = DRAFT_ID_FIRST,
messageId = MESSAGE_ID_FIRST,
messageThreadId = THREAD_ID_FIRST,
rawMsg = draft.message.raw
)
draftsCache.add(newDraft)

MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(newDraft.toString())
}

MESSAGE_SUBJECT_SECOND -> {
val newDraft = prepareDraft(
draftId = DRAFT_ID_SECOND,
messageId = MESSAGE_ID_SECOND,
messageThreadId = THREAD_ID_SECOND,
rawMsg = draft.message.raw
)
draftsCache.add(newDraft)

MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(newDraft.toString())
}

else -> {
MockResponse().setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST)
}
}
}

request.path == "/gmail/v1/users/me/messages/${MESSAGE_ID_FIRST}?fields=id,threadId,historyId&format=full" -> {
return genMsgDetailsMockResponse(MESSAGE_ID_FIRST, THREAD_ID_FIRST)
}

request.path == "/gmail/v1/users/me/messages/${MESSAGE_ID_SECOND}?fields=id,threadId,historyId&format=full" -> {
return genMsgDetailsMockResponse(MESSAGE_ID_SECOND, THREAD_ID_SECOND)
}

request.method == "GET" && request.path == genPathForGmailMessages(MESSAGE_ID_FIRST) -> {

return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setHeader("Content-Type", Json.MEDIA_TYPE)
.setBody(
genMessage(
messageId = MESSAGE_ID_FIRST,
messageThreadId = THREAD_ID_FIRST,
subject = MESSAGE_SUBJECT_FIRST,
historyIdValue = HISTORY_ID_FIRST
)
)
}

else -> return handleCommonAPICalls(request)
else -> handleCommonAPICalls(request)
}
}
})
Expand All @@ -159,41 +106,57 @@ class DraftsGmailAPITestCorrectCreatingAndUpdatingFlowTest : BaseDraftsGmailAPIF
onView(withId(R.id.recyclerViewMsgs))
.check(matches(withRecyclerViewItemCount(0)))

openComposeScreenAndTypeSubject(MESSAGE_SUBJECT_FIRST)
Thread.sleep(DraftViewModel.DELAY_TIMEOUT * 2)
//create a draft
openComposeScreenAndTypeData(MESSAGE_SUBJECT_FIRST)
//switch to standard mode
openActionBarOverflowOrOptionsMenu(getTargetContext())
onView(withText(R.string.switch_to_standard_email))
.check(matches(isDisplayed()))
.perform(click())
waitUntil(DraftViewModel.DELAY_TIMEOUT * 2) {
draftsCache.isNotEmpty()
}
pressBack()
waitForObjectWithText(MESSAGE_SUBJECT_FIRST, TimeUnit.SECONDS.toMillis(10))

//check that the first draft was created
//check that the a thread with a single draft was created
assertEquals(1, draftsCache.size)
onView(withId(R.id.recyclerViewMsgs))
.check(matches(withRecyclerViewItemCount(1)))
val mimeMessageFirst = getMimeMessageFromCache(0)
val mimeMessageFirst = getMimeMessageFromCache(DRAFT_ID_FIRST)
assertEquals(MESSAGE_SUBJECT_FIRST, mimeMessageFirst.subject)

openComposeScreenAndTypeSubject(MESSAGE_SUBJECT_SECOND)
Thread.sleep(DraftViewModel.DELAY_TIMEOUT * 2)
pressBack()
//open the created thread and wait rendering
onView(allOf(withId(R.id.recyclerViewMsgs), isDisplayed())).perform(
actionOnItemAtPosition<ViewHolder>(0, click())
)
waitForObjectWithText(MESSAGE_SUBJECT_FIRST, TimeUnit.SECONDS.toMillis(10))

//check that the second draft was created
assertEquals(2, draftsCache.size)
onView(withId(R.id.recyclerViewMsgs))
.check(matches(withRecyclerViewItemCount(2)))
val mimeMessageSecond = getMimeMessageFromCache(1)
assertEquals(MESSAGE_SUBJECT_SECOND, mimeMessageSecond.subject)
onView(Matchers.allOf(withId(R.id.recyclerViewMessages), isDisplayed()))
.perform(
RecyclerViewActions.scrollTo<ViewHolder>(
hasDescendant(
allOf(
withId(R.id.textViewSubject),
withText(MESSAGE_SUBJECT_FIRST)
)
)
)
)

//open the first draft and modify it
onView(withId(R.id.recyclerViewMsgs))
.perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(1, click()))
//wait for the message details
Thread.sleep(2000)
onView(
allOf(
//as we have viewpager at this stage need to add additional selector
hasSibling(allOf(withId(R.id.textViewSubject), withText(MESSAGE_SUBJECT_FIRST))),
withId(R.id.imageButtonEditDraft)
//click to edit a draft
onView(allOf(withId(R.id.recyclerViewMessages), isDisplayed()))
.perform(
actionOnItemAtPosition<ViewHolder>(
1,
ClickOnViewInRecyclerViewItem(R.id.imageButtonEditDraft)
)
)
).check(matches(isDisplayed()))
.perform(click())

//wait rendering a draft on the compose message screen
waitForObjectWithText(MESSAGE_SUBJECT_FIRST, TimeUnit.SECONDS.toMillis(10))

//update the draft subject
onView(withId(R.id.editTextEmailSubject))
.check(matches(isDisplayed()))
.perform(
Expand All @@ -203,20 +166,30 @@ class DraftsGmailAPITestCorrectCreatingAndUpdatingFlowTest : BaseDraftsGmailAPIF
closeSoftKeyboard()
)

Thread.sleep(DraftViewModel.DELAY_TIMEOUT * 2)
pressBack()//back to the message details screen
pressBack()//back to the messages list screen
//back to the message details screen and check if content was updated
pressBack()
waitForObjectWithText(MESSAGE_SUBJECT_FIRST_EDITED, TimeUnit.SECONDS.toMillis(10))
onView(Matchers.allOf(withId(R.id.recyclerViewMessages), isDisplayed()))
.perform(
RecyclerViewActions.scrollTo<ViewHolder>(
hasDescendant(
allOf(
withId(R.id.textViewSubject),
withText(MESSAGE_SUBJECT_FIRST_EDITED)
)
)
)
)

//check if 1st draft is updated correctly and 2nd draft remains same
assertEquals(2, draftsCache.size)
//back to the messages list screen and check if content was updated
pressBack()
assertEquals(1, draftsCache.size)
onView(withId(R.id.recyclerViewMsgs))
.check(matches(withRecyclerViewItemCount(2)))
val mimeMessageFirstEdited = getMimeMessageFromCache(0)
.check(matches(withRecyclerViewItemCount(1)))
val mimeMessageFirstEdited = getMimeMessageFromCache(DRAFT_ID_FIRST)
assertEquals(MESSAGE_SUBJECT_FIRST_EDITED, mimeMessageFirstEdited.subject)
onView(withText(MESSAGE_SUBJECT_FIRST_EDITED))
.check(matches(isDisplayed()))
val mimeMessageSecondAfterEditingFirst = getMimeMessageFromCache(1)
assertEquals(MESSAGE_SUBJECT_SECOND, mimeMessageSecondAfterEditingFirst.subject)
}

companion object {
Expand Down
Loading