fix: add UI test for URL modal dialog and fix mention infinite layout inflation bugs (#841)

This commit is contained in:
Harris 2022-02-09 14:18:22 +11:00 committed by GitHub
parent 07ccc2696b
commit b01075cef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 17 deletions

View File

@ -1,26 +1,44 @@
package network.loki.messenger package network.loki.messenger
import android.app.Instrumentation
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.view.View
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable
import network.loki.messenger.util.NewConversationButtonDrawableMatcher.Companion.newConversationButtonWithDrawable import network.loki.messenger.util.NewConversationButtonDrawableMatcher.Companion.newConversationButtonWithDrawable
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not import org.hamcrest.Matchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBar
import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.mms.GlideApp
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@LargeTest @LargeTest
@ -29,10 +47,29 @@ class HomeActivityTests {
@get:Rule @get:Rule
var activityRule = ActivityScenarioRule(HomeActivity::class.java) var activityRule = ActivityScenarioRule(HomeActivity::class.java)
private fun sendMessage(messageToSend: String) { private val activityMonitor = Instrumentation.ActivityMonitor(ConversationActivityV2::class.java.name, null, false)
@Before
fun setUp() {
InstrumentationRegistry.getInstrumentation().addMonitor(activityMonitor)
}
@After
fun tearDown() {
InstrumentationRegistry.getInstrumentation().removeMonitor(activityMonitor)
}
private fun sendMessage(messageToSend: String, linkPreview: LinkPreview? = null) {
// assume in chat activity // assume in chat activity
onView(allOf(isDescendantOfA(withId(R.id.inputBar)),withId(R.id.inputBarEditText))).perform(ViewActions.replaceText(messageToSend)) onView(allOf(isDescendantOfA(withId(R.id.inputBar)),withId(R.id.inputBarEditText))).perform(ViewActions.replaceText(messageToSend))
if (linkPreview != null) {
val activity = activityMonitor.waitForActivity() as ConversationActivityV2
val glide = GlideApp.with(activity)
activity.findViewById<InputBar>(R.id.inputBar).updateLinkPreviewDraft(glide, linkPreview)
}
onView(allOf(isDescendantOfA(withId(R.id.inputBar)),inputButtonWithDrawable(R.drawable.ic_arrow_up))).perform(ViewActions.click()) onView(allOf(isDescendantOfA(withId(R.id.inputBar)),inputButtonWithDrawable(R.drawable.ic_arrow_up))).perform(ViewActions.click())
// TODO: text can flaky on cursor reload, figure out a better way to wait for the UI to settle with new data
onView(isRoot()).perform(waitFor(500))
} }
private fun setupLoggedInState(hasViewedSeed: Boolean = false) { private fun setupLoggedInState(hasViewedSeed: Boolean = false) {
@ -99,7 +136,43 @@ class HomeActivityTests {
// tests url rewriter doesn't crash // tests url rewriter doesn't crash
sendMessage("https://www.getsession.org?random_query_parameter=testtesttesttesttesttesttesttest&other_query_parameter=testtesttesttesttesttesttesttest") sendMessage("https://www.getsession.org?random_query_parameter=testtesttesttesttesttesttesttest&other_query_parameter=testtesttesttesttesttesttesttest")
sendMessage("https://www.ámazon.com") sendMessage("https://www.ámazon.com")
// TODO: check data / tap URL and check it's displayed properly here }
@Test
fun testChat_displaysCorrectUrl() {
setupLoggedInState()
goToMyChat()
TextSecurePreferences.setLinkPreviewsEnabled(InstrumentationRegistry.getInstrumentation().targetContext, true)
// given the link url text
val url = "https://www.ámazon.com"
sendMessage(url, LinkPreview(url, "amazon", Optional.absent()))
// when the URL span is clicked
onView(withSubstring(url)).perform(ViewActions.click())
// then the URL dialog should be displayed with a known punycode url
val amazonPuny = "https://www.xn--mazon-wqa.com/"
val dialogPromptText = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.dialog_open_url_explanation, amazonPuny)
onView(withText(dialogPromptText)).check(matches(isDisplayed()))
}
/**
* Perform action of waiting for a specific time.
*/
fun waitFor(millis: Long): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View>? {
return isRoot()
}
override fun getDescription(): String = "Wait for $millis milliseconds."
override fun perform(uiController: UiController, view: View?) {
uiController.loopMainThreadForAtLeast(millis)
}
}
} }
} }

View File

@ -622,6 +622,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val query = text.substring(currentMentionStartIndex + 1) // + 1 to get rid of the "@" val query = text.substring(currentMentionStartIndex + 1) // + 1 to get rid of the "@"
showOrUpdateMentionCandidatesIfNeeded(query) showOrUpdateMentionCandidatesIfNeeded(query)
} }
} else {
currentMentionStartIndex = -1
hideMentionCandidates()
} }
previousText = text previousText = text
} }
@ -636,13 +639,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, viewModel.recipient.isOpenGroupRecipient) val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, viewModel.recipient.isOpenGroupRecipient)
this.mentionCandidatesView = view this.mentionCandidatesView = view
view.show(candidates, viewModel.threadId) view.show(candidates, viewModel.threadId)
view.alpha = 0.0f
val animation = ValueAnimator.ofObject(FloatEvaluator(), view.alpha, 1.0f)
animation.duration = 250L
animation.addUpdateListener { animator ->
view.alpha = animator.animatedValue as Float
}
animation.start()
} else { } else {
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, viewModel.recipient.isOpenGroupRecipient) val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, viewModel.recipient.isOpenGroupRecipient)
this.mentionCandidatesView!!.setMentionCandidates(candidates) this.mentionCandidatesView!!.setMentionCandidates(candidates)

View File

@ -93,6 +93,7 @@ class VisibleMessageContentView : LinearLayout {
binding.quoteView.isVisible = message is MmsMessageRecord && message.quote != null binding.quoteView.isVisible = message is MmsMessageRecord && message.quote != null
binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty()
binding.linkPreviewView.bodyTextView = binding.bodyTextView
val linkPreviewLayout = binding.linkPreviewView.layoutParams val linkPreviewLayout = binding.linkPreviewView.layoutParams
linkPreviewLayout.width = if (mediaThumbnailMessage) 0 else ViewGroup.LayoutParams.WRAP_CONTENT linkPreviewLayout.width = if (mediaThumbnailMessage) 0 else ViewGroup.LayoutParams.WRAP_CONTENT

View File

@ -16,9 +16,9 @@
<org.thoughtcrime.securesms.conversation.v2.ConversationRecyclerView <org.thoughtcrime.securesms.conversation.v2.ConversationRecyclerView
android:id="@+id/conversationRecyclerView" android:id="@+id/conversationRecyclerView"
android:layout_above="@+id/typingIndicatorViewContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:layout_above="@+id/typingIndicatorViewContainer" />
<org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorViewContainer <org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorViewContainer
android:id="@+id/typingIndicatorViewContainer" android:id="@+id/typingIndicatorViewContainer"
@ -45,8 +45,7 @@
android:id="@+id/additionalContentContainer" android:id="@+id/additionalContentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignBottom="@+id/conversationRecyclerView"/>
android:layout_marginBottom="@dimen/input_bar_height" />
<LinearLayout <LinearLayout
android:id="@+id/attachmentOptionsContainer" android:id="@+id/attachmentOptionsContainer"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidateView <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="44dp" android:layout_height="44dp"
@ -51,4 +51,4 @@
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:background="@color/separator" /> android:background="@color/separator" />
</org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidateView> </RelativeLayout>