From 88864ca81a73cd711490c38de64d8dda2e663d71 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 9 Oct 2026 08:58:43 +0200 Subject: [PATCH] fix(garm): drawer activity it Signed-off-by: alperozturk96 --- .../android/ui/activity/DrawerActivityIT.kt | 190 +++++++++--------- 1 file changed, 90 insertions(+), 100 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt index 81393576af39..e7e9a9f791f2 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt @@ -10,19 +10,24 @@ package com.owncloud.android.ui.activity import android.accounts.Account import android.accounts.AccountManager +import android.content.ContentUris import android.content.ContentValues import android.net.Uri +import android.os.SystemClock +import androidx.test.core.app.ActivityScenario import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.contrib.DrawerActions import androidx.test.espresso.contrib.NavigationViewActions import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.matcher.IntentMatchers -import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule -import com.nextcloud.client.account.User +import com.google.android.material.navigation.NavigationView import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl import com.nextcloud.test.Flaky @@ -34,16 +39,18 @@ import com.owncloud.android.R import com.owncloud.android.db.ProviderMeta.ProviderTableMeta import com.owncloud.android.lib.common.ExternalLinkType import com.owncloud.android.lib.common.accounts.AccountUtils -import org.hamcrest.Matchers -import org.junit.Assert +import com.owncloud.android.ui.navigation.NavigatorActivity +import com.owncloud.android.ui.navigation.NavigatorScreen +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.anyOf +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.BeforeClass import org.junit.Rule import org.junit.Test -import java.util.function.Supplier class DrawerActivityIT : AbstractIT() { - @Rule - @JvmField + @get:Rule val retryTestRule = RetryTestRule() @get:Rule @@ -52,62 +59,65 @@ class DrawerActivityIT : AbstractIT() { @Test @Flaky(reason = "Account switch relaunches FileDisplayActivity, which races with the drawer assertions") fun switchAccountViaAccountList() { - // Switching accounts finishes and relaunches FileDisplayActivity (see - // FileDisplayActivity.handleRestartIntent). That self-relaunch is incompatible with - // ActivityScenario#close(), which cannot drive its tracked instance to DESTROYED, so the - // scenario is launched without auto-closing. val scenario = launchActivity() lateinit var sut: FileDisplayActivity - scenario.onActivity { activity -> - sut = activity - } + scenario.onActivity { sut = it } - Assert.assertEquals(account1, sut.user.get().toPlatformAccount()) + assertEquals(account1, sut.user.get().toPlatformAccount()) - onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) - onView( - Matchers.anyOf( - ViewMatchers.withText(account2Name), - ViewMatchers.withText( - account2DisplayName - ) - ) - ).perform(ViewActions.click()) + onView(withId(R.id.switch_account_button)).perform(click()) + onView(anyOf(withText(account2Name), withText(account2DisplayName))).perform(click()) - Assert.assertEquals(account2, sut.user.get().toPlatformAccount()) + assertEquals(account2, sut.user.get().toPlatformAccount()) - onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) - onView(ViewMatchers.withText(account1?.name)).perform(ViewActions.click()) + onView(withId(R.id.switch_account_button)).perform(click()) + onView(withText(account1.name)).perform(click()) } - /** - * External link menu items get the item id [MENU_ITEM_EXTERNAL_LINK] + the link's local database id, and that - * id grows without bound because the table is cleared and refilled on every refresh. A link whose id has grown - * past the legacy 0-100 window must still open. - */ @Test fun externalLinkWithIdBeyondLegacyRangeIsOpened() { - insertExternalLink() + val linkId = insertExternalLink() + assertTrue(linkId > LEGACY_EXTERNAL_LINK_RANGE) + val menuItemId = MENU_ITEM_EXTERNAL_LINK + linkId Intents.init() try { - launchActivity().use { - waitForIdleSync() + val intent = NavigatorActivity.intent(targetContext, NavigatorScreen.Community) + ActivityScenario.launch(intent).use { scenario -> + waitUntil { scenario.hasDrawerMenuItem(menuItemId) } - onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open()) - onView(ViewMatchers.withId(R.id.nav_view)) - .perform(NavigationViewActions.navigateTo(MENU_ITEM_EXTERNAL_LINK + EXTERNAL_LINK_ID)) + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(menuItemId)) - Intents.intended(IntentMatchers.hasComponent(ExternalSiteWebView::class.java.name)) - Intents.intended(IntentMatchers.hasExtra(ExternalSiteWebView.EXTRA_URL, EXTERNAL_LINK_URL)) + val externalSiteIntent = allOf( + hasComponent(ExternalSiteWebView::class.java.name), + hasExtra(ExternalSiteWebView.EXTRA_URL, EXTERNAL_LINK_URL) + ) + waitUntil { Intents.getIntents().any(externalSiteIntent::matches) } } } finally { Intents.release() - deleteExternalLink() + deleteExternalLink(linkId) } } - private fun insertExternalLink() { + private fun ActivityScenario.hasDrawerMenuItem(menuItemId: Int): Boolean { + var found = false + onActivity { + found = it.findViewById(R.id.nav_view).menu.findItem(menuItemId) != null + } + return found + } + + private fun waitUntil(condition: () -> Boolean) { + val deadline = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MILLIS + while (!condition()) { + check(SystemClock.elapsedRealtime() < deadline) { "Condition not met within $WAIT_TIMEOUT_MILLIS ms" } + SystemClock.sleep(WAIT_POLL_INTERVAL_MILLIS) + } + } + + private fun insertExternalLink(): Int { val values = ContentValues().apply { put(ProviderTableMeta._ID, EXTERNAL_LINK_ID) put(ProviderTableMeta.EXTERNAL_LINKS_ICON_URL, "") @@ -119,25 +129,18 @@ class DrawerActivityIT : AbstractIT() { } val uri = targetContext.contentResolver.insert(ProviderTableMeta.CONTENT_URI_EXTERNAL_LINKS, values) - Assert.assertNotNull("External link could not be stored", uri) - - // the whole point of the test is an id outside the legacy window, so fail loudly if it was not honoured - Assert.assertTrue( - "External link id must exceed the legacy window", - EXTERNAL_LINK_ID > LEGACY_EXTERNAL_LINK_RANGE - ) + return ContentUris.parseId(requireNotNull(uri) { "External link could not be stored" }).toInt() } - private fun deleteExternalLink() { + private fun deleteExternalLink(linkId: Int) { targetContext.contentResolver.delete( ProviderTableMeta.CONTENT_URI_EXTERNAL_LINKS, "${ProviderTableMeta._ID} = ?", - arrayOf(EXTERNAL_LINK_ID.toString()) + arrayOf(linkId.toString()) ) } companion object { - // kept in sync with DrawerActivity, where both values are private private const val MENU_ITEM_EXTERNAL_LINK = 111 private const val LEGACY_EXTERNAL_LINK_RANGE = 100 @@ -145,60 +148,47 @@ class DrawerActivityIT : AbstractIT() { private const val EXTERNAL_LINK_NAME = "High ID Test" private const val EXTERNAL_LINK_URL = "https://nextcloud.com" - private var account1: Account? = null - private var user1: User? = null - private var account2: Account? = null - private var account2Name: String? = null - private var account2DisplayName: String? = null + private const val WAIT_TIMEOUT_MILLIS = 5_000L + private const val WAIT_POLL_INTERVAL_MILLIS = 100L + + private const val SERVER_VERSION = "14.0.0.0" + + private lateinit var account1: Account + private lateinit var account2: Account + private lateinit var account2Name: String + private lateinit var account2DisplayName: String @JvmStatic @BeforeClass fun beforeClass() { - val arguments = InstrumentationRegistry.getArguments() - val baseUrl = Uri.parse(arguments.getString("TEST_SERVER_URL")) - + val baseUrl = Uri.parse(InstrumentationRegistry.getArguments().getString("TEST_SERVER_URL")) val platformAccountManager = AccountManager.get(targetContext) - val userAccountManager: UserAccountManager = UserAccountManagerImpl.fromContext(targetContext) - for (account in platformAccountManager.accounts) { - platformAccountManager.removeAccountExplicitly(account) - } + platformAccountManager.accounts.forEach(platformAccountManager::removeAccountExplicitly) - var loginName = "user1" - var password = "user1" - - var temp = Account("$loginName@$baseUrl", MainApp.getAccountType(targetContext)) - platformAccountManager.addAccountExplicitly(temp, password, null) - platformAccountManager.setUserData( - temp, - AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION, - UserAccountManager.ACCOUNT_VERSION.toString() - ) - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0") - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString()) - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, loginName) // same as userId - - account1 = userAccountManager.getAccountByName("$loginName@$baseUrl") - user1 = userAccountManager.getUser(account1!!.name) - .orElseThrow(Supplier { IllegalAccessError() }) - - loginName = "user2" - password = "user2" - - temp = Account("$loginName@$baseUrl", MainApp.getAccountType(targetContext)) - platformAccountManager.addAccountExplicitly(temp, password, null) - platformAccountManager.setUserData( - temp, - AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION, - UserAccountManager.ACCOUNT_VERSION.toString() - ) - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_VERSION, "14.0.0.0") - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString()) - platformAccountManager.setUserData(temp, AccountUtils.Constants.KEY_USER_ID, loginName) // same as userId - - account2 = userAccountManager.getAccountByName("$loginName@$baseUrl") - account2Name = "$loginName@$baseUrl" + account1 = addAccount(platformAccountManager, "user1", baseUrl) + account2 = addAccount(platformAccountManager, "user2", baseUrl) + account2Name = account2.name account2DisplayName = "User Two@$baseUrl" } + + private fun addAccount(platformAccountManager: AccountManager, loginName: String, baseUrl: Uri): Account { + val accountName = "$loginName@$baseUrl" + val account = Account(accountName, MainApp.getAccountType(targetContext)) + + platformAccountManager.run { + addAccountExplicitly(account, loginName, null) + setUserData( + account, + AccountUtils.Constants.KEY_OC_ACCOUNT_VERSION, + UserAccountManager.ACCOUNT_VERSION.toString() + ) + setUserData(account, AccountUtils.Constants.KEY_OC_VERSION, SERVER_VERSION) + setUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL, baseUrl.toString()) + setUserData(account, AccountUtils.Constants.KEY_USER_ID, loginName) + } + + return requireNotNull(UserAccountManagerImpl.fromContext(targetContext).getAccountByName(accountName)) + } } }