diff --git a/app/build.gradle b/app/build.gradle index d883f4ab68..0fbafe0fc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -127,19 +127,30 @@ dependencies { testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' testImplementation 'androidx.test:core:1.3.0' - androidTestImplementation 'androidx.multidex:multidex:2.0.1' - androidTestImplementation 'androidx.multidex:multidex-instrumentation:2.0.0' - androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' - androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestImplementation ('org.assertj:assertj-core:1.7.1') { - exclude group: 'org.hamcrest', module: 'hamcrest-core' - } - androidTestImplementation ('com.squareup.assertj:assertj-android:1.1.1') { - exclude group: 'org.hamcrest', module: 'hamcrest-core' - exclude group: 'com.android.support', module: 'support-annotations' - } - testImplementation 'org.robolectric:robolectric:4.2.1' - testImplementation 'org.robolectric:shadows-multidex:4.2' + // Core library + androidTestImplementation 'androidx.test:core:1.4.0' + + // AndroidJUnitRunner and JUnit Rules + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + + // Assertions + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.ext:truth:1.4.0' + androidTestImplementation 'com.google.truth:truth:1.0' + + // 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 @@ -209,6 +220,14 @@ android { buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" 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 { diff --git a/app/src/androidTest/AndroidManifest.xml b/app/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..68f81f6f87 --- /dev/null +++ b/app/src/androidTest/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt new file mode 100644 index 0000000000..43652b09e9 --- /dev/null +++ b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/network/loki/messenger/util/Matchers.kt b/app/src/androidTest/java/network/loki/messenger/util/Matchers.kt new file mode 100644 index 0000000000..1c93745467 --- /dev/null +++ b/app/src/androidTest/java/network/loki/messenger/util/Matchers.kt @@ -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() { + + 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() { + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt index 25e209424e..abf6720aa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt @@ -9,7 +9,6 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.util.AttributeSet -import android.util.Log import android.view.Gravity import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -18,10 +17,7 @@ import android.widget.RelativeLayout import androidx.annotation.DrawableRes import network.loki.messenger.R import org.thoughtcrime.securesms.util.* -import org.thoughtcrime.securesms.util.GlowViewUtilities -import org.thoughtcrime.securesms.util.InputBarButtonImageViewContainer import java.util.* -import kotlin.math.abs class InputBarButton : RelativeLayout { private val gestureHandler = Handler(Looper.getMainLooper()) @@ -105,6 +101,8 @@ class InputBarButton : RelativeLayout { isHapticFeedbackEnabled = true } + fun getIconID() = iconID + fun expand() { 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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index d8bc59d7da..d9886d2c32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -20,6 +20,7 @@ import androidx.loader.content.Loader import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.LinearLayoutManager 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.coroutines.Dispatchers import kotlinx.coroutines.flow.* @@ -168,7 +169,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis profileButton.update() val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this) if (hasViewedSeed) { - seedReminderStub.visibility = View.GONE + seedReminderView?.isVisible = false } if (TextSecurePreferences.getConfigurationMessageSynced(this)) { lifecycleScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/NewConversationButtonSetView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/NewConversationButtonSetView.kt index 1bd4df55d9..f414cba54a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/NewConversationButtonSetView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/NewConversationButtonSetView.kt @@ -17,8 +17,6 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import network.loki.messenger.R import org.thoughtcrime.securesms.util.* -import org.thoughtcrime.securesms.util.GlowViewUtilities -import org.thoughtcrime.securesms.util.NewConversationButtonImageView class NewConversationButtonSetView : RelativeLayout { private var expandedButton: Button? = null @@ -52,6 +50,8 @@ class NewConversationButtonSetView : RelativeLayout { @DrawableRes private var iconID = 0 private var isMain = false + fun getIconID() = iconID + companion object { val animationDuration = 250.toLong() }