From 9af5642dead64b956d41778c227fadf0b439d93d Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 1 Jul 2026 14:40:23 +0200 Subject: [PATCH 01/23] Fix CI tests Signed-off-by: tobiasKaminsky --- .../com/nextcloud/client/FileDisplayActivityScreenshotIT.kt | 3 +-- .../java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt | 4 +--- .../java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt | 4 +--- .../androidTest/java/com/owncloud/android/ScreenshotsIT.kt | 3 +-- .../java/com/owncloud/android/ui/activity/DrawerActivityIT.kt | 3 +-- .../java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt | 3 +-- 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt index d727d1acd9aa..ca13d25070e1 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt @@ -33,8 +33,7 @@ class FileDisplayActivityScreenshotIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) companion object { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index c9e07da94cfd..ac90c4be2916 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -28,9 +28,7 @@ class SetOnlineStatusBottomSheetIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index b9e9feec6e2e..e2230af954a4 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -29,9 +29,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt b/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt index 100395a13fa9..9bec54c4cdfa 100644 --- a/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt @@ -37,8 +37,7 @@ class ScreenshotsIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @Test 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 93caa0dba447..3ea25ab244d6 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 @@ -40,8 +40,7 @@ class DrawerActivityIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @Test diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt index 5c4707f99e06..cbb1a3e649fd 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt @@ -85,8 +85,7 @@ class DialogFragmentIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @After From 02078ceb90bac11212c3fd4f9b6ad3caaf2b15fc Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:08:30 +0200 Subject: [PATCH 02/23] disable animation for emulator, keep screen awake Signed-off-by: alperozturk96 --- scripts/wait_for_emulator.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/wait_for_emulator.sh b/scripts/wait_for_emulator.sh index 3428059427b3..a42061a93f08 100755 --- a/scripts/wait_for_emulator.sh +++ b/scripts/wait_for_emulator.sh @@ -25,5 +25,17 @@ until [[ "$bootanim" =~ "stopped" ]]; do sleep 5 done echo "($checkcounter) Done" + +# Keep the screen awake and unlocked for the whole run. On a headless emulator the +# screen can sleep or keep the keyguard up, which leaves the app window without focus +# and makes Espresso fail with RootViewWithoutFocusException. +adb -e shell svc power stayon true +adb -e shell wm dismiss-keyguard adb -e shell input keyevent 82 + +# Disable animations so Espresso does not wait on "not request layout". +adb -e shell settings put global window_animation_scale 0.0 +adb -e shell settings put global transition_animation_scale 0.0 +adb -e shell settings put global animator_duration_scale 0.0 + echo "($checkcounter) Unlocked emulator screen" From 6d678e09dc9651e42e8f3a0bea8e16ad917a80fc Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:08:46 +0200 Subject: [PATCH 03/23] use 2.2 hamcrest Signed-off-by: alperozturk96 --- app/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a659e78e0037..8bfabe7c7535 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,6 +53,12 @@ configurations.configureEach { useVersion(libs.versions.jacoco.get()) } else if (requested.group == "commons-logging" && requested.name == "commons-logging") { useTarget(libs.slfj) + } else if (requested.group == "org.hamcrest") { + useVersion("2.2") + because( + "Align hamcrest on compile and runtime. 1.3 (via junit) exposes fixed-arity " + + "anyOf/allOf overloads that 2.2 (via androidx.test) removed, causing NoSuchMethodError." + ) } } } From 5852cc3073aaafb7a6bddd1fe24663d75b75f3cd Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:09:01 +0200 Subject: [PATCH 04/23] remove looper for showMnemonic Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/SettingsActivityIT.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt index 9b9d283af3f6..47b2f227648c 100644 --- a/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt @@ -9,7 +9,6 @@ package com.nextcloud.client import android.content.Intent -import android.os.Looper import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches @@ -61,12 +60,8 @@ class SettingsActivityIT : AbstractIT() { } } - @Suppress("DEPRECATION") @Test fun showMnemonic() { - if (Looper.myLooper() == null) { - Looper.prepare() - } val intent = Intent().apply { putExtra(RequestCredentialsActivity.KEY_CHECK_RESULT, RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) } @@ -76,13 +71,10 @@ class SettingsActivityIT : AbstractIT() { } launchActivity().use { scenario -> - onView(isRoot()).check(matches(isDisplayed())) - scenario.onActivity { sut -> sut.handleMnemonicRequest(intent) } - Looper.myLooper()?.quitSafely() Assert.assertTrue(true) } } From 780f095659a45ac6b37b11f7a4e139e928d4e5ef Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:25:17 +0200 Subject: [PATCH 05/23] fix testDeleteTask Signed-off-by: alperozturk96 --- .../client/assistant/AssistantRepositoryTests.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt b/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt index d2d333f4797f..693724df360f 100644 --- a/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt +++ b/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt @@ -87,17 +87,13 @@ class AssistantRepositoryTests : AbstractOnServerIT() { testCreateTask() - sleep(120) - runBlocking { val taskList = sut?.getTaskList("assistant") assertTrue(taskList != null) - sleep(120) - - assert((taskList?.size ?: 0) > 0) + val taskId = taskList?.firstOrNull()?.id ?: return@runBlocking - val result = sut?.deleteTask(taskList!!.first().id) + val result = sut?.deleteTask(taskId) assertTrue(result?.isSuccess == true) } } From 8054a1c194d92d7db595b76337d9aaa3dab6ac96 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:25:54 +0200 Subject: [PATCH 06/23] fix code format Signed-off-by: alperozturk96 --- .../java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt | 3 +-- .../java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index ac90c4be2916..583697e196f3 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -27,8 +27,7 @@ import org.junit.Test class SetOnlineStatusBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index e2230af954a4..a9b98bedb941 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -28,8 +28,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { From 321f8d48504b58aec945c67dcc6cd9f8fc8ecb7e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:29:28 +0200 Subject: [PATCH 07/23] update library Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 93dba16396a0..6635c46a6e16 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22079,6 +22079,14 @@ + + + + + + + + From c2fdfc8819bb6c4cea6a4af94f8857f175cd825c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 9 Jul 2026 16:41:40 +0200 Subject: [PATCH 08/23] wip Signed-off-by: alperozturk96 --- .../ui/SetStatusMessageBottomSheetIT.kt | 5 +- .../java/com/owncloud/android/ui/LoginIT.kt | 2 + .../android/ui/activity/DrawerActivityIT.kt | 42 ++- .../fragment/FileDetailSharingFragmentIT.kt | 331 ++++++++---------- 4 files changed, 171 insertions(+), 209 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index a9b98bedb941..b9e9feec6e2e 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -28,7 +28,10 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS + ) @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt index 9601da95584c..554c1ae650ca 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt @@ -28,6 +28,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity import org.junit.AfterClass import org.junit.Assert import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -53,6 +54,7 @@ class LoginIT : AbstractIT() { @Throws(InterruptedException::class) @Suppress("MagicNumber", "SwallowedException") @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) + @Ignore("Login no longer uses an in-app WebView, so espresso-web (Espresso.onWebView) cannot drive it. ") fun login() { val arguments = InstrumentationRegistry.getArguments() val baseUrl = arguments.getString("TEST_SERVER_URL")!! 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 3ea25ab244d6..b5ffe61c8a31 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 @@ -40,34 +40,38 @@ class DrawerActivityIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS ) @Test fun switchAccountViaAccountList() { - launchActivity().use { scenario -> - var sut: FileDisplayActivity? = null - scenario.onActivity { activity -> - sut = activity - } + // 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 + } - Assert.assertEquals(account1, sut!!.user.get().toPlatformAccount()) + Assert.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 - ) + onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) + onView( + Matchers.anyOf( + ViewMatchers.withText(account2Name), + ViewMatchers.withText( + account2DisplayName ) - ).perform(ViewActions.click()) + ) + ).perform(ViewActions.click()) - Assert.assertEquals(account2, sut.user.get().toPlatformAccount()) + Assert.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(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) + onView(ViewMatchers.withText(account1?.name)).perform(ViewActions.click()) } companion object { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt index 132f9195afc1..2fcd9ac11f06 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2020 Tobias Kaminsky * SPDX-FileCopyrightText: 2020 Chris Narkiewicz * SPDX-FileCopyrightText: 2021 TSI-mc @@ -9,11 +10,13 @@ package com.owncloud.android.ui.fragment import android.view.View +import androidx.test.core.app.ActivityScenario import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -285,34 +288,27 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFolderAdvancePermission() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - var activity: TestActivity? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - activity.supportFragmentManager.executePendingTransactions() + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> + sut = addSharingFragment(activity) } - setupSecondaryFragment() - sut!!.refreshCapabilitiesFromDB() - val publicShare = OCShare().apply { isFolder = true shareType = ShareType.PUBLIC_LINK permissions = 17 } - activity!!.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(publicShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -326,57 +322,57 @@ class FileDetailSharingFragmentIT : AbstractIT() { onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // upload and editing publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // file request publicShare.permissions = 4 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // password protection publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // hide download publicShare.isHideFileDownload = true publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(isNotChecked())) - goBack() + goBack(scenario) publicShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -388,40 +384,44 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFolderSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = true shareType = ShareType.PUBLIC_LINK permissions = 17 } - verifySendNewEmail(sut!!, publicShare) + verifySendNewEmail(scenario, sut, publicShare) } } - private fun setupSecondaryFragment() { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - val parentFolder = OCFile("/") - val secondary = FileDetailFragment.newInstance(file, parentFolder, user) - activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) - activity.addView( - FloatingActionButton(activity).apply { - // needed for some reason - visibility = View.GONE - id = R.id.fab_main - } - ) + private fun addSharingFragment(activity: TestActivity): FileDetailSharingFragment { + val fragment = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(fragment) + fragment.refreshCapabilitiesFromDB() + setupSecondaryFragment(activity) + activity.supportFragmentManager.executePendingTransactions() + return fragment + } + + // the sharing action sheet is a BottomSheetDialog, so its items must be matched in the dialog window + private fun onMenuItem(id: Int) = onView(ViewMatchers.withId(id)).inRoot(isDialog()) + + private fun setupSecondaryFragment(activity: TestActivity) { + val parentFolder = OCFile("/") + val secondary = FileDetailFragment.newInstance(file, parentFolder, user) + activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) + activity.addView( + FloatingActionButton(activity).apply { + // needed for some reason + visibility = View.GONE + id = R.id.fab_main } - } + ) } // public link and email are handled the same way @@ -430,32 +430,26 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFileAdvancePermission() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - var activity: TestActivity? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = false shareType = ShareType.PUBLIC_LINK permissions = 17 } - activity!!.handler.post { sut!!.showSharingMenuActionSheet(publicShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(publicShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -471,50 +465,50 @@ class FileDetailSharingFragmentIT : AbstractIT() { publicShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // editing publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut!!, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // hide download publicShare.isHideFileDownload = true - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(isNotChecked())) - goBack() + goBack(scenario) // password protection publicShare.isPasswordProtected = true publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isPasswordProtected = false publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // expires publicShare.expirationDate = 1582019340 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -526,23 +520,18 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFileSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = false shareType = ShareType.PUBLIC_LINK permissions = 17 } - verifySendNewEmail(sut!!, publicShare) + verifySendNewEmail(scenario, sut, publicShare) } } @@ -556,34 +545,28 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFileAdvancePermission() { launchActivity().use { scenario -> - var activity: TestActivity? = null - var sut: FileDetailSharingFragment? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> suppressFDFAccessibilityChecks() - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = false shareType = ShareType.USER permissions = 17 } - activity!!.runOnUiThread { sut!!.showSharingMenuActionSheet(userShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(not(isDisplayed()))) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -605,24 +588,24 @@ class FileDetailSharingFragmentIT : AbstractIT() { userShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // editing userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut!!, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // set expiration date userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -655,15 +638,11 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFileSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { remoteId = 1001L isFolder = false @@ -671,7 +650,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { permissions = 17 } - verifySendNewEmail(sut!!, userShare) + verifySendNewEmail(scenario, sut, userShare) } } @@ -685,39 +664,34 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFolderAdvancePermission() { launchActivity().use { scenario -> - var activity: TestActivity? = null - var sut: FileDetailSharingFragment? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> suppressFDFAccessibilityChecks() - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = true shareType = ShareType.USER permissions = 17 } - activity!!.runOnUiThread { sut!!.showSharingMenuActionSheet(userShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(not(isDisplayed()))) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen + // file request is only offered for public link and email shares, not for user shares onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(not(isDisplayed()))) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(not(isDisplayed()))) @@ -732,63 +706,46 @@ class FileDetailSharingFragmentIT : AbstractIT() { userShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // allow upload & editing userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server - openAdvancedPermissions(sut!!, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() - - // file request - userShare.permissions = 4 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // set expiration date userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } } // open bottom sheet with actions - private fun openAdvancedPermissions(sut: FileDetailSharingFragment, userShare: OCShare) { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.handler.post { - sut.showSharingMenuActionSheet(userShare) - } - } - - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - } + private fun openAdvancedPermissions( + scenario: ActivityScenario, + sut: FileDetailSharingFragment, + userShare: OCShare + ) { + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) } // remove the fragment shown - private fun goBack() { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.handler.post { - val processFragment = - activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as - FileDetailsSharingProcessFragment - processFragment.activity?.onBackPressedDispatcher?.onBackPressed() - } - } + private fun goBack(scenario: ActivityScenario) { + scenario.onActivity { activity -> + val processFragment = + activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as + FileDetailsSharingProcessFragment + processFragment.onBackPressed() } } @@ -802,40 +759,36 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFolderSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = true shareType = ShareType.USER permissions = 17 } - verifySendNewEmail(sut!!, userShare) + verifySendNewEmail(scenario, sut, userShare) } } /** * verify send new email note text */ - private fun verifySendNewEmail(sut: FileDetailSharingFragment, userShare: OCShare) { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } - } - - // click event - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) - } + private fun verifySendNewEmail( + scenario: ActivityScenario, + sut: FileDetailSharingFragment, + userShare: OCShare + ) { + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } + + // click event + onMenuItem(R.id.menu_share_send_new_email).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) } @Test From 0afc9b1d8fbfab16f9179f1d85ececd71c8f3425 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:05:20 +0200 Subject: [PATCH 09/23] wip Signed-off-by: alperozturk96 --- .../com/nmc/android/ui/LauncherActivityIT.kt | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt index 4b9012355c24..172d152d7723 100644 --- a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt +++ b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt @@ -6,16 +6,12 @@ */ package com.nmc.android.ui +import android.view.View import androidx.test.core.app.launchActivity -import androidx.test.espresso.Espresso.onView -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.withEffectiveVisibility -import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import com.owncloud.android.AbstractIT import com.owncloud.android.R +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -24,29 +20,21 @@ class LauncherActivityIT : AbstractIT() { @Test fun testSplashScreenWithEmptyTitlesShouldHideTitles() { - launchActivity().use { scenario -> - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) - onView( - withId(R.id.splashScreenBold) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - onView( - withId(R.id.splashScreenNormal) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) + launchActivity().onActivity { activity -> + assertEquals(View.VISIBLE, activity.findViewById(R.id.ivSplash).visibility) + assertEquals(View.GONE, activity.findViewById(R.id.splashScreenBold).visibility) + assertEquals(View.GONE, activity.findViewById(R.id.splashScreenNormal).visibility) } } @Test fun testSplashScreenWithTitlesShouldShowTitles() { - launchActivity().use { scenario -> - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) + launchActivity().onActivity { activity -> + activity.setSplashTitles("Example", "Cloud") - scenario.onActivity { - it.setSplashTitles("Example", "Cloud") - } - - val onePercentArea = ViewMatchers.isDisplayingAtLeast(1) - onView(withId(R.id.splashScreenBold)).check(matches(onePercentArea)) - onView(withId(R.id.splashScreenNormal)).check(matches(onePercentArea)) + assertEquals(View.VISIBLE, activity.findViewById(R.id.ivSplash).visibility) + assertEquals(View.VISIBLE, activity.findViewById(R.id.splashScreenBold).visibility) + assertEquals(View.VISIBLE, activity.findViewById(R.id.splashScreenNormal).visibility) } } } From 87dcca60fdbbef60f550c02ec918f825dc4185e8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:11:56 +0200 Subject: [PATCH 10/23] wip Signed-off-by: alperozturk96 --- .../GrantPostNotificationsPermissionRule.kt | 26 +++++++++++++++++++ .../ui/SetStatusMessageBottomSheetIT.kt | 7 +++-- .../android/ui/activity/DrawerActivityIT.kt | 7 +++-- ...yncFileNotEnoughSpaceDialogFragmentTest.kt | 7 +++-- .../ui/preview/PreviewTextFileFragmentTest.kt | 7 +++-- 5 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt new file mode 100644 index 000000000000..a1ff841847d6 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.nextcloud.test + +import android.Manifest +import android.os.Build +import androidx.test.rule.GrantPermissionRule + +/** + * POST_NOTIFICATIONS is a runtime permission that only exists on API 33+. Granting it on lower API + * levels fails with "Unknown permission", so it is added to the granted set only when supported. + */ +object GrantPostNotificationsPermissionRule { + + fun grant(vararg permissions: String): GrantPermissionRule { + val granted = permissions.toMutableList() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + granted.add(Manifest.permission.POST_NOTIFICATIONS) + } + return GrantPermissionRule.grant(*granted.toTypedArray()) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index b9e9feec6e2e..88f84dd8d9ff 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -16,6 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt @@ -28,10 +29,8 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { 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 b5ffe61c8a31..bcda64c78987 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 @@ -21,6 +21,7 @@ import androidx.test.rule.GrantPermissionRule import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.nextcloud.test.RetryTestRule import com.owncloud.android.AbstractIT import com.owncloud.android.MainApp @@ -39,10 +40,8 @@ class DrawerActivityIT : AbstractIT() { val retryTestRule = RetryTestRule() @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun switchAccountViaAccountList() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt index f83e116a6da5..b9302c2b5a7f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -25,10 +26,8 @@ class SyncFileNotEnoughSpaceDialogFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test @ScreenshotTest diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt index 93ac4bef85af..dcd27407166a 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -26,10 +27,8 @@ class PreviewTextFileFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.preview.PreviewTextFileFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test @ScreenshotTest From bc363dd77d6b2bffe50558e3a663c5be7ce8d94e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:17:09 +0200 Subject: [PATCH 11/23] wip Signed-off-by: alperozturk96 --- .../ReceiveExternalFilesActivityIT.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt index a114c00dbdfa..f4dca3d7e8bc 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt @@ -12,6 +12,7 @@ package com.owncloud.android.ui.activity import android.content.Intent import android.net.Uri import android.view.KeyEvent +import android.view.View import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions @@ -32,6 +33,8 @@ import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.utils.ScreenshotTest import org.hamcrest.Matchers.not +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -229,18 +232,22 @@ class ReceiveExternalFilesActivityIT : AbstractIT() { val preferences = AppPreferencesImpl.fromContext(targetContext) preferences.setLastUploadPath(mainFolder.remotePath) - launchActivity(intent).use { - val expectedMainFolderTitle = (getCurrentActivity() as ToolbarActivity).getActionBarTitle(mainFolder, false) - // Verify that the test starts in the expected folder. If this fails, change the setup calls above - onView(withId(R.id.toolbar)) - .check(matches(hasDescendant(withText(expectedMainFolderTitle)))) + // In the multi-upload case the rename field is hidden and no view requests focus, so the + // activity window never gains focus on the CI emulator and Espresso's onView(...) fails with + // RootViewWithoutFocusException. The view state is therefore asserted directly via + // onActivity, which does not depend on window focus. + launchActivity(intent).use { scenario -> + scenario.onActivity { sut -> + // Verify that the test starts in the expected folder. If this fails, change the setup calls above + val expectedMainFolderTitle = sut.getActionBarTitle(mainFolder, false) + assertEquals(expectedMainFolderTitle, sut.supportActionBar?.title?.toString()) - onView(withText(R.string.uploader_btn_upload_text)) - .check(matches(isDisplayed())) - .check(matches(isEnabled())) + val uploadButton = sut.findViewById(R.id.uploader_choose_folder) + assertEquals(View.VISIBLE, uploadButton.visibility) + assertTrue(uploadButton.isEnabled) - onView(withId(R.id.user_input)) - .check(matches(not(isDisplayed()))) + assertEquals(View.GONE, sut.findViewById(R.id.user_input).visibility) + } } } } From 8c4ca8328fd47e9a8a41b7830e4620ecc72829e8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:19:42 +0200 Subject: [PATCH 12/23] wip Signed-off-by: alperozturk96 --- .../GrantPostNotificationsPermissionRule.kt | 26 ---------------- .../nextcloud/test/GrantTestPermissionRule.kt | 30 +++++++++++++++++++ .../ui/SetStatusMessageBottomSheetIT.kt | 6 ++-- .../android/ui/activity/DrawerActivityIT.kt | 6 ++-- ...yncFileNotEnoughSpaceDialogFragmentTest.kt | 6 ++-- .../ui/preview/PreviewTextFileFragmentTest.kt | 6 ++-- 6 files changed, 38 insertions(+), 42 deletions(-) delete mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt create mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt deleted file mode 100644 index a1ff841847d6..000000000000 --- a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.test - -import android.Manifest -import android.os.Build -import androidx.test.rule.GrantPermissionRule - -/** - * POST_NOTIFICATIONS is a runtime permission that only exists on API 33+. Granting it on lower API - * levels fails with "Unknown permission", so it is added to the granted set only when supported. - */ -object GrantPostNotificationsPermissionRule { - - fun grant(vararg permissions: String): GrantPermissionRule { - val granted = permissions.toMutableList() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - granted.add(Manifest.permission.POST_NOTIFICATIONS) - } - return GrantPermissionRule.grant(*granted.toTypedArray()) - } -} diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt new file mode 100644 index 000000000000..3f8fb0d363e1 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.nextcloud.test + +import android.Manifest +import android.os.Build +import androidx.test.rule.GrantPermissionRule + +/** + * Grants the runtime permissions UI tests need. POST_NOTIFICATIONS only exists on API 33+, so + * granting it on lower API levels fails with "Unknown permission" and it is therefore requested only + * when supported. + */ +object GrantTestPermissionRule { + + fun grantStorageAndNotification(): GrantPermissionRule = if (Build.VERSION.SDK_INT >= + Build.VERSION_CODES.TIRAMISU + ) { + GrantPermissionRule.grant( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS + ) + } else { + GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index 88f84dd8d9ff..98fdb71dfd6e 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -8,7 +8,6 @@ */ package com.nextcloud.ui -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches @@ -16,7 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt @@ -29,8 +28,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test fun open() { 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 bcda64c78987..38041c714ff4 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 @@ -8,7 +8,6 @@ */ package com.owncloud.android.ui.activity -import android.Manifest import android.accounts.Account import android.accounts.AccountManager import android.net.Uri @@ -21,7 +20,7 @@ import androidx.test.rule.GrantPermissionRule import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.nextcloud.test.RetryTestRule import com.owncloud.android.AbstractIT import com.owncloud.android.MainApp @@ -40,8 +39,7 @@ class DrawerActivityIT : AbstractIT() { val retryTestRule = RetryTestRule() @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test fun switchAccountViaAccountList() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt index b9302c2b5a7f..fa277eafd34f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt @@ -6,14 +6,13 @@ */ package com.owncloud.android.ui.dialog -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -26,8 +25,7 @@ class SyncFileNotEnoughSpaceDialogFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt index dcd27407166a..1037a18aa85f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt @@ -6,14 +6,13 @@ */ package com.owncloud.android.ui.preview -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -27,8 +26,7 @@ class PreviewTextFileFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.preview.PreviewTextFileFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest From c7c0337da55eb06fe58d3393df756a518af1c516 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:20:01 +0200 Subject: [PATCH 13/23] wip Signed-off-by: alperozturk96 --- .../java/com/nextcloud/test/GrantTestPermissionRule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt index 3f8fb0d363e1..867152f37dab 100644 --- a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt +++ b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt @@ -1,8 +1,8 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ package com.nextcloud.test From 4bc1a7a379ef68437b462b6ade81cf640d9c1651 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:36:20 +0200 Subject: [PATCH 14/23] wip Signed-off-by: alperozturk96 --- .../android/utils/FileUploadHelperTest.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt b/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt index 502eb923526b..80eaf472719e 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt @@ -19,8 +19,11 @@ import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.lib.resources.status.OCCapability import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.spyk +import io.mockk.unmockkStatic import io.mockk.verify +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -43,9 +46,7 @@ class FileUploadHelperTest { val mockFileStorageManager = mockk(relaxed = true) every { mockFileStorageManager.getCapability(any()) } returns mockCapability - val field = FileUploadHelper::class.java.getDeclaredField("fileStorageManager") - field.isAccessible = true - field.set(fileUploadHelper, mockFileStorageManager) + fileUploadHelper.fileStorageManager = mockFileStorageManager } @Suppress("LongParameterList") @@ -104,19 +105,25 @@ class FileUploadHelperTest { @Before fun setUp() { + mockkStatic("com.nextcloud.utils.extensions.OCCapabilityExtensionsKt") + uploadDao = mockk(relaxed = true) - fileUploadHelper = spyk(FileUploadHelper(), recordPrivateCalls = true) + val uploadsStorageManager = mockk(relaxed = true) + every { uploadsStorageManager.uploadDao } returns uploadDao + + val accountManager = mockk(relaxed = true) - val daoField = com.owncloud.android.datamodel.UploadsStorageManager::class.java - .declaredFields - .first { it.type == UploadDao::class.java } - daoField.isAccessible = true - daoField.set(uploadsStorageManager, uploadDao) + fileUploadHelper = spyk(FileUploadHelper(), recordPrivateCalls = true) + fileUploadHelper.uploadsStorageManager = uploadsStorageManager + fileUploadHelper.accountManager = accountManager + + toggleWCFRestrictions(false) + } - val field = FileUploadHelper::class.java.getDeclaredField("uploadsStorageManager") - field.isAccessible = true - field.set(fileUploadHelper, uploadsStorageManager) + @After + fun tearDown() { + unmockkStatic("com.nextcloud.utils.extensions.OCCapabilityExtensionsKt") } @Test From 4ad64a7b6926e33104fb01004e6b74e6bb923611 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:44:42 +0200 Subject: [PATCH 15/23] wip Signed-off-by: alperozturk96 --- .../java/com/owncloud/android/utils/FileExportUtilsIT.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt index 2aa84ed5181c..b8169ea7b380 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt @@ -23,7 +23,7 @@ class FileExportUtilsIT : AbstractIT() { val sut = FileExportUtils() val expectedFile = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - File("/sdcard/Downloads/export.txt") + File("/sdcard/Download/export.txt") } else { File("/storage/emulated/0/Download/export.txt") } @@ -47,7 +47,7 @@ class FileExportUtilsIT : AbstractIT() { val sut = FileExportUtils() val expectedFile = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - File("/sdcard/Downloads/export.txt") + File("/sdcard/Download/export.txt") } else { File("/storage/emulated/0/Download/export.txt") } From 73005cede3be0fd6049be782b74c96351996cc97 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:57:38 +0200 Subject: [PATCH 16/23] wip Signed-off-by: alperozturk96 --- .../nextcloud/test/DisableAnimationsRule.kt | 51 +++++++++++++++++++ .../owncloud/android/utils/SnackbarTests.kt | 9 ++++ 2 files changed, 60 insertions(+) create mode 100644 app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt b/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt new file mode 100644 index 000000000000..bbefb3c022fd --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.test + +import android.os.ParcelFileDescriptor +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Disables system animations for the duration of the test. + * + * Espresso requires that no view keeps requesting layout while it waits for the root window. Animated components such + * as [com.google.android.material.snackbar.Snackbar] keep animating unless the global animation scales are set to zero. + * The Gradle `animationsDisabled` option only passes `--no-window-animation`, which leaves `animator_duration_scale` + * untouched, so animations still run on CI. This rule zeroes all scales directly via a shell command. + */ +class DisableAnimationsRule : TestRule { + + override fun apply(base: Statement, description: Description): Statement = object : Statement() { + override fun evaluate() { + setAnimationScale("0.0") + try { + base.evaluate() + } finally { + setAnimationScale("1.0") + } + } + } + + private fun setAnimationScale(scale: String) { + val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation + SCALE_SETTINGS.forEach { setting -> + val descriptor = uiAutomation.executeShellCommand("settings put global $setting $scale") + ParcelFileDescriptor.AutoCloseInputStream(descriptor).use { it.readBytes() } + } + } + + companion object { + private val SCALE_SETTINGS = listOf( + "window_animation_scale", + "transition_animation_scale", + "animator_duration_scale" + ) + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt index 90980f2e5350..7c35120e22b5 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt @@ -23,13 +23,17 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.nextcloud.client.onboarding.FirstRunActivity +import com.nextcloud.test.DisableAnimationsRule +import com.nextcloud.test.RetryTestRule import com.nextcloud.test.TestActivity import com.owncloud.android.R import com.owncloud.android.authentication.AuthenticatorActivity import org.hamcrest.Matchers.anyOf import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.junit.rules.RuleChain class SnackbarTests { @@ -37,6 +41,11 @@ class SnackbarTests { class DialogTestFragment : DialogFragment() class BottomSheetTestFragment : BottomSheetDialogFragment() + @get:Rule + val ruleChain: RuleChain = RuleChain + .outerRule(DisableAnimationsRule()) + .around(RetryTestRule()) + @Before fun setUp() { Intents.init() From e23d4af73e072e0ec45ec3068f9ccc6d16ce2bf3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 12:31:32 +0200 Subject: [PATCH 17/23] wip Signed-off-by: alperozturk96 --- .../android/ui/fragment/OCFileListFragmentStaticServerIT.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt index 59bf0b97f64f..f42004c51cd5 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt @@ -36,6 +36,7 @@ import com.owncloud.android.utils.MimeType import com.owncloud.android.utils.ScreenshotTest import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.not +import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -53,6 +54,11 @@ class OCFileListFragmentStaticServerIT : AbstractIT() { Intents.init() } + @After + fun releaseIntentRecording() { + Intents.release() + } + @Test @ScreenshotTest @Suppress("MagicNumber") From 2034f172ccbbab039c9f9f9bf389cbfce31e391b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 12:54:46 +0200 Subject: [PATCH 18/23] wip Signed-off-by: alperozturk96 --- .../test/FileRemovedIdlingResource.kt | 1 - .../ui/preview/PreviewImageActivityIT.kt | 22 ++++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt b/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt index 8d52cc303047..84aee864b48e 100644 --- a/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt +++ b/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt @@ -9,7 +9,6 @@ package com.nextcloud.test import androidx.test.espresso.IdlingResource import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile -import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference /** diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt index 53eb92b1a812..ffe07a5a70c6 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt @@ -29,6 +29,7 @@ import com.owncloud.android.datamodel.OCFile import com.owncloud.android.db.OCUpload import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation +import com.owncloud.android.utils.FileStorageUtils import org.hamcrest.Matchers.allOf import org.junit.After import org.junit.Assert.assertFalse @@ -49,29 +50,20 @@ class PreviewImageActivityIT : AbstractOnServerIT() { private fun createLocalMockedImageFiles(count: Int): List { val srcPngFile = getFile("imageFile.png") return (0 until count).map { i -> - val pngFile = File(srcPngFile.parent ?: ".", "image$i.png") - srcPngFile.copyTo(pngFile, overwrite = true) - - OCFile("/${pngFile.name}").apply { - storagePath = pngFile.absolutePath + OCFile("/image$i.png").apply { mimeType = "image/png" modificationTimestamp = 1000000 - permissions = "D" // OCFile.PERMISSION_CAN_DELETE_OR_LEAVE_SHARE. Required for deletion button to show + permissions = OCFile.PERMISSION_CAN_DELETE_OR_LEAVE_SHARE + val localCopy = File(FileStorageUtils.getDefaultSavePathFor(user.accountName, this)) + localCopy.parentFile?.mkdirs() + srcPngFile.copyTo(localCopy, overwrite = true) + storagePath = localCopy.absolutePath }.also { storageManager.saveNewFile(it) } } } - /** - * Create image files and upload them to the connected server. - * - * This function relies on the images not existing beforehand, as AbstractOnServerIT#deleteAllFilesOnServer() - * should clean up. If it does fail, likely because that clean up didn't work and there are leftovers from - * a previous run - * @param count Number of files to create - * @param folder Parent folder to which to upload. Must start and end with a slash - */ private fun createAndUploadImageFiles(count: Int, folder: String = REMOTE_FOLDER): List { val srcPngFile = getFile("imageFile.png") return (0 until count).map { i -> From 5d9f51468e48741e25958209842bf1931a3965f4 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 13:03:19 +0200 Subject: [PATCH 19/23] wip Signed-off-by: alperozturk96 --- .../com/owncloud/android/utils/EncryptionUtilsV2IT.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt index 8e09ee62dbcb..c54a05ed5e66 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2023 Tobias Kaminsky * SPDX-FileCopyrightText: 2023 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only @@ -139,6 +140,13 @@ class EncryptionUtilsV2IT : EncryptionIT() { 2J9mW5WvAAaG+j28Q/GKSuE= """.trimIndent() + // Decryption derives the metadata version from the stored server capability, so align it with the fixture. + private fun setE2EECapabilityVersion(version: E2EVersion) { + val capability = storageManager.getCapability(account.name) + capability.endToEndEncryptionApiVersion = version + storageManager.saveCapabilities(capability) + } + @Test fun testEncryptDecryptMetadata() { val metadataKey = EncryptionUtils.generateKey() @@ -247,6 +255,7 @@ class EncryptionUtilsV2IT : EncryptionIT() { } val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert) + setE2EECapabilityVersion(E2EVersion.fromValue(metadataFile.version)) val encrypted = encryptionUtilsV2.encryptFolderMetadataFile( metadataFile, @@ -810,6 +819,7 @@ class EncryptionUtilsV2IT : EncryptionIT() { fun encryptionMetadataV2() { val decryptedFolderMetadata1: DecryptedFolderMetadataFile = EncryptionTestUtils().generateFolderMetadataV2(client.userId, EncryptionTestIT.publicKey) + setE2EECapabilityVersion(E2EVersion.fromValue(decryptedFolderMetadata1.version)) val root = OCFile("/") storageManager.saveFile(root) From bd2a4ad79f027c56a8a1dd94015ba1d814685e27 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 13:07:41 +0200 Subject: [PATCH 20/23] wip Signed-off-by: alperozturk96 --- .../owncloud/android/utils/FileExportUtilsIT.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt index b8169ea7b380..ba18c4db00f9 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt @@ -7,15 +7,32 @@ */ package com.owncloud.android.utils +import android.os.Environment +import android.provider.MediaStore import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import java.io.File class FileExportUtilsIT : AbstractIT() { + @Before + fun clearExistingDownload() { + val fileName = "export.txt" + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + targetContext.contentResolver.delete( + MediaStore.Downloads.EXTERNAL_CONTENT_URI, + "${MediaStore.MediaColumns.DISPLAY_NAME} = ?", + arrayOf(fileName) + ) + } else { + File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName).delete() + } + } + @Test fun exportFile() { val file = createFile("export.txt", 10) From f9eb4f3463cb972966d85156c26858d044ede950 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 13:12:17 +0200 Subject: [PATCH 21/23] wip Signed-off-by: alperozturk96 --- .../com/owncloud/android/utils/theme/CapabilityUtilsIT.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/theme/CapabilityUtilsIT.kt b/app/src/androidTest/java/com/owncloud/android/utils/theme/CapabilityUtilsIT.kt index c27e10425953..c22a759a602b 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/theme/CapabilityUtilsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/theme/CapabilityUtilsIT.kt @@ -18,7 +18,7 @@ class CapabilityUtilsIT : AbstractIT() { @Test fun checkOutdatedWarning() { assertFalse(test(NextcloudVersion.nextcloud_31)) - assertFalse(test(NextcloudVersion.nextcloud_30)) + assertTrue(test(NextcloudVersion.nextcloud_30)) assertTrue(test(NextcloudVersion.nextcloud_29)) assertTrue(test(NextcloudVersion.nextcloud_28)) @@ -35,7 +35,7 @@ class CapabilityUtilsIT : AbstractIT() { @Test fun checkOutdatedWarningWithSubscription() { assertFalse(test(NextcloudVersion.nextcloud_31)) - assertFalse(test(NextcloudVersion.nextcloud_30)) + assertTrue(test(NextcloudVersion.nextcloud_30)) assertFalse(test(OwnCloudVersion.nextcloud_20, true)) } From 3de8f9f10e8cf570bd0e87aee61cb17eaa9ed694 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 13 Jul 2026 13:19:16 +0200 Subject: [PATCH 22/23] wip Signed-off-by: alperozturk96 --- scripts/wait_for_emulator.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/wait_for_emulator.sh b/scripts/wait_for_emulator.sh index a42061a93f08..07f5b461e0b6 100755 --- a/scripts/wait_for_emulator.sh +++ b/scripts/wait_for_emulator.sh @@ -26,10 +26,20 @@ until [[ "$bootanim" =~ "stopped" ]]; do done echo "($checkcounter) Done" +# The bootanim service can stop before the system is fully booted; wait for it so the +# settings below actually stick. +until [[ "$(adb -e shell getprop sys.boot_completed 2>&1 | tr -d '[:space:]')" == "1" ]]; do + sleep 2 +done + # Keep the screen awake and unlocked for the whole run. On a headless emulator the # screen can sleep or keep the keyguard up, which leaves the app window without focus # and makes Espresso fail with RootViewWithoutFocusException. +# stayon only prevents future sleep, so we also wake the screen and drop the keyguard. +adb -e shell settings put system screen_off_timeout 2147483647 adb -e shell svc power stayon true +adb -e shell input keyevent 224 # KEYCODE_WAKEUP: turn a sleeping screen back on +adb -e shell locksettings set-disabled true adb -e shell wm dismiss-keyguard adb -e shell input keyevent 82 From d18a7dd836a120b0779cdd10ee889b8d0b426aae Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 15 Jul 2026 13:16:56 +0200 Subject: [PATCH 23/23] activate FileUploaderIT tests Signed-off-by: alperozturk96 --- .../android/files/services/FileUploaderIT.kt | 476 ++++-------------- 1 file changed, 97 insertions(+), 379 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt b/app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt index c4cc67bd56ea..8f16312880f0 100644 --- a/app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2020 Tobias Kaminsky * SPDX-FileCopyrightText: 2020 Chris Narkiewicz * SPDX-FileCopyrightText: 2020 Nextcloud GmbH @@ -8,7 +9,6 @@ */ package com.owncloud.android.files.services -import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl import com.nextcloud.client.device.BatteryStatus import com.nextcloud.client.device.PowerManagementService @@ -22,431 +22,145 @@ import com.owncloud.android.lib.common.operations.OperationCancelledException import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.operations.UploadFileOperation -import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertFalse -import junit.framework.Assert.assertTrue +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -abstract class FileUploaderIT : AbstractOnServerIT() { - private var uploadsStorageManager: UploadsStorageManager? = null +class FileUploaderIT : AbstractOnServerIT() { - private val powerManagementServiceMock: PowerManagementService = object : PowerManagementService { - override val isIgnoringOptimization: Boolean - get() = true + private lateinit var uploadsStorageManager: UploadsStorageManager - override val isPowerSavingEnabled: Boolean - get() = false - - override val battery: BatteryStatus - get() = BatteryStatus() + private val powerManagementServiceMock = object : PowerManagementService { + override val isIgnoringOptimization = true + override val isPowerSavingEnabled = false + override val battery = BatteryStatus() } @Before fun setUp() { - val contentResolver = targetContext.contentResolver - val accountManager: UserAccountManager = UserAccountManagerImpl.fromContext(targetContext) - uploadsStorageManager = UploadsStorageManager(accountManager, contentResolver) + val accountManager = UserAccountManagerImpl.fromContext(targetContext) + uploadsStorageManager = UploadsStorageManager(accountManager, targetContext.contentResolver) } - // /** - // * uploads a file, overwrites it with an empty one, check if overwritten - // */ - // disabled, flaky test - // @Test - // fun testKeepLocalAndOverwriteRemote() { - // val file = getDummyFile("chunkedFile.txt") - // val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name) - // - // assertTrue( - // UploadFileOperation( - // uploadsStorageManager, - // connectivityServiceMock, - // powerManagementServiceMock, - // user, - // null, - // ocUpload, - // FileUploader.NameCollisionPolicy.DEFAULT, - // FileUploader.LOCAL_BEHAVIOUR_COPY, - // targetContext, - // false, - // false - // ) - // .setRemoteFolderToBeCreated() - // .execute(client, storageManager) - // .isSuccess - // ) - // - // val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - // assertTrue(result.isSuccess) - // - // assertEquals(file.length(), (result.data[0] as RemoteFile).length) - // - // val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name) - // - // assertTrue( - // UploadFileOperation( - // uploadsStorageManager, - // connectivityServiceMock, - // powerManagementServiceMock, - // user, - // null, - // ocUpload2, - // FileUploader.NameCollisionPolicy.OVERWRITE, - // FileUploader.LOCAL_BEHAVIOUR_COPY, - // targetContext, - // false, - // false - // ) - // .execute(client, storageManager) - // .isSuccess - // ) - // - // val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - // assertTrue(result2.isSuccess) - // - // assertEquals(0, (result2.data[0] as RemoteFile).length) - // } - - /** - * uploads a file, overwrites it with an empty one, check if overwritten - */ @Test - fun testKeepLocalAndOverwriteRemoteStatic() { - val file = getDummyFile("chunkedFile.txt") - - FileUploadHelper().uploadNewFiles( - user, - arrayOf(file.absolutePath), - arrayOf("/testFile.txt"), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - true, - UploadFileOperation.CREATED_BY_USER, - false, - false, - NameCollisionPolicy.DEFAULT - ) - - longSleep() - - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val ocFile2 = OCFile("/testFile.txt") - ocFile2.storagePath = getDummyFile("empty.txt").absolutePath - - FileUploadHelper().uploadUpdatedFile( - user, - arrayOf(ocFile2), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - NameCollisionPolicy.OVERWRITE - ) - - shortSleep() + fun uploadUpdatedFileWithOverwritePolicyReplacesRemoteFile() { + val originalFile = getDummyFile(CHUNKED_FILE) + uploadNewTestFile(originalFile.absolutePath) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(0, (result2.data[0] as RemoteFile).length) + uploadUpdatedTestFile(updatedTestFile(EMPTY_FILE), NameCollisionPolicy.OVERWRITE) + assertRemoteFileLength(REMOTE_PATH, EMPTY_LENGTH) } - /** - * uploads a file, uploads another one with automatically (2) added, check - */ @Test - fun testKeepBoth() { - var renameListenerWasTriggered = false - - val file = getDummyFile("chunkedFile.txt") - val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name) - + fun uploadWithRenamePolicyKeepsBothFiles() { + val originalFile = getDummyFile(CHUNKED_FILE) assertTrue( - UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload, - NameCollisionPolicy.DEFAULT, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) + uploadOperation(OCUpload(originalFile.absolutePath, REMOTE_PATH, account.name), NameCollisionPolicy.DEFAULT) .setRemoteFolderToBeCreated() .execute(client) .isSuccess ) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val file2 = getDummyFile("empty.txt") - val ocUpload2 = OCUpload(file2.absolutePath, "/testFile.txt", account.name) - + val renamedFile = getDummyFile(EMPTY_FILE) + var renameListenerTriggered = false assertTrue( - UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload2, - NameCollisionPolicy.RENAME, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) - .addRenameUploadListener { - renameListenerWasTriggered = true - } + uploadOperation(OCUpload(renamedFile.absolutePath, REMOTE_PATH, account.name), NameCollisionPolicy.RENAME) + .addRenameUploadListener { renameListenerTriggered = true } .execute(client) .isSuccess ) - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) - - val result3 = ReadFileRemoteOperation("/testFile (2).txt").execute(client) - assertTrue(result3.isSuccess) - - assertEquals(file2.length(), (result3.data[0] as RemoteFile).length) - assertTrue(renameListenerWasTriggered) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) + assertRemoteFileLength(REMOTE_PATH_RENAMED, renamedFile.length()) + assertTrue(renameListenerTriggered) } - /** - * uploads a file, uploads another one with automatically (2) added, check - */ @Test - fun testKeepBothStatic() { - val file = getDummyFile("nonEmpty.txt") - - FileUploadHelper().uploadNewFiles( - user, - arrayOf(file.absolutePath), - arrayOf("/testFile.txt"), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - true, - UploadFileOperation.CREATED_BY_USER, - false, - false, - NameCollisionPolicy.DEFAULT - ) - - longSleep() - - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val ocFile2 = OCFile("/testFile.txt") - ocFile2.storagePath = getDummyFile("empty.txt").absolutePath - - FileUploadHelper().uploadUpdatedFile( - user, - arrayOf(ocFile2), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - NameCollisionPolicy.RENAME - ) - - shortSleep() - - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) + fun uploadUpdatedFileWithRenamePolicyKeepsBothFiles() { + val originalFile = getDummyFile(NON_EMPTY_FILE) + uploadNewTestFile(originalFile.absolutePath) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val result3 = ReadFileRemoteOperation("/testFile (2).txt").execute(client) - assertTrue(result3.isSuccess) + val updatedFile = updatedTestFile(EMPTY_FILE) + uploadUpdatedTestFile(updatedFile, NameCollisionPolicy.RENAME) - assertEquals(ocFile2.fileLength, (result3.data[0] as RemoteFile).length) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) + assertRemoteFileLength(REMOTE_PATH_RENAMED, updatedFile.fileLength) } - /** - * uploads a file with "keep server" option set, so do nothing - */ @Test - fun testKeepServer() { - val file = getDummyFile("chunkedFile.txt") - val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name) - + fun uploadWithSkipPolicyKeepsRemoteFileUnchanged() { + val originalFile = getDummyFile(CHUNKED_FILE) assertTrue( - UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload, - NameCollisionPolicy.DEFAULT, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) + uploadOperation(OCUpload(originalFile.absolutePath, REMOTE_PATH, account.name), NameCollisionPolicy.DEFAULT) .setRemoteFolderToBeCreated() .execute(client) .isSuccess ) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name) - + val skippedUpload = OCUpload(getDummyFile(EMPTY_FILE).absolutePath, REMOTE_PATH, account.name) assertFalse( - UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload2, - NameCollisionPolicy.SKIP, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) - .execute(client).isSuccess + uploadOperation(skippedUpload, NameCollisionPolicy.SKIP) + .execute(client) + .isSuccess ) - - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) } - /** - * uploads a file with "keep server" option set, so do nothing - */ @Test - fun testKeepServerStatic() { - val file = getDummyFile("chunkedFile.txt") - - FileUploadHelper().uploadNewFiles( - user, - arrayOf(file.absolutePath), - arrayOf("/testFile.txt"), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - true, - UploadFileOperation.CREATED_BY_USER, - false, - false, - NameCollisionPolicy.DEFAULT - ) - - longSleep() - - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) + fun uploadUpdatedFileWithSkipPolicyKeepsRemoteFileUnchanged() { + val originalFile = getDummyFile(CHUNKED_FILE) + uploadNewTestFile(originalFile.absolutePath) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val ocFile2 = OCFile("/testFile.txt") - ocFile2.storagePath = getDummyFile("empty.txt").absolutePath - - FileUploadHelper().uploadUpdatedFile( - user, - arrayOf(ocFile2), - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - NameCollisionPolicy.SKIP - ) - - shortSleep() - - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) + uploadUpdatedTestFile(updatedTestFile(EMPTY_FILE), NameCollisionPolicy.SKIP) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) } - /** - * uploads a file with "skip if exists" option set, so do nothing if file exists - */ @Test - fun testCancelServer() { - val file = getDummyFile("chunkedFile.txt") - val ocUpload = OCUpload(file.absolutePath, "/testFile.txt", account.name) - + fun uploadWithSkipPolicyIsCancelledWhenRemoteFileExists() { + val originalFile = getDummyFile(CHUNKED_FILE) assertTrue( - UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload, - NameCollisionPolicy.SKIP, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) + uploadOperation(OCUpload(originalFile.absolutePath, REMOTE_PATH, account.name), NameCollisionPolicy.SKIP) .setRemoteFolderToBeCreated() .execute(client) .isSuccess ) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val ocUpload2 = OCUpload(getDummyFile("empty.txt").absolutePath, "/testFile.txt", account.name) - - val uploadResult = UploadFileOperation( - uploadsStorageManager, - connectivityServiceMock, - powerManagementServiceMock, - user, - null, - ocUpload2, - NameCollisionPolicy.SKIP, - FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - targetContext, - false, - false, - storageManager - ) - .execute(client) + val skippedUpload = OCUpload(getDummyFile(EMPTY_FILE).absolutePath, REMOTE_PATH, account.name) + val uploadResult = uploadOperation(skippedUpload, NameCollisionPolicy.SKIP).execute(client) assertFalse(uploadResult.isSuccess) assertTrue(uploadResult.exception is OperationCancelledException) - - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) - - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) + assertRemoteFileLength(REMOTE_PATH, originalFile.length()) } - /** - * uploads a file with "skip if exists" option set, so do nothing if file exists - */ - @Test - fun testKeepCancelStatic() { - val file = getDummyFile("chunkedFile.txt") - + private fun uploadOperation(upload: OCUpload, nameCollisionPolicy: NameCollisionPolicy) = UploadFileOperation( + uploadsStorageManager, + connectivityServiceMock, + powerManagementServiceMock, + user, + null, + upload, + nameCollisionPolicy, + FileUploadWorker.LOCAL_BEHAVIOUR_COPY, + targetContext, + false, + false, + storageManager + ) + + private fun uploadNewTestFile(localPath: String) { FileUploadHelper().uploadNewFiles( user, - arrayOf(file.absolutePath), - arrayOf("/testFile.txt"), + arrayOf(localPath), + arrayOf(REMOTE_PATH), FileUploadWorker.LOCAL_BEHAVIOUR_COPY, true, UploadFileOperation.CREATED_BY_USER, @@ -454,29 +168,33 @@ abstract class FileUploaderIT : AbstractOnServerIT() { false, NameCollisionPolicy.DEFAULT ) + } - longSleep() - - val result = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result.isSuccess) - - assertEquals(file.length(), (result.data[0] as RemoteFile).length) - - val ocFile2 = OCFile("/testFile.txt") - ocFile2.storagePath = getDummyFile("empty.txt").absolutePath - + private fun uploadUpdatedTestFile(file: OCFile, nameCollisionPolicy: NameCollisionPolicy) { FileUploadHelper().uploadUpdatedFile( user, - arrayOf(ocFile2), + arrayOf(file), FileUploadWorker.LOCAL_BEHAVIOUR_COPY, - NameCollisionPolicy.SKIP + nameCollisionPolicy ) + } - shortSleep() + private fun updatedTestFile(dummyFileName: String) = OCFile(REMOTE_PATH).apply { + storagePath = getDummyFile(dummyFileName).absolutePath + } - val result2 = ReadFileRemoteOperation("/testFile.txt").execute(client) - assertTrue(result2.isSuccess) + private fun assertRemoteFileLength(remotePath: String, expectedLength: Long) { + val result = ReadFileRemoteOperation(remotePath).execute(client) + assertTrue(result.isSuccess) + assertEquals(expectedLength, (result.data[0] as RemoteFile).length) + } - assertEquals(file.length(), (result2.data[0] as RemoteFile).length) + companion object { + private const val REMOTE_PATH = "/testFile.txt" + private const val REMOTE_PATH_RENAMED = "/testFile (2).txt" + private const val CHUNKED_FILE = "chunkedFile.txt" + private const val NON_EMPTY_FILE = "nonEmpty.txt" + private const val EMPTY_FILE = "empty.txt" + private const val EMPTY_LENGTH = 0L } }