@@ -22,20 +22,34 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot
2222import androidx.test.espresso.matcher.ViewMatchers.withId
2323import androidx.test.espresso.matcher.ViewMatchers.withText
2424import com.nextcloud.test.ConnectivityServiceOfflineMock
25+ import com.nextcloud.test.FileRemovedIdlingResource
2526import com.nextcloud.test.LoopFailureHandler
2627import com.owncloud.android.AbstractOnServerIT
2728import com.owncloud.android.R
2829import com.owncloud.android.datamodel.OCFile
30+ import com.owncloud.android.db.OCUpload
31+ import com.owncloud.android.files.services.NameCollisionPolicy
32+ import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation
2933import org.hamcrest.Matchers.allOf
34+ import org.junit.After
35+ import org.junit.Assert.assertFalse
36+ import org.junit.Assert.assertTrue
37+ import org.junit.Before
38+ import org.junit.Ignore
3039import org.junit.Test
3140import java.io.File
3241
3342class PreviewImageActivityIT : AbstractOnServerIT () {
34- lateinit var testFiles: List <OCFile >
43+ companion object {
44+ private const val REMOTE_FOLDER : String = " /PreviewImageActivityIT/"
45+ }
46+
47+ var fileRemovedIdlingResource = FileRemovedIdlingResource (storageManager)
3548
36- fun createMockedImageFiles (count : Int , localOnly : Boolean ) {
49+ @Suppress(" SameParameterValue" )
50+ private fun createLocalMockedImageFiles (count : Int ): List <OCFile > {
3751 val srcPngFile = getFile(" imageFile.png" )
38- testFiles = (0 until count).map { i ->
52+ return (0 until count).map { i ->
3953 val pngFile = File (srcPngFile.parent ? : " ." , " image$i .png" )
4054 srcPngFile.copyTo(pngFile, overwrite = true )
4155
@@ -44,27 +58,56 @@ class PreviewImageActivityIT : AbstractOnServerIT() {
4458 mimeType = " image/png"
4559 modificationTimestamp = 1000000
4660 permissions = " D" // OCFile.PERMISSION_CAN_DELETE_OR_LEAVE_SHARE. Required for deletion button to show
47- remoteId = if (localOnly) null else " abc-mocked-remote-id" // mocking the file to be on the server
4861 }.also {
4962 storageManager.saveNewFile(it)
5063 }
5164 }
5265 }
5366
54- fun veryImageThenDelete (index : Int ) {
55- val currentFileName = testFiles[index].fileName
67+ /* *
68+ * Create image files and upload them to the connected server.
69+ *
70+ * This function relies on the images not existing beforehand, as AbstractOnServerIT#deleteAllFilesOnServer()
71+ * should clean up. If it does fail, likely because that clean up didn't work and there are leftovers from
72+ * a previous run
73+ * @param count Number of files to create
74+ * @param folder Parent folder to which to upload. Must start and end with a slash
75+ */
76+ private fun createAndUploadImageFiles (count : Int , folder : String = REMOTE_FOLDER ): List <OCFile > {
77+ val srcPngFile = getFile(" imageFile.png" )
78+ return (0 until count).map { i ->
79+ val pngFile = File (srcPngFile.parent ? : " ." , " image$i .png" )
80+ srcPngFile.copyTo(pngFile, overwrite = true )
81+
82+ val ocUpload = OCUpload (
83+ pngFile.absolutePath,
84+ folder + pngFile.name,
85+ account.name
86+ ).apply {
87+ nameCollisionPolicy = NameCollisionPolicy .OVERWRITE
88+ }
89+ uploadOCUpload(ocUpload)
90+
91+ fileDataStorageManager.getFileByDecryptedRemotePath(folder + pngFile.name)!!
92+ }
93+ }
94+
95+ private fun veryImageThenDelete (testFile : OCFile ) {
5696 Espresso .setFailureHandler(
57- LoopFailureHandler (targetContext, " Test failed with image file index $index , $currentFileName " )
97+ LoopFailureHandler (targetContext, " Test failed with image file ${testFile.fileName} " )
5898 )
5999
100+ assertTrue(testFile.exists())
101+ assertTrue(testFile.fileExists())
102+
60103 onView(withId(R .id.image))
61104 .check(matches(isDisplayed()))
62105
63106 // Check that the Action Bar shows the file name as title
64107 onView(
65108 allOf(
66109 isDescendantOfA(isAssignableFrom(ActionBarContainer ::class .java)),
67- withText(currentFileName )
110+ withText(testFile.fileName )
68111 )
69112 ).check(matches(isDisplayed()))
70113
@@ -82,7 +125,7 @@ class PreviewImageActivityIT : AbstractOnServerIT() {
82125 onView(withText(R .string.common_remove)).perform(ViewActions .click())
83126
84127 // Check confirmation dialog and then confirm the deletion by clicking the main button of the dialog
85- val expectedText = targetContext.getString(R .string.confirmation_remove_file_alert, currentFileName )
128+ val expectedText = targetContext.getString(R .string.confirmation_remove_file_alert, testFile.fileName )
86129 onView(withId(android.R .id.message))
87130 .inRoot(isDialog())
88131 .check(matches(withText(expectedText)))
@@ -92,43 +135,80 @@ class PreviewImageActivityIT : AbstractOnServerIT() {
92135 .check(matches(withText(R .string.file_delete)))
93136 .perform(ViewActions .click())
94137
138+ // Register the idling resource to wait for successful deletion
139+ fileRemovedIdlingResource.setFile(testFile)
140+
141+ // Wait for idle, then verify that the file is gone. Somehow waitForIdleSync() doesn't work and we need onIdle()
142+ Espresso .onIdle()
143+ assertFalse(" test file still exists: ${testFile.fileName} " , testFile.exists())
144+
95145 Espresso .setFailureHandler(DefaultFailureHandler (targetContext))
96146 }
97147
98- @Test
99- fun deleteFromSlideshow_localOnly_online () {
148+ @Before
149+ fun bringUp () {
150+ IdlingRegistry .getInstance().register(fileRemovedIdlingResource)
151+ }
152+
153+ @After
154+ fun tearDown () {
155+ IdlingRegistry .getInstance().unregister(fileRemovedIdlingResource)
156+ }
157+
158+ private fun testDeleteFromSlideshow_impl (localOnly : Boolean , offline : Boolean ) {
100159 // Prepare local test data
101160 val imageCount = 5
102- createMockedImageFiles(imageCount, localOnly = true )
161+ val testFiles = if (localOnly) {
162+ createLocalMockedImageFiles(
163+ imageCount
164+ )
165+ } else {
166+ createAndUploadImageFiles(imageCount)
167+ }
103168
104169 // Launch the activity with the first image
105170 val intent = PreviewImageActivity .previewFileIntent(targetContext, user, testFiles[0 ])
106- launchActivity<PreviewImageActivity >(intent).use {
171+ launchActivity<PreviewImageActivity >(intent).use { scenario ->
172+ if (offline) {
173+ scenario.onActivity { activity ->
174+ activity.connectivityService = ConnectivityServiceOfflineMock ()
175+ }
176+ }
107177 onView(isRoot()).check(matches(isDisplayed()))
108178
109- for (i in 0 until imageCount) {
110- veryImageThenDelete(i)
179+ for (testFile in testFiles) {
180+ veryImageThenDelete(testFile)
181+ assertTrue(
182+ " Test file still exists on the server: ${testFile.remotePath} " ,
183+ ExistenceCheckRemoteOperation (testFile.remotePath, true ).execute(client).isSuccess
184+ )
111185 }
112186 }
113187 }
114188
189+ @Test
190+ fun deleteFromSlideshow_localOnly_online () {
191+ testDeleteFromSlideshow_impl(localOnly = true , offline = false )
192+ }
193+
115194 @Test
116195 fun deleteFromSlideshow_localOnly_offline () {
117- // Prepare local test data
118- val imageCount = 5
119- createMockedImageFiles(imageCount, localOnly = true )
196+ testDeleteFromSlideshow_impl(localOnly = true , offline = true )
197+ }
120198
121- // Launch the activity with the first image
122- val intent = PreviewImageActivity .previewFileIntent(targetContext, user, testFiles[0 ])
123- launchActivity<PreviewImageActivity >(intent).use { scenario ->
124- scenario.onActivity { activity ->
125- activity.connectivityService = ConnectivityServiceOfflineMock ()
126- }
127- onView(isRoot()).check(matches(isDisplayed()))
199+ @Test
200+ fun deleteFromSlideshow_remote_online () {
201+ testDeleteFromSlideshow_impl(localOnly = false , offline = false )
202+ }
128203
129- for (i in 0 until imageCount) {
130- veryImageThenDelete(i)
131- }
132- }
204+ @Test
205+ @Ignore(
206+ " Offline deletion is following a different UX and it is also brittle: Deletion might happen 10 minutes later"
207+ )
208+ fun deleteFromSlideshow_remote_offline () {
209+ // Note: the offline mock doesn't actually do what it is supposed to. The OfflineOperationsWorker uses its
210+ // own connectivityService, which is online, and may still execute the server deletion.
211+ // You'll need to address this, should you activate that test. Otherwise it might not catch all error cases
212+ testDeleteFromSlideshow_impl(localOnly = false , offline = true )
133213 }
134214}
0 commit comments