aboutsummaryrefslogtreecommitdiffstats
path: root/cypress/e2e
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2024-03-12 18:49:20 +0100
committerLouis Chemineau <louis@chmn.me>2024-03-14 11:07:20 +0100
commit01fe326df16bd479a0c75842b6baa22eebb9e279 (patch)
tree7baf22104fad86ea820d2bc08fed87c36f943fdb /cypress/e2e
parent2de9880d79adeeab0e0a01ce3992de404a0c274d (diff)
downloadnextcloud-server-01fe326df16bd479a0c75842b6baa22eebb9e279.tar.gz
nextcloud-server-01fe326df16bd479a0c75842b6baa22eebb9e279.zip
test(files): Add e2e tests for live photo sync
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'cypress/e2e')
-rw-r--r--cypress/e2e/files/FilesUtils.ts31
-rw-r--r--cypress/e2e/files/live_photos.cy.ts215
-rw-r--r--cypress/e2e/files_sharing/filesSharingUtils.ts8
-rw-r--r--cypress/e2e/files_versions/version_restoration.cy.ts2
4 files changed, 255 insertions, 1 deletions
diff --git a/cypress/e2e/files/FilesUtils.ts b/cypress/e2e/files/FilesUtils.ts
index 7f61584bcde..798b9b5f60d 100644
--- a/cypress/e2e/files/FilesUtils.ts
+++ b/cypress/e2e/files/FilesUtils.ts
@@ -20,17 +20,31 @@
*
*/
+export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
+export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')
export const getActionsForFile = (filename: string) => getRowForFile(filename).find('[data-cy-files-list-row-actions]')
+export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).find('button[aria-label="Actions"]')
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).find('button[aria-label="Actions"]')
+export const triggerActionForFileId = (fileid: number, actionId: string) => {
+ getActionButtonForFileId(fileid).click()
+ cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
+}
export const triggerActionForFile = (filename: string, actionId: string) => {
getActionButtonForFile(filename).click()
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
}
+export const triggerInlineActionForFileId = (fileid: number, actionId: string) => {
+ getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
+}
+export const triggerInlineActionForFile = (filename: string, actionId: string) => {
+ getActionsForFile(filename).get(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
+}
+
export const moveFile = (fileName: string, dirName: string) => {
getRowForFile(fileName).should('be.visible')
triggerActionForFile(fileName, 'move-copy')
@@ -85,6 +99,23 @@ export const copyFile = (fileName: string, dirName: string) => {
})
}
+export const renameFile = (fileName: string, newFileName: string) => {
+ getRowForFile(fileName)
+ triggerActionForFile(fileName, 'rename')
+
+ // intercept the move so we can wait for it
+ cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile')
+
+ getRowForFile(fileName).find('[data-cy-files-list-row-name] input').clear()
+ getRowForFile(fileName).find('[data-cy-files-list-row-name] input').type(`${newFileName}{enter}`)
+
+ cy.wait('@moveFile')
+}
+
export const navigateToFolder = (folderName: string) => {
getRowForFile(folderName).should('be.visible').find('[data-cy-files-list-row-name-link]').click()
}
+
+export const closeSidebar = () => {
+ cy.get('[cy-data-sidebar] .app-sidebar__close').click()
+}
diff --git a/cypress/e2e/files/live_photos.cy.ts b/cypress/e2e/files/live_photos.cy.ts
new file mode 100644
index 00000000000..98babb86941
--- /dev/null
+++ b/cypress/e2e/files/live_photos.cy.ts
@@ -0,0 +1,215 @@
+/**
+ * @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me>
+ *
+ * @author Louis Chmn <louis@chmn.me>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import type { User } from '@nextcloud/cypress'
+import { closeSidebar, copyFile, getRowForFile, getRowForFileId, renameFile, triggerActionForFile, triggerInlineActionForFileId } from './FilesUtils'
+
+/**
+ *
+ * @param label
+ */
+function refreshView(label: string) {
+ cy.intercept('PROPFIND', /\/remote.php\/dav\//).as('propfind')
+ cy.get('[data-cy-files-content-breadcrumbs]').contains(label).click()
+ cy.wait('@propfind')
+}
+
+/**
+ *
+ * @param user
+ * @param fileName
+ * @param domain
+ * @param requesttoken
+ * @param metadata
+ */
+function setMetadata(user: User, fileName: string, domain: string, requesttoken: string, metadata: object) {
+ cy.request({
+ method: 'PROPPATCH',
+ url: `http://${domain}/remote.php/dav/files/${user.userId}/${fileName}`,
+ auth: { user: user.userId, pass: user.password },
+ headers: {
+ requesttoken,
+ },
+ body: `<?xml version="1.0"?>
+ <d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
+ <d:set>
+ <d:prop>
+ ${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
+ </d:prop>
+ </d:set>
+ </d:propertyupdate>`,
+ })
+}
+
+describe('Files: Live photos', { testIsolation: true }, () => {
+ let currentUser: User
+ let randomFileName: string
+ let jpgFileId: number
+ let movFileId: number
+ let hostname: string
+ let requesttoken: string
+
+ before(() => {
+ cy.createRandomUser().then((user) => {
+ currentUser = user
+ cy.login(currentUser)
+ cy.visit('/apps/files')
+ })
+
+ cy.url().then(url => { hostname = new URL(url).hostname })
+ })
+
+ beforeEach(() => {
+ randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10)
+
+ cy.uploadContent(currentUser, new Blob(['jpg file'], { type: 'image/jpg' }), 'image/jpg', `/${randomFileName}.jpg`)
+ .then(response => { jpgFileId = parseInt(response.headers['oc-fileid']) })
+ cy.uploadContent(currentUser, new Blob(['mov file'], { type: 'video/mov' }), 'video/mov', `/${randomFileName}.mov`)
+ .then(response => { movFileId = parseInt(response.headers['oc-fileid']) })
+
+ cy.login(currentUser)
+ cy.visit('/apps/files')
+
+ cy.get('head').invoke('attr', 'data-requesttoken').then(_requesttoken => { requesttoken = _requesttoken as string })
+
+ cy.then(() => {
+ setMetadata(currentUser, `${randomFileName}.jpg`, hostname, requesttoken, { 'nc:metadata-files-live-photo': movFileId })
+ setMetadata(currentUser, `${randomFileName}.mov`, hostname, requesttoken, { 'nc:metadata-files-live-photo': jpgFileId })
+ })
+
+ cy.then(() => {
+ cy.visit(`/apps/files/files/${jpgFileId}`) // Refresh and scroll to the .jpg file.
+ closeSidebar()
+ })
+ })
+
+ it('Only renders the .jpg file', () => {
+ getRowForFileId(jpgFileId).should('have.length', 1)
+ getRowForFileId(movFileId).should('have.length', 0)
+ })
+
+ context("'Show hidden files' is enabled", () => {
+ before(() => {
+ cy.login(currentUser)
+ cy.visit('/apps/files')
+ cy.get('[data-cy-files-navigation-settings-button]').click()
+ // Force:true because the checkbox is hidden by the pretty UI.
+ cy.get('[data-cy-files-settings-setting="show_hidden"] input').check({ force: true })
+ })
+
+ it("Shows both files when 'Show hidden files' is enabled", () => {
+ getRowForFileId(jpgFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.jpg`)
+ getRowForFileId(movFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.mov`)
+ })
+
+ it('Copies both files when copying the .jpg', () => {
+ copyFile(`${randomFileName}.jpg`, '.')
+ refreshView('All files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
+ getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
+ getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
+ })
+
+ it('Copies both files when copying the .mov', () => {
+ copyFile(`${randomFileName}.mov`, '.')
+ refreshView('All files')
+
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
+ getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
+ getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
+ })
+
+ it('Moves files when moving the .jpg', () => {
+ renameFile(`${randomFileName}.jpg`, `${randomFileName}_moved.jpg`)
+ refreshView('All files')
+
+ getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
+ getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
+ })
+
+ it('Moves files when moving the .mov', () => {
+ renameFile(`${randomFileName}.mov`, `${randomFileName}_moved.mov`)
+ refreshView('All files')
+
+ getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
+ getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
+ })
+
+ it('Deletes files when deleting the .jpg', () => {
+ triggerActionForFile(`${randomFileName}.jpg`, 'delete')
+ refreshView('All files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
+
+ cy.visit('/apps/files/trashbin')
+
+ getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.jpg\\.d[0-9]+$`))
+ getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.mov\\.d[0-9]+$`))
+ })
+
+ it('Block deletion when deleting the .mov', () => {
+ triggerActionForFile(`${randomFileName}.mov`, 'delete')
+ refreshView('All files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
+
+ cy.visit('/apps/files/trashbin')
+
+ getRowForFileId(jpgFileId).should('have.length', 0)
+ getRowForFileId(movFileId).should('have.length', 0)
+ })
+
+ it('Restores files when restoring the .jpg', () => {
+ triggerActionForFile(`${randomFileName}.jpg`, 'delete')
+ cy.visit('/apps/files/trashbin')
+ triggerInlineActionForFileId(jpgFileId, 'restore')
+ refreshView('Deleted files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
+
+ cy.visit('/apps/files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
+ })
+
+ it('Blocks restoration when restoring the .mov', () => {
+ triggerActionForFile(`${randomFileName}.jpg`, 'delete')
+ cy.visit('/apps/files/trashbin')
+ triggerInlineActionForFileId(movFileId, 'restore')
+ refreshView('Deleted files')
+
+ getRowForFileId(jpgFileId).should('have.length', 1)
+ getRowForFileId(movFileId).should('have.length', 1)
+
+ cy.visit('/apps/files')
+
+ getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
+ getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
+ })
+ })
+})
diff --git a/cypress/e2e/files_sharing/filesSharingUtils.ts b/cypress/e2e/files_sharing/filesSharingUtils.ts
index ee80041b619..cb407153380 100644
--- a/cypress/e2e/files_sharing/filesSharingUtils.ts
+++ b/cypress/e2e/files_sharing/filesSharingUtils.ts
@@ -58,8 +58,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
if (shareSettings.download !== undefined) {
cy.get('[data-cy-files-sharing-share-permissions-checkbox="download"]').find('input').as('downloadCheckbox')
if (shareSettings.download) {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@downloadCheckbox').check({ force: true, scrollBehavior: 'nearest' })
} else {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@downloadCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
}
}
@@ -67,8 +69,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
if (shareSettings.read !== undefined) {
cy.get('[data-cy-files-sharing-share-permissions-checkbox="read"]').find('input').as('readCheckbox')
if (shareSettings.read) {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@readCheckbox').check({ force: true, scrollBehavior: 'nearest' })
} else {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@readCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
}
}
@@ -76,8 +80,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
if (shareSettings.update !== undefined) {
cy.get('[data-cy-files-sharing-share-permissions-checkbox="update"]').find('input').as('updateCheckbox')
if (shareSettings.update) {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@updateCheckbox').check({ force: true, scrollBehavior: 'nearest' })
} else {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@updateCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
}
}
@@ -85,8 +91,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
if (shareSettings.delete !== undefined) {
cy.get('[data-cy-files-sharing-share-permissions-checkbox="delete"]').find('input').as('deleteCheckbox')
if (shareSettings.delete) {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@deleteCheckbox').check({ force: true, scrollBehavior: 'nearest' })
} else {
+ // Force:true because the checkbox is hidden by the pretty UI.
cy.get('@deleteCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
}
}
diff --git a/cypress/e2e/files_versions/version_restoration.cy.ts b/cypress/e2e/files_versions/version_restoration.cy.ts
index c5dbaeab964..d9d983b0d09 100644
--- a/cypress/e2e/files_versions/version_restoration.cy.ts
+++ b/cypress/e2e/files_versions/version_restoration.cy.ts
@@ -113,7 +113,7 @@ describe('Versions restoration', () => {
auth: { user: recipient.userId, pass: recipient.password },
headers: {
cookie: '',
- Destination: 'https://nextcloud_server1.test/remote.php/dav/versions/admin/restore/target',
+ Destination: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/restore/target`,
},
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
failOnStatusCode: false,