fix: Home activity crash handling from the seed view optimisations. added tests for that plus rendering URL crash

This commit is contained in:
Harris 2021-08-09 10:06:58 +10:00
parent b9cf70ec3a
commit bf6c2d29f1
7 changed files with 188 additions and 20 deletions

View File

@ -127,19 +127,30 @@ dependencies {
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
testImplementation 'androidx.test:core:1.3.0' testImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.multidex:multidex:2.0.1' // Core library
androidTestImplementation 'androidx.multidex:multidex-instrumentation:2.0.0' androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' // AndroidJUnitRunner and JUnit Rules
androidTestImplementation ('org.assertj:assertj-core:1.7.1') { androidTestImplementation 'androidx.test:runner:1.4.0'
exclude group: 'org.hamcrest', module: 'hamcrest-core' androidTestImplementation 'androidx.test:rules:1.4.0'
}
androidTestImplementation ('com.squareup.assertj:assertj-android:1.1.1') { // Assertions
exclude group: 'org.hamcrest', module: 'hamcrest-core' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
exclude group: 'com.android.support', module: 'support-annotations' androidTestImplementation 'androidx.test.ext:truth:1.4.0'
} androidTestImplementation 'com.google.truth:truth:1.0'
testImplementation 'org.robolectric:robolectric:4.2.1'
testImplementation 'org.robolectric:shadows-multidex:4.2' // Espresso dependencies
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
testImplementation 'org.robolectric:robolectric:4.4'
testImplementation 'org.robolectric:shadows-multidex:4.4'
} }
def canonicalVersionCode = 217 def canonicalVersionCode = 217
@ -209,6 +220,14 @@ android {
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
resConfigs autoResConfig() resConfigs autoResConfig()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
} }
buildTypes { buildTypes {

View File

@ -0,0 +1,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="network.loki.messenger">
<application>
<uses-library android:name="android.test.runner"
android:required="false" />
</application>
</manifest>

View File

@ -0,0 +1,100 @@
package network.loki.messenger
import android.content.ClipboardManager
import android.content.Context
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable
import network.loki.messenger.util.NewConversationButtonDrawableMatcher.Companion.newConversationButtonWithDrawable
import org.hamcrest.Matchers.allOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.home.HomeActivity
@RunWith(AndroidJUnit4::class)
@LargeTest
class HomeActivityTests {
@get:Rule
var activityRule = ActivityScenarioRule(HomeActivity::class.java)
private fun sendMessage(messageToSend: String) {
// 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)),inputButtonWithDrawable(R.drawable.ic_arrow_up))).perform(ViewActions.click())
}
private fun setupLoggedInState(hasViewedSeed: Boolean = false) {
// landing activity
onView(withId(R.id.registerButton)).perform(ViewActions.click())
// session ID - register activity
onView(withId(R.id.registerButton)).perform(ViewActions.click())
// display name selection
onView(withId(R.id.displayNameEditText)).perform(ViewActions.typeText("test-user123"))
onView(withId(R.id.registerButton)).perform(ViewActions.click())
// PN select
if (hasViewedSeed) {
// has viewed seed is set to false after register activity
TextSecurePreferences.setHasViewedSeed(InstrumentationRegistry.getInstrumentation().targetContext, true)
}
onView(withId(R.id.backgroundPollingOptionView)).perform(ViewActions.click())
onView(withId(R.id.registerButton)).perform(ViewActions.click())
}
private fun goToMyChat() {
onView(newConversationButtonWithDrawable(R.drawable.ic_plus)).perform(ViewActions.click())
onView(newConversationButtonWithDrawable(R.drawable.ic_message)).perform(ViewActions.click())
// new chat
onView(withId(R.id.copyButton)).perform(ViewActions.click())
val context = InstrumentationRegistry.getInstrumentation().targetContext
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val copied = clipboardManager.primaryClip!!.getItemAt(0).text
onView(withId(R.id.publicKeyEditText)).perform(ViewActions.typeText(copied.toString()))
onView(withId(R.id.createPrivateChatButton)).perform(ViewActions.click())
}
@Test
fun testLaunches_dismiss_seedView() {
setupLoggedInState()
onView(allOf(withId(R.id.button), isDescendantOfA(withId(R.id.seedReminderView)))).perform(ViewActions.click())
onView(withId(R.id.copyButton)).perform(ViewActions.click())
pressBack()
onView(withId(R.id.seedReminderView)).check(matches(withEffectiveVisibility(Visibility.GONE)))
}
@Test
fun testIsVisible_seedView() {
setupLoggedInState()
onView(withId(R.id.seedReminderView)).check(matches(isCompletelyDisplayed()))
}
@Test
fun testIsVisible_alreadyDismissed_seedView() {
setupLoggedInState(hasViewedSeed = true)
onView(withId(R.id.seedReminderView)).check(doesNotExist())
}
@Test
fun testChat_withSelf() {
setupLoggedInState()
goToMyChat()
TextSecurePreferences.setLinkPreviewsEnabled(InstrumentationRegistry.getInstrumentation().targetContext, true)
sendMessage("howdy")
sendMessage("test")
// tests url rewriter doesn't crash
sendMessage("https://www.getsession.org?random_query_parameter=testtesttesttesttesttesttesttest&other_query_parameter=testtesttesttesttesttesttesttest")
sendMessage("https://www.ámazon.com")
// TODO: check data / tap URL and check it's displayed properly here
}
}

View File

@ -0,0 +1,42 @@
package network.loki.messenger.util
import android.view.View
import androidx.annotation.DrawableRes
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
import org.thoughtcrime.securesms.home.NewConversationButtonSetView
class NewConversationButtonDrawableMatcher(@DrawableRes private val expectedId: Int): TypeSafeMatcher<View>() {
companion object {
@JvmStatic fun newConversationButtonWithDrawable(@DrawableRes expectedId: Int) = NewConversationButtonDrawableMatcher(expectedId)
}
override fun describeTo(description: Description?) {
description?.appendText("with drawable on button with resource id: $expectedId")
}
override fun matchesSafely(item: View): Boolean {
if (item !is NewConversationButtonSetView.Button) return false
return item.getIconID() == expectedId
}
}
class InputBarButtonDrawableMatcher(@DrawableRes private val expectedId: Int): TypeSafeMatcher<View>() {
companion object {
@JvmStatic fun inputButtonWithDrawable(@DrawableRes expectedId: Int) = InputBarButtonDrawableMatcher(expectedId)
}
override fun describeTo(description: Description?) {
description?.appendText("with drawable on button with resource id: $expectedId")
}
override fun matchesSafely(item: View): Boolean {
if (item !is InputBarButton) return false
return item.getIconID() == expectedId
}
}

View File

@ -9,7 +9,6 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.Gravity import android.view.Gravity
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.MotionEvent import android.view.MotionEvent
@ -18,10 +17,7 @@ import android.widget.RelativeLayout
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.util.* import org.thoughtcrime.securesms.util.*
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.InputBarButtonImageViewContainer
import java.util.* import java.util.*
import kotlin.math.abs
class InputBarButton : RelativeLayout { class InputBarButton : RelativeLayout {
private val gestureHandler = Handler(Looper.getMainLooper()) private val gestureHandler = Handler(Looper.getMainLooper())
@ -105,6 +101,8 @@ class InputBarButton : RelativeLayout {
isHapticFeedbackEnabled = true isHapticFeedbackEnabled = true
} }
fun getIconID() = iconID
fun expand() { fun expand() {
GlowViewUtilities.animateColorChange(context, imageViewContainer, colorID, R.color.accent) GlowViewUtilities.animateColorChange(context, imageViewContainer, colorID, R.color.accent)
imageViewContainer.animateSizeChange(R.dimen.input_bar_button_collapsed_size, R.dimen.input_bar_button_expanded_size, animationDuration) imageViewContainer.animateSizeChange(R.dimen.input_bar_button_collapsed_size, R.dimen.input_bar_button_expanded_size, animationDuration)

View File

@ -20,6 +20,7 @@ import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.seed_reminder_stub.*
import kotlinx.android.synthetic.main.seed_reminder_stub.view.* import kotlinx.android.synthetic.main.seed_reminder_stub.view.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -168,7 +169,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
profileButton.update() profileButton.update()
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this) val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
if (hasViewedSeed) { if (hasViewedSeed) {
seedReminderStub.visibility = View.GONE seedReminderView?.isVisible = false
} }
if (TextSecurePreferences.getConfigurationMessageSynced(this)) { if (TextSecurePreferences.getConfigurationMessageSynced(this)) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {

View File

@ -17,8 +17,6 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.util.* import org.thoughtcrime.securesms.util.*
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.NewConversationButtonImageView
class NewConversationButtonSetView : RelativeLayout { class NewConversationButtonSetView : RelativeLayout {
private var expandedButton: Button? = null private var expandedButton: Button? = null
@ -52,6 +50,8 @@ class NewConversationButtonSetView : RelativeLayout {
@DrawableRes private var iconID = 0 @DrawableRes private var iconID = 0
private var isMain = false private var isMain = false
fun getIconID() = iconID
companion object { companion object {
val animationDuration = 250.toLong() val animationDuration = 250.toLong()
} }