Skip to content

Commit 2fb0eed

Browse files
committed
WIP - Added automated test for renaming during file upload
Making the Permission keys in OCFile public is a good change anyways - why should these magic strings be private after all? This only invites duplication and hinders maintenance.
1 parent f3a5655 commit 2fb0eed

3 files changed

Lines changed: 165 additions & 10 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Philipp Hasper <vcs@hasper.info>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.test
9+
10+
import android.view.View
11+
import android.widget.TextView
12+
import org.hamcrest.Description
13+
import org.hamcrest.Matcher
14+
import org.hamcrest.TypeSafeMatcher
15+
16+
fun withSelectedText(expected: String): Matcher<View> = object : TypeSafeMatcher<View>() {
17+
override fun describeTo(description: Description) {
18+
description.appendText("with selected text \"$expected\"")
19+
}
20+
21+
override fun matchesSafely(view: View): Boolean {
22+
if (view !is TextView) return false
23+
val text = view.text?.toString() ?: ""
24+
val s = view.selectionStart
25+
val e = view.selectionEnd
26+
if (s < 0 || e < 0 || s > e || e > text.length) return false
27+
return text.substring(s, e) == expected
28+
}
29+
}

app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
11
/*
22
* Nextcloud - Android Client
33
*
4+
* SPDX-FileCopyrightText: 2025 Philipp Hasper <vcs@hasper.info>
45
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
56
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
67
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
78
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
89
*/
910
package com.owncloud.android.ui.activity
1011

12+
import android.content.Intent
13+
import android.net.Uri
14+
import android.view.KeyEvent
1115
import androidx.test.core.app.launchActivity
1216
import androidx.test.espresso.Espresso.onView
17+
import androidx.test.espresso.action.ViewActions
1318
import androidx.test.espresso.assertion.ViewAssertions.matches
19+
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
1420
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
21+
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
1522
import androidx.test.espresso.matcher.ViewMatchers.isRoot
23+
import androidx.test.espresso.matcher.ViewMatchers.withId
24+
import androidx.test.espresso.matcher.ViewMatchers.withText
25+
import com.nextcloud.client.preferences.AppPreferencesImpl
26+
import com.nextcloud.test.GrantStoragePermissionRule
27+
import com.nextcloud.test.withSelectedText
28+
import com.nextcloud.utils.extensions.removeFileExtension
1629
import com.owncloud.android.AbstractIT
30+
import com.owncloud.android.R
31+
import com.owncloud.android.datamodel.OCFile
1732
import com.owncloud.android.utils.ScreenshotTest
33+
import org.junit.Rule
1834
import org.junit.Test
35+
import org.junit.rules.TestRule
36+
import java.io.File
1937

2038
class ReceiveExternalFilesActivityIT : AbstractIT() {
2139
private val testClassName = "com.owncloud.android.ui.activity.ReceiveExternalFilesActivityIT"
2240

41+
@get:Rule
42+
var storagePermissionRule: TestRule = GrantStoragePermissionRule.grant()
43+
2344
@Test
2445
@ScreenshotTest
2546
fun open() {
@@ -40,4 +61,109 @@ class ReceiveExternalFilesActivityIT : AbstractIT() {
4061
open()
4162
removeAccount(secondAccount)
4263
}
64+
65+
/*
66+
TODO: Tests to add
67+
1. Change file name, then navigate to another folder, then back press to top folder -> filename must still stay changed
68+
2. Have a file name without a dot
69+
3. BUG: Change name multiple times (first long name, then short), leaving and re-entering the text field
70+
3. Share a folder, not a file
71+
4. Share multiple files
72+
5. Use unexpected characters, like a "/"
73+
*/
74+
75+
fun createSendIntent(file: File): Intent = Intent(targetContext, ReceiveExternalFilesActivity::class.java).apply {
76+
action = Intent.ACTION_SEND
77+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
78+
putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file))
79+
}
80+
81+
// public fun createSendIntent(context: Context, files: Array<File>): Intent = Intent().apply {
82+
// action = Intent.ACTION_SEND_MULTIPLE
83+
// type = getUniqueMimetype(files)
84+
// addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
85+
// putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(files.map { it.toURI() }))
86+
// }
87+
88+
@Test
89+
fun renameSingleFileUpload() {
90+
val imageFile = getDummyFile("image.jpg")
91+
val intent = createSendIntent(imageFile)
92+
93+
// Create folders with the necessary permissions
94+
val mainFolder = OCFile("/folder/").apply {
95+
permissions = OCFile.PERMISSION_CAN_CREATE_FILE_AND_FOLDER
96+
setFolder()
97+
fileDataStorageManager.saveNewFile(this)
98+
}
99+
val subFolder = OCFile("${mainFolder.remotePath}sub folder/").apply {
100+
permissions = OCFile.PERMISSION_CAN_CREATE_FILE_AND_FOLDER
101+
setFolder()
102+
fileDataStorageManager.saveNewFile(this)
103+
}
104+
105+
// Store the folder in preferences, so the activity starts from there.
106+
@Suppress("DEPRECATION")
107+
val preferences = AppPreferencesImpl.fromContext(targetContext)
108+
preferences.setLastUploadPath(mainFolder.remotePath)
109+
110+
launchActivity<ReceiveExternalFilesActivity>(intent).use {
111+
val expectedMainFolderTitle = (getCurrentActivity() as ToolbarActivity).getActionBarTitle(mainFolder, false)
112+
// Verify that the test starts in the expected folder. If this fails, change the setup calls above
113+
onView(withId(R.id.toolbar))
114+
.check(matches(hasDescendant(withText(expectedMainFolderTitle))))
115+
116+
onView(withText(R.string.uploader_btn_upload_text))
117+
.check(matches(isDisplayed()))
118+
.check(matches(isEnabled()))
119+
120+
// Test the pre-selection behavior (filename, but without extension, shall be selected)
121+
onView(withId(R.id.user_input))
122+
.check(matches(withText(imageFile.name)))
123+
.perform(ViewActions.click())
124+
.check(matches(withSelectedText(imageFile.name.removeFileExtension())))
125+
126+
// Set a new file name
127+
val secondFileName = "New filename.jpg"
128+
onView(withId(R.id.user_input))
129+
.perform(ViewActions.typeTextIntoFocusedView(secondFileName.removeFileExtension()))
130+
.check(matches(withText(secondFileName)))
131+
// Leave the field and come back to verify the pre-selection behavior correctly handles the new name
132+
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_TAB))
133+
.perform(ViewActions.click())
134+
.check(matches(withSelectedText(secondFileName.removeFileExtension())))
135+
136+
// Set a file name without file extension
137+
val thirdFileName = "no extension"
138+
onView(withId(R.id.user_input))
139+
.perform(ViewActions.clearText())
140+
.perform(ViewActions.typeTextIntoFocusedView(thirdFileName))
141+
.check(matches(withText(thirdFileName)))
142+
// Leave the field and come back to verify the pre-selection behavior correctly handles the new name
143+
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_TAB))
144+
.perform(ViewActions.click())
145+
.check(matches(withSelectedText(thirdFileName)))
146+
147+
/*
148+
* TODO - NOMERGE: check that the new name is actually used during the upload!
149+
*/
150+
151+
// Enter the subfolder and verify that the text stays intact
152+
val expectedSubFolderTitle = (getCurrentActivity() as ToolbarActivity).getActionBarTitle(subFolder, false)
153+
onView(withText(expectedSubFolderTitle))
154+
.perform(ViewActions.click())
155+
onView(withId(R.id.toolbar))
156+
.check(matches(hasDescendant(withText(expectedSubFolderTitle))))
157+
onView(withId(R.id.user_input))
158+
.check(matches(withText(secondFileName)))
159+
.perform(ViewActions.click())
160+
.check(matches(withSelectedText(secondFileName.removeFileExtension())))
161+
162+
// Set a new, shorter file name
163+
val fourthFileName = "short.jpg"
164+
onView(withId(R.id.user_input))
165+
.perform(ViewActions.typeTextIntoFocusedView(fourthFileName.removeFileExtension()))
166+
.check(matches(withText(fourthFileName)))
167+
}
168+
}
43169
}

app/src/main/java/com/owncloud/android/datamodel/OCFile.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@
4747
public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterface {
4848

4949
public final static String PERMISSION_CAN_RESHARE = "R";
50-
private final static String PERMISSION_SHARED = "S";
51-
private final static String PERMISSION_MOUNTED = "M";
52-
private final static String PERMISSION_CAN_CREATE_FILE_INSIDE_FOLDER = "C";
53-
private final static String PERMISSION_CAN_CREATE_FOLDER_INSIDE_FOLDER = "K";
54-
private final static String PERMISSION_CAN_READ = "G";
55-
private final static String PERMISSION_CAN_WRITE = "W";
56-
private final static String PERMISSION_CAN_DELETE_OR_LEAVE_SHARE = "D";
57-
private final static String PERMISSION_CAN_RENAME = "N";
58-
private final static String PERMISSION_CAN_MOVE = "V";
59-
private final static String PERMISSION_CAN_CREATE_FILE_AND_FOLDER = PERMISSION_CAN_CREATE_FILE_INSIDE_FOLDER + PERMISSION_CAN_CREATE_FOLDER_INSIDE_FOLDER;
50+
public final static String PERMISSION_SHARED = "S";
51+
public final static String PERMISSION_MOUNTED = "M";
52+
public final static String PERMISSION_CAN_CREATE_FILE_INSIDE_FOLDER = "C";
53+
public final static String PERMISSION_CAN_CREATE_FOLDER_INSIDE_FOLDER = "K";
54+
public final static String PERMISSION_CAN_READ = "G";
55+
public final static String PERMISSION_CAN_WRITE = "W";
56+
public final static String PERMISSION_CAN_DELETE_OR_LEAVE_SHARE = "D";
57+
public final static String PERMISSION_CAN_RENAME = "N";
58+
public final static String PERMISSION_CAN_MOVE = "V";
59+
public final static String PERMISSION_CAN_CREATE_FILE_AND_FOLDER = PERMISSION_CAN_CREATE_FILE_INSIDE_FOLDER + PERMISSION_CAN_CREATE_FOLDER_INSIDE_FOLDER;
6060

6161
private final static int MAX_FILE_SIZE_FOR_IMMEDIATE_PREVIEW_BYTES = 1024000;
6262

0 commit comments

Comments
 (0)