diff --git a/cypress/e2e/files/FilesUtils.ts b/cypress/e2e/files/FilesUtils.ts new file mode 100644 index 0000000000000..b94ad3941ec11 --- /dev/null +++ b/cypress/e2e/files/FilesUtils.ts @@ -0,0 +1,138 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +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, dirPath: string) => { + getRowForFile(fileName).should('be.visible') + triggerActionForFile(fileName, 'move-copy') + + cy.get('.file-picker').within(() => { + // intercept the copy so we can wait for it + cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile') + + if (dirPath === '/') { + // select home folder + cy.get('button[title="Home"]').should('be.visible').click() + // click move + cy.contains('button', 'Move').should('be.visible').click() + } else if (dirPath === '.') { + // click move + cy.contains('button', 'Copy').should('be.visible').click() + } else { + const directories = dirPath.split('/') + directories.forEach((directory) => { + // select the folder + cy.get(`[data-filename="${directory}"]`).should('be.visible').click() + }) + + // click move + cy.contains('button', `Move to ${directories.at(-1)}`).should('be.visible').click() + } + + cy.wait('@moveFile') + }) +} + +export const copyFile = (fileName: string, dirPath: string) => { + getRowForFile(fileName).should('be.visible') + triggerActionForFile(fileName, 'move-copy') + + cy.get('.file-picker').within(() => { + // intercept the copy so we can wait for it + cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile') + + if (dirPath === '/') { + // select home folder + cy.get('button[title="Home"]').should('be.visible').click() + // click copy + cy.contains('button', 'Copy').should('be.visible').click() + } else if (dirPath === '.') { + // click copy + cy.contains('button', 'Copy').should('be.visible').click() + } else { + const directories = dirPath.split('/') + directories.forEach((directory) => { + // select the folder + cy.get(`[data-filename="${CSS.escape(directory)}"]`).should('be.visible').click() + }) + + // click copy + cy.contains('button', `Copy to ${directories.at(-1)}`).should('be.visible').click() + } + + cy.wait('@copyFile') + }) +} + +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 = (dirPath: string) => { + const directories = dirPath.split('/') + directories.forEach((directory) => { + getRowForFile(directory).should('be.visible').find('[data-cy-files-list-row-name-link]').click() + }) + +} + +export const closeSidebar = () => { + // {force: true} as it might be hidden behind toasts + cy.get('[data-cy-sidebar] .app-sidebar__close').click({ force: true }) +} + +export const clickOnBreadcrumbs = (label: string) => { + cy.intercept('PROPFIND', /\/remote.php\/dav\//).as('propfind') + cy.get('[data-cy-files-content-breadcrumbs]').contains(label).click() + cy.wait('@propfind') +} + +export const createFolder = (folderName: string) => { + cy.intercept('MKCOL', /\/remote.php\/dav\/files\//).as('createFolder') + + // TODO: replace by proper data-cy selectors + cy.get('[data-cy-upload-picker] .action-item__menutoggle').first().click() + cy.contains('.upload-picker__menu-entry button', 'New folder').click() + cy.get('[data-cy-files-new-node-dialog]').should('be.visible') + cy.get('[data-cy-files-new-node-dialog-input]').type(`{selectall}${folderName}`) + cy.get('[data-cy-files-new-node-dialog-submit]').click() + + cy.wait('@createFolder') + + getRowForFile(folderName).should('be.visible') +} diff --git a/cypress/e2e/files_sharing/FilesSharingUtils.ts b/cypress/e2e/files_sharing/FilesSharingUtils.ts new file mode 100644 index 0000000000000..ef8cf462a06c9 --- /dev/null +++ b/cypress/e2e/files_sharing/FilesSharingUtils.ts @@ -0,0 +1,183 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +/* eslint-disable jsdoc/require-jsdoc */ +import { triggerActionForFile } from '../files/FilesUtils' + +export interface ShareSetting { + read: boolean + update: boolean + delete: boolean + share: boolean + download: boolean + note: string +} + +export function createShare(fileName: string, username: string, shareSettings: Partial = {}) { + openSharingPanel(fileName) + + cy.get('#app-sidebar-vue').within(() => { + cy.get('#sharing-search-input').clear() + cy.intercept({ times: 1, method: 'GET', url: '**/apps/files_sharing/api/v1/sharees?*' }).as('userSearch') + cy.get('#sharing-search-input').type(username) + cy.wait('@userSearch') + }) + + cy.get(`[user="${username}"]`).click() + + // HACK: Save the share and then update it, as permissions changes are currently not saved for new share. + cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' }) + updateShare(fileName, 0, shareSettings) +} + +export function updateShare(fileName: string, index: number, shareSettings: Partial = {}) { + openSharingPanel(fileName) + + cy.intercept({ times: 1, method: 'PUT', url: '**/apps/files_sharing/api/v1/shares/*' }).as('updateShare') + + cy.get('#app-sidebar-vue').within(() => { + cy.get('[data-cy-files-sharing-share-actions]').eq(index).click() + cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click() + + 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' }) + } + } + + 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' }) + } + } + + 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' }) + } + } + + 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' }) + } + } + + if (shareSettings.note !== undefined) { + cy.findByRole('checkbox', { name: /note to recipient/i }).check({ force: true, scrollBehavior: 'nearest' }) + cy.findByRole('textbox', { name: /note to recipient/i }).type(shareSettings.note) + } + + cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' }) + + cy.wait('@updateShare') + }) +} + +export function openSharingPanel(fileName: string) { + triggerActionForFile(fileName, 'details') + + cy.get('#app-sidebar-vue') + .get('[aria-controls="tab-sharing"]') + .click() +} + +type FileRequestOptions = { + label?: string + note?: string + password?: string + /* YYYY-MM-DD format */ + expiration?: string +} + +/** + * Create a file request for a folder + * @param path The path of the folder, leading slash is required + * @param options The options for the file request + */ +export const createFileRequest = (path: string, options: FileRequestOptions = {}) => { + if (!path.startsWith('/')) { + throw new Error('Path must start with a slash') + } + + // Navigate to the folder + cy.visit('/apps/files/files?dir=' + path) + + // Open the file request dialog + cy.get('[data-cy-upload-picker] .action-item__menutoggle').first().click() + cy.contains('.upload-picker__menu-entry button', 'Create file request').click() + cy.get('[data-cy-file-request-dialog]').should('be.visible') + + // Check and fill the first page options + cy.get('[data-cy-file-request-dialog-fieldset="label"]').should('be.visible') + cy.get('[data-cy-file-request-dialog-fieldset="destination"]').should('be.visible') + cy.get('[data-cy-file-request-dialog-fieldset="note"]').should('be.visible') + + cy.get('[data-cy-file-request-dialog-fieldset="destination"] input').should('contain.value', path) + if (options.label) { + cy.get('[data-cy-file-request-dialog-fieldset="label"] input').type(`{selectall}${options.label}`) + } + if (options.note) { + cy.get('[data-cy-file-request-dialog-fieldset="note"] textarea').type(`{selectall}${options.note}`) + } + + // Go to the next page + cy.get('[data-cy-file-request-dialog-controls="next"]').click() + cy.get('[data-cy-file-request-dialog-fieldset="expiration"] input[type="checkbox"]').should('exist') + cy.get('[data-cy-file-request-dialog-fieldset="expiration"] input[type="date"]').should('not.exist') + cy.get('[data-cy-file-request-dialog-fieldset="password"] input[type="checkbox"]').should('exist') + cy.get('[data-cy-file-request-dialog-fieldset="password"] input[type="password"]').should('not.exist') + if (options.expiration) { + cy.get('[data-cy-file-request-dialog-fieldset="expiration"] input[type="checkbox"]').check({ force: true }) + cy.get('[data-cy-file-request-dialog-fieldset="expiration"] input[type="date"]').type(`{selectall}${options.expiration}`) + } + if (options.password) { + cy.get('[data-cy-file-request-dialog-fieldset="password"] input[type="checkbox"]').check({ force: true }) + cy.get('[data-cy-file-request-dialog-fieldset="password"] input[type="password"]').type(`{selectall}${options.password}`) + } + + // Create the file request + cy.get('[data-cy-file-request-dialog-controls="next"]').click() + + // Get the file request URL + cy.get('[data-cy-file-request-dialog-fieldset="link"]').then(($link) => { + const url = $link.val() + cy.log(`File request URL: ${url}`) + cy.wrap(url).as('fileRequestUrl') + }) + + // Close + cy.get('[data-cy-file-request-dialog-controls="finish"]').click() +} + +export const enterGuestName = (name: string) => { + cy.get('[data-cy-public-auth-prompt-dialog]').should('be.visible') + cy.get('[data-cy-public-auth-prompt-dialog-name]').should('be.visible') + cy.get('[data-cy-public-auth-prompt-dialog-submit]').should('be.visible') + + cy.get('[data-cy-public-auth-prompt-dialog-name]').type(`{selectall}${name}`) + cy.get('[data-cy-public-auth-prompt-dialog-submit]').click() + + cy.get('[data-cy-public-auth-prompt-dialog]').should('not.exist') +} diff --git a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts index fc70e49af6453..26b6b89cb2a3d 100644 --- a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts +++ b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts @@ -4,7 +4,7 @@ */ import { User } from "@nextcloud/cypress" -import { createShare } from "./filesSharingUtils" +import { createShare } from "./FilesSharingUtils" describe('Limit to sharing to people in the same group', () => { let alice: User