diff --git a/ring-android/app/build.gradle.kts b/ring-android/app/build.gradle.kts index 5b442238a36f918f889c40144337e16404e6adb5..9f060cd50da607b9ed58a9b8af4edb614368cd9f 100644 --- a/ring-android/app/build.gradle.kts +++ b/ring-android/app/build.gradle.kts @@ -20,6 +20,7 @@ android { targetSdk = 31 versionCode = 326 versionName = "20220121-01" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } sourceSets { getByName("main") { @@ -114,6 +115,10 @@ dependencies { // Dagger dependency injection implementation("com.google.dagger:hilt-android:$hilt_version") + implementation("androidx.test.ext:junit-ktx:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test:rules:1.4.0") + androidTestImplementation("androidx.test.ext:junit:1.1.3") kapt("com.google.dagger:hilt-android-compiler:$hilt_version") // Glide diff --git a/ring-android/app/src/androidTest/java/cx/ring/client/Test0001AccountCreation.kt b/ring-android/app/src/androidTest/java/cx/ring/client/Test0001AccountCreation.kt new file mode 100644 index 0000000000000000000000000000000000000000..ac0c5544eb1aa39ddec9a04fc2e375a9a99b8d98 --- /dev/null +++ b/ring-android/app/src/androidTest/java/cx/ring/client/Test0001AccountCreation.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 20022 Savoir-faire Linux Inc. + * + * Authors: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.client + +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.* +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 cx.ring.R +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.allOf +import org.hamcrest.TypeSafeMatcher +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +class Test0001AccountCreation { + + @Rule + @JvmField + var mActivityTestRule = ActivityScenarioRule(HomeActivity::class.java) + + @Test + /** + * This test creates an account with "Foo" as a display name and check the MainView + */ + fun testAccountCreation() { + val materialButton = onView( + allOf(withId(R.id.ring_create_btn), withText("Create a Jami account"), + childAtPosition( + childAtPosition( + withClassName(`is`("android.widget.ScrollView")), + 0), + 2))) + materialButton.perform(scrollTo(), click()) + + val materialButton2 = onView( + allOf(withId(R.id.skip), withText("Skip choosing username"), + childAtPosition( + childAtPosition( + withClassName(`is`("androidx.cardview.widget.CardView")), + 0), + 3), + isDisplayed())) + materialButton2.perform(click()) + + val materialButton3 = onView( + allOf(withId(R.id.create_account), withText("Create account"), + childAtPosition( + childAtPosition( + withClassName(`is`("androidx.cardview.widget.CardView")), + 0), + 4), + isDisplayed())) + materialButton3.perform(click()) + + val textInputEditText = onView( + allOf(withId(R.id.username))) + textInputEditText.perform(click()) + textInputEditText.perform(replaceText("Foo"), closeSoftKeyboard()) + + val materialButton4 = onView( + allOf(withId(R.id.next_create_account), withText("Save Profile"), + childAtPosition( + childAtPosition( + withClassName(`is`("android.widget.LinearLayout")), + 3), + 0), + isDisplayed())) + materialButton4.perform(click()) + + waitUntilViewIsDisplayed(withId(R.id.title)) + + val textView = onView( + allOf(withId(R.id.title), withText("Foo"), + withParent(withParent(withId(R.id.spinner_toolbar))), + isDisplayed())) + textView.check(matches(withText("Foo"))) + } + + private fun childAtPosition( + parentMatcher: Matcher<View>, position: Int): Matcher<View> { + + return object : TypeSafeMatcher<View>() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } + } +} diff --git a/ring-android/app/src/androidTest/java/cx/ring/client/Test0002SearchDirectUri.kt b/ring-android/app/src/androidTest/java/cx/ring/client/Test0002SearchDirectUri.kt new file mode 100644 index 0000000000000000000000000000000000000000..336999d42bd34998d9ef45f81bf0cda0ed949260 --- /dev/null +++ b/ring-android/app/src/androidTest/java/cx/ring/client/Test0002SearchDirectUri.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 20022 Savoir-faire Linux Inc. + * + * Authors: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.client +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.* +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 cx.ring.R +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf +import org.hamcrest.TypeSafeMatcher +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +class Test0002SearchDirectUri { + + @Rule + @JvmField + var mActivityTestRule = ActivityScenarioRule(HomeActivity::class.java) + + @Test + /** + * This test search a random URI in the search bar. This should return an item from the public directory + * (valid because it's a valid uri) + */ + fun searchDirectUri() { + val actionMenuItemView = onView( + allOf(withId(R.id.menu_contact_search), withContentDescription("Search name or phone number…"), + childAtPosition( + childAtPosition( + withId(R.id.main_toolbar), + 2), + 0), + isDisplayed())) + actionMenuItemView.perform(click()) + + val editText = onView( + allOf(withId(R.id.search_src_text))) + editText.perform(longClick()) + editText.perform(replaceText("000000069ecabfecf731e1c98eafc4b592ab0000"), closeSoftKeyboard()) + editText.check(matches(withText("000000069ecabfecf731e1c98eafc4b592ab0000"))) + + waitUntilViewIsDisplayed(allOf(withId(R.id.conv_participant), withText("000000069ecabfecf731e1c98eafc4b592ab0000"))) + val textView = onView( + allOf(withId(R.id.conv_participant), withText("000000069ecabfecf731e1c98eafc4b592ab0000"), + withParent(withParent(withId(R.id.item_layout))), + isDisplayed())) + textView.check(matches(withText("000000069ecabfecf731e1c98eafc4b592ab0000"))) + } + + private fun childAtPosition( + parentMatcher: Matcher<View>, position: Int): Matcher<View> { + + return object : TypeSafeMatcher<View>() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } + } +} diff --git a/ring-android/app/src/androidTest/java/cx/ring/client/ViewIdlingResource.kt b/ring-android/app/src/androidTest/java/cx/ring/client/ViewIdlingResource.kt new file mode 100644 index 0000000000000000000000000000000000000000..7df3779d0a3ce8246921bcdb82bf4ffce4287d32 --- /dev/null +++ b/ring-android/app/src/androidTest/java/cx/ring/client/ViewIdlingResource.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 20022 Savoir-faire Linux Inc. + * + * Authors: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.client + +import android.view.View +import androidx.test.espresso.Espresso +import androidx.test.espresso.IdlingRegistry +import androidx.test.espresso.IdlingResource +import androidx.test.espresso.ViewFinder +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers +import org.hamcrest.Matcher +import java.lang.reflect.Field + +// Cf https://stackoverflow.com/questions/50628219/is-it-possible-to-use-espressos-idlingresource-to-wait-until-a-certain-view-app +/** + * @param viewMatcher The matcher to find the view. + * @param idleMatcher The matcher condition to be fulfilled to be considered idle. + */ +class ViewIdlingResource( + private val viewMatcher: Matcher<View?>?, + private val idleMatcher: Matcher<View?>? +) : IdlingResource { + + private var resourceCallback: IdlingResource.ResourceCallback? = null + + override fun isIdleNow(): Boolean { + val view: View? = getView(viewMatcher) + val isIdle: Boolean = idleMatcher?.matches(view) ?: false + if (isIdle) { + resourceCallback?.onTransitionToIdle() + } + return isIdle + } + + override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback?) { + this.resourceCallback = resourceCallback + } + + override fun getName(): String = "$this ${viewMatcher.toString()}" + + private fun getView(viewMatcher: Matcher<View?>?): View? { + return try { + val viewInteraction = Espresso.onView(viewMatcher) + val finderField: Field? = viewInteraction.javaClass.getDeclaredField("viewFinder") + finderField?.isAccessible = true + val finder = finderField?.get(viewInteraction) as ViewFinder + finder.view + } catch (e: Exception) { + null + } + } + +} + + +/** + * Waits for a matching View or throws an error if it's taking too long. + */ +fun waitUntilViewIsDisplayed(matcher: Matcher<View?>) { + val idlingResource: IdlingResource = ViewIdlingResource(matcher, ViewMatchers.isDisplayed()) + try { + IdlingRegistry.getInstance().register(idlingResource) + // First call to onView is to trigger the idler. + Espresso.onView(ViewMatchers.withId(0)).check(ViewAssertions.doesNotExist()) + } finally { + IdlingRegistry.getInstance().unregister(idlingResource) + } +} \ No newline at end of file