diff options
Diffstat (limited to 'cypress/e2e/files_sharing')
-rw-r--r-- | cypress/e2e/files_sharing/FilesSharingUtils.ts | 11 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/file-request.cy.ts | 44 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts | 49 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/download-files.cy.ts | 141 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/header-menu.cy.ts (renamed from cypress/e2e/files_sharing/public-share-header-menu.cy.ts) | 134 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/rename-files.cy.ts | 32 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/setup-public-share.ts | 119 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts | 169 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/view_view-only-no-download.cy.ts | 104 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/view_view-only.cy.ts | 107 |
10 files changed, 811 insertions, 99 deletions
diff --git a/cypress/e2e/files_sharing/FilesSharingUtils.ts b/cypress/e2e/files_sharing/FilesSharingUtils.ts index ef8cf462a06..6e97a757a15 100644 --- a/cypress/e2e/files_sharing/FilesSharingUtils.ts +++ b/cypress/e2e/files_sharing/FilesSharingUtils.ts @@ -170,14 +170,3 @@ export const createFileRequest = (path: string, options: FileRequestOptions = {} // 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/file-request.cy.ts b/cypress/e2e/files_sharing/file-request.cy.ts index 7c33594e25c..d109c4c585d 100644 --- a/cypress/e2e/files_sharing/file-request.cy.ts +++ b/cypress/e2e/files_sharing/file-request.cy.ts @@ -5,12 +5,31 @@ import type { User } from '@nextcloud/cypress' import { createFolder, getRowForFile, navigateToFolder } from '../files/FilesUtils' -import { createFileRequest, enterGuestName } from './FilesSharingUtils' +import { createFileRequest } from './FilesSharingUtils' + +const enterGuestName = (name: string) => { + cy.findByRole('dialog', { name: /Upload files to/ }) + .should('be.visible') + .within(() => { + cy.findByRole('textbox', { name: 'Nickname' }) + .should('be.visible') + + cy.findByRole('textbox', { name: 'Nickname' }) + .type(`{selectall}${name}`) + + cy.findByRole('button', { name: 'Submit name' }) + .should('be.visible') + .click() + }) + + cy.findByRole('dialog', { name: /Upload files to/ }) + .should('not.exist') +} describe('Files', { testIsolation: true }, () => { + const folderName = 'test-folder' let user: User let url = '' - let folderName = 'test-folder' it('Login with a user and create a file request', () => { cy.createRandomUser().then((_user) => { @@ -33,19 +52,22 @@ describe('Files', { testIsolation: true }, () => { enterGuestName('Guest') // Check various elements on the page - cy.get('#public-upload .emptycontent').should('be.visible') - cy.get('#public-upload h2').contains(`Upload files to ${folderName}`) - cy.get('#public-upload input[type="file"]').as('fileInput').should('exist') + cy.contains(`Upload files to ${folderName}`) + .should('be.visible') + cy.findByRole('button', { name: 'Upload' }) + .should('be.visible') cy.intercept('PUT', '/public.php/dav/files/*/*').as('uploadFile') // Upload a file - cy.get('@fileInput').selectFile({ - contents: Cypress.Buffer.from('abcdef'), - fileName: 'file.txt', - mimeType: 'text/plain', - lastModified: Date.now(), - }, { force: true }) + cy.get('[data-cy-files-sharing-file-drop] input[type="file"]') + .should('exist') + .selectFile({ + contents: Cypress.Buffer.from('abcdef'), + fileName: 'file.txt', + mimeType: 'text/plain', + lastModified: Date.now(), + }, { force: true }) cy.wait('@uploadFile').its('response.statusCode').should('eq', 201) }) diff --git a/cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts b/cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts new file mode 100644 index 00000000000..078ecf747bf --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts @@ -0,0 +1,49 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { copyFile, getRowForFile, moveFile, navigateToFolder } from '../../files/FilesUtils.ts' +import { getShareUrl, setupPublicShare } from './setup-public-share.ts' + +describe('files_sharing: Public share - copy and move files', { testIsolation: true }, () => { + + beforeEach(() => { + setupPublicShare() + .then(() => cy.logout()) + .then(() => cy.visit(getShareUrl())) + }) + + it('Can copy a file to new folder', () => { + getRowForFile('foo.txt').should('be.visible') + getRowForFile('subfolder').should('be.visible') + + copyFile('foo.txt', 'subfolder') + + // still visible + getRowForFile('foo.txt').should('be.visible') + navigateToFolder('subfolder') + + cy.url().should('contain', 'dir=/subfolder') + getRowForFile('foo.txt').should('be.visible') + getRowForFile('bar.txt').should('be.visible') + getRowForFile('subfolder').should('not.exist') + }) + + it('Can move a file to new folder', () => { + getRowForFile('foo.txt').should('be.visible') + getRowForFile('subfolder').should('be.visible') + + moveFile('foo.txt', 'subfolder') + + // wait until visible again + getRowForFile('subfolder').should('be.visible') + + // file should be moved -> not exist anymore + getRowForFile('foo.txt').should('not.exist') + navigateToFolder('subfolder') + + cy.url().should('contain', 'dir=/subfolder') + getRowForFile('foo.txt').should('be.visible') + getRowForFile('subfolder').should('not.exist') + }) +}) diff --git a/cypress/e2e/files_sharing/public-share/download-files.cy.ts b/cypress/e2e/files_sharing/public-share/download-files.cy.ts new file mode 100644 index 00000000000..4e37d1b38ae --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/download-files.cy.ts @@ -0,0 +1,141 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +// @ts-expect-error The package is currently broken - but works... +import { deleteDownloadsFolderBeforeEach } from 'cypress-delete-downloads-folder' + +import { zipFileContains } from '../../../support/utils/assertions.ts' +import { getRowForFile, triggerActionForFile } from '../../files/FilesUtils.ts' +import { getShareUrl, setupPublicShare } from './setup-public-share.ts' + +describe('files_sharing: Public share - downloading files', { testIsolation: true }, () => { + + const shareName = 'shared' + + before(() => setupPublicShare()) + + deleteDownloadsFolderBeforeEach() + + beforeEach(() => { + cy.logout() + cy.visit(getShareUrl()) + }) + + it('Can download all files', () => { + getRowForFile('foo.txt').should('be.visible') + + cy.get('[data-cy-files-list]').within(() => { + cy.findByRole('checkbox', { name: /Toggle selection for all files/i }) + .should('exist') + .check({ force: true }) + + // see that two files are selected + cy.contains('2 selected').should('be.visible') + + // click download + cy.findByRole('button', { name: 'Download (selected)' }) + .should('be.visible') + .click() + + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/${shareName}.zip`, null, { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 30) + // Check all files are included + .and(zipFileContains([ + 'foo.txt', + 'subfolder/', + 'subfolder/bar.txt', + ])) + }) + }) + + it('Can download selected files', () => { + getRowForFile('subfolder') + .should('be.visible') + + cy.get('[data-cy-files-list]').within(() => { + getRowForFile('subfolder') + .findByRole('checkbox') + .check({ force: true }) + + // see that two files are selected + cy.contains('1 selected').should('be.visible') + + // click download + cy.findByRole('button', { name: 'Download (selected)' }) + .should('be.visible') + .click() + + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/subfolder.zip`, null, { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 30) + // Check all files are included + .and(zipFileContains([ + 'subfolder/', + 'subfolder/bar.txt', + ])) + }) + }) + + it('Can download folder by action', () => { + getRowForFile('subfolder') + .should('be.visible') + + cy.get('[data-cy-files-list]').within(() => { + triggerActionForFile('subfolder', 'download') + + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/subfolder.zip`, null, { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 30) + // Check all files are included + .and(zipFileContains([ + 'subfolder/', + 'subfolder/bar.txt', + ])) + }) + }) + + it('Can download file by action', () => { + getRowForFile('foo.txt') + .should('be.visible') + + cy.get('[data-cy-files-list]').within(() => { + triggerActionForFile('foo.txt', 'download') + + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/foo.txt`, 'utf-8', { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 5) + .and('contain', '<content>foo</content>') + }) + }) + + it('Can download file by selection', () => { + getRowForFile('foo.txt') + .should('be.visible') + + cy.get('[data-cy-files-list]').within(() => { + getRowForFile('foo.txt') + .findByRole('checkbox') + .check({ force: true }) + + cy.findByRole('button', { name: 'Download (selected)' }) + .click() + + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/foo.txt`, 'utf-8', { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 5) + .and('contain', '<content>foo</content>') + }) + }) +}) diff --git a/cypress/e2e/files_sharing/public-share-header-menu.cy.ts b/cypress/e2e/files_sharing/public-share/header-menu.cy.ts index 020ea410dba..a89ee8eb90e 100644 --- a/cypress/e2e/files_sharing/public-share-header-menu.cy.ts +++ b/cypress/e2e/files_sharing/public-share/header-menu.cy.ts @@ -2,67 +2,39 @@ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { haveValidity, zipFileContains } from '../../support/utils/assertions.ts' -import { openSharingPanel } from './FilesSharingUtils.ts' -// @ts-expect-error The package is currently broken - but works... -import { deleteDownloadsFolderBeforeEach } from 'cypress-delete-downloads-folder' +import { haveValidity, zipFileContains } from '../../../support/utils/assertions.ts' +import { getShareUrl, setupPublicShare } from './setup-public-share.ts' +/** + * This tests ensures that on public shares the header actions menu correctly works + */ describe('files_sharing: Public share - header actions menu', { testIsolation: true }, () => { - let shareUrl: string - const shareName = 'to be shared' - - before(() => { - cy.createRandomUser().then(($user) => { - cy.mkdir($user, `/${shareName}`) - cy.mkdir($user, `/${shareName}/subfolder`) - cy.uploadContent($user, new Blob([]), 'text/plain', `/${shareName}/foo.txt`) - cy.uploadContent($user, new Blob([]), 'text/plain', `/${shareName}/subfolder/bar.txt`) - cy.login($user) - // open the files app - cy.visit('/apps/files') - // open the sidebar - openSharingPanel(shareName) - // create the share - cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') - cy.findByRole('button', { name: 'Create a new share link' }) - .click() - // extract the link - cy.wait('@createShare').should(({ response }) => { - const { ocs } = response?.body ?? {} - shareUrl = ocs?.data.url - expect(shareUrl).to.match(/^http:\/\//) - }) - }) - }) - - deleteDownloadsFolderBeforeEach() - + before(() => setupPublicShare()) beforeEach(() => { cy.logout() - cy.visit(shareUrl) + cy.visit(getShareUrl()) }) it('Can download all files', () => { - // Check the button cy.get('header') - .findByRole('button', { name: 'Download all files' }) + .findByRole('button', { name: 'Download' }) .should('be.visible') cy.get('header') - .findByRole('button', { name: 'Download all files' }) + .findByRole('button', { name: 'Download' }) .click() // check a file is downloaded const downloadsFolder = Cypress.config('downloadsFolder') - cy.readFile(`${downloadsFolder}/${shareName}.zip`, null, { timeout: 15000 }) + cy.readFile(`${downloadsFolder}/shared.zip`, null, { timeout: 15000 }) .should('exist') .and('have.length.gt', 30) // Check all files are included .and(zipFileContains([ - `${shareName}/`, - `${shareName}/foo.txt`, - `${shareName}/subfolder/`, - `${shareName}/subfolder/bar.txt`, + 'shared/', + 'shared/foo.txt', + 'shared/subfolder/', + 'shared/subfolder/bar.txt', ])) }) @@ -78,12 +50,12 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t cy.findByRole('menu', { name: /More action/i }) .should('be.visible') // see correct link in item - cy.findByRole('menuitem', { name: /Direct link/i }) + cy.findByRole('menuitem', { name: 'Direct link' }) .should('be.visible') .and('have.attr', 'href') .then((attribute) => expect(attribute).to.match(/^http:\/\/.+\/download$/)) // see menu closes on click - cy.findByRole('menuitem', { name: /Direct link/i }) + cy.findByRole('menuitem', { name: 'Direct link' }) .click() cy.findByRole('menu', { name: /More actions/i }) .should('not.exist') @@ -100,7 +72,7 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t // See the menu cy.findByRole('menu', { name: /More action/i }) .should('be.visible') - // see correct item + // see correct button cy.findByRole('menuitem', { name: /Add to your/i }) .should('be.visible') .click() @@ -125,6 +97,7 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t .findByRole('button', { name: /More actions/i }) .should('be.visible') .click() + // see correct button cy.findByRole('menuitem', { name: /Add to your/i }) .should('be.visible') .click() @@ -134,10 +107,11 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t .type('user@nextcloud.local') // intercept request, the request is continued when the promise is resolved const { promise, resolve } = Promise.withResolvers() - cy.intercept('POST', '**/apps/federatedfilesharing/createFederatedShare', async (req) => { - await promise - req.reply({ statusCode: 503 }) + cy.intercept('POST', '**/apps/federatedfilesharing/createFederatedShare', (request) => { + // we need to wait in the onResponse handler as the intercept handler times out otherwise + request.on('response', async (response) => { await promise; response.statusCode = 503 }) }).as('createFederatedShare') + // create the share cy.findByRole('button', { name: 'Create share' }) .click() @@ -161,7 +135,7 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t .findByRole('button', { name: /More actions/i }) .should('be.visible') .click() - // see correct item + // see correct button cy.findByRole('menuitem', { name: /Add to your/i }) .should('be.visible') .click() @@ -183,37 +157,43 @@ describe('files_sharing: Public share - header actions menu', { testIsolation: t it('See primary action is moved to menu on small screens', () => { cy.viewport(490, 490) // Check the button does not exist - cy.get('header') - .should('be.visible') - .findByRole('button', { name: 'Download all files' }) - .should('not.exist') - // Open the menu - cy.get('header') - .findByRole('button', { name: /More actions/i }) - .should('be.visible') - .click() - // See that the button is located in the menu - cy.findByRole('menuitem', { name: /Download all files/i }) - .should('be.visible') - // See all other items are also available + cy.get('header').within(() => { + cy.findByRole('button', { name: 'Direct link' }) + .should('not.exist') + cy.findByRole('button', { name: 'Download' }) + .should('not.exist') + cy.findByRole('button', { name: /Add to your/i }) + .should('not.exist') + // Open the menu + cy.findByRole('button', { name: /More actions/i }) + .should('be.visible') + .click() + }) + + // See correct number of menu item cy.findByRole('menu', { name: 'More actions' }) .findAllByRole('menuitem') .should('have.length', 3) - // Click the button to test the download - cy.findByRole('menuitem', { name: /Download all files/i }) - .click() + cy.findByRole('menu', { name: 'More actions' }) + .within(() => { + // See that download, federated share and direct link are moved to the menu + cy.findByRole('menuitem', { name: /^Download/ }) + .should('be.visible') + cy.findByRole('menuitem', { name: /Add to your/i }) + .should('be.visible') + cy.findByRole('menuitem', { name: 'Direct link' }) + .should('be.visible') - // check a file is downloaded - const downloadsFolder = Cypress.config('downloadsFolder') - cy.readFile(`${downloadsFolder}/${shareName}.zip`, null, { timeout: 15000 }) - .should('exist') - .and('have.length.gt', 30) - // Check all files are included - .and(zipFileContains([ - `${shareName}/`, - `${shareName}/foo.txt`, - `${shareName}/subfolder/`, - `${shareName}/subfolder/bar.txt`, - ])) + // See that direct link works + cy.findByRole('menuitem', { name: 'Direct link' }) + .should('be.visible') + .and('have.attr', 'href') + .then((attribute) => expect(attribute).to.match(/^http:\/\/.+\/download$/)) + // See remote share works + cy.findByRole('menuitem', { name: /Add to your/i }) + .should('be.visible') + .click() + }) + cy.findByRole('dialog', { name: /Add to your Nextcloud/i }).should('be.visible') }) }) diff --git a/cypress/e2e/files_sharing/public-share/rename-files.cy.ts b/cypress/e2e/files_sharing/public-share/rename-files.cy.ts new file mode 100644 index 00000000000..5f2fe00e650 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/rename-files.cy.ts @@ -0,0 +1,32 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { getRowForFile, haveValidity, triggerActionForFile } from '../../files/FilesUtils.ts' +import { getShareUrl, setupPublicShare } from './setup-public-share.ts' + +describe('files_sharing: Public share - renaming files', { testIsolation: true }, () => { + + beforeEach(() => { + setupPublicShare() + .then(() => cy.logout()) + .then(() => cy.visit(getShareUrl())) + }) + + it('can rename a file', () => { + // All are visible by default + getRowForFile('foo.txt').should('be.visible') + + triggerActionForFile('foo.txt', 'rename') + + getRowForFile('foo.txt') + .findByRole('textbox', { name: 'Filename' }) + .should('be.visible') + .type('{selectAll}other.txt') + .should(haveValidity('')) + .type('{enter}') + + // See it is renamed + getRowForFile('other.txt').should('be.visible') + }) +}) diff --git a/cypress/e2e/files_sharing/public-share/setup-public-share.ts b/cypress/e2e/files_sharing/public-share/setup-public-share.ts new file mode 100644 index 00000000000..9549552c200 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/setup-public-share.ts @@ -0,0 +1,119 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { User } from '@nextcloud/cypress' +import { openSharingPanel } from '../FilesSharingUtils.ts' + +let user: User +let url: string + +/** + * URL of the share + */ +export function getShareUrl() { + if (url === undefined) { + throw new Error('You need to setup the share first!') + } + return url +} + +/** + * Setup the available data + * @param shareName The name of the shared folder + */ +function setupData(shareName: string) { + cy.mkdir(user, `/${shareName}`) + cy.mkdir(user, `/${shareName}/subfolder`) + cy.uploadContent(user, new Blob(['<content>foo</content>']), 'text/plain', `/${shareName}/foo.txt`) + cy.uploadContent(user, new Blob(['<content>bar</content>']), 'text/plain', `/${shareName}/subfolder/bar.txt`) +} + +/** + * Create a public link share + * @param shareName The name of the shared folder + */ +function createShare(shareName: string) { + cy.login(user) + // open the files app + cy.visit('/apps/files') + // open the sidebar + openSharingPanel(shareName) + // create the share + cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') + cy.findByRole('button', { name: 'Create a new share link' }) + .click() + + // extract the link + return cy.wait('@createShare') + .should(({ response }) => { + const { ocs } = response!.body + url = ocs?.data.url + expect(url).to.match(/^http:\/\//) + }) + .then(() => cy.wrap(url)) +} + +/** + * Adjust share permissions to be editable + */ +function adjustSharePermission() { + // Update the share to be a file drop + cy.findByRole('list', { name: 'Link shares' }) + .findAllByRole('listitem') + .first() + .findByRole('button', { name: /Actions/i }) + .click() + cy.findByRole('menuitem', { name: /Customize link/i }) + .should('be.visible') + .click() + + // Enable upload-edit + cy.get('[data-cy-files-sharing-share-permissions-bundle]') + .should('be.visible') + cy.get('[data-cy-files-sharing-share-permissions-bundle="upload-edit"]') + .click() + // save changes + cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') + cy.findByRole('button', { name: 'Update share' }) + .click() + cy.wait('@updateShare') +} + +/** + * Setup a public share and backup the state. + * If the setup was already done in another run, the state will be restored. + * + * @return The URL of the share + */ +export function setupPublicShare(): Cypress.Chainable<string> { + const shareName = 'shared' + + return cy.task('getVariable', { key: 'public-share-data' }) + .then((data) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { dataSnapshot, dbSnapshot, shareUrl } = data as any || {} + if (dataSnapshot && dbSnapshot) { + cy.restoreDB(dbSnapshot) + cy.restoreData(dataSnapshot) + url = shareUrl + return cy.wrap(shareUrl as string) + } else { + cy.restoreData() + cy.restoreDB() + + const shareData: Record<string, unknown> = {} + return cy.createRandomUser() + .then(($user) => { user = $user }) + .then(() => setupData(shareName)) + .then(() => createShare(shareName)) + .then((value) => { shareData.shareUrl = value }) + .then(() => adjustSharePermission()) + .then(() => cy.backupDB().then((value) => { shareData.dbSnapshot = value })) + .then(() => cy.backupData([user.userId]).then((value) => { shareData.dataSnapshot = value })) + .then(() => cy.task('setVariable', { key: 'public-share-data', value: shareData })) + .then(() => cy.log(`Public share setup, URL: ${shareData.shareUrl}`)) + .then(() => cy.wrap(url)) + } + }) +} diff --git a/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts b/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts new file mode 100644 index 00000000000..8bc4b9b8e15 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts @@ -0,0 +1,169 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { getRowForFile } from '../../files/FilesUtils.ts' +import { openSharingPanel } from '../FilesSharingUtils.ts' + +describe('files_sharing: Public share - File drop', { testIsolation: true }, () => { + + let shareUrl: string + let user: string + const shareName = 'shared' + + before(() => { + cy.createRandomUser().then(($user) => { + user = $user.userId + cy.mkdir($user, `/${shareName}`) + cy.uploadContent($user, new Blob(['content']), 'text/plain', `/${shareName}/foo.txt`) + cy.login($user) + // open the files app + cy.visit('/apps/files') + // open the sidebar + openSharingPanel(shareName) + // create the share + cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') + cy.findByRole('button', { name: 'Create a new share link' }) + .click() + // extract the link + cy.wait('@createShare').should(({ response }) => { + const { ocs } = response?.body ?? {} + shareUrl = ocs?.data.url + expect(shareUrl).to.match(/^http:\/\//) + }) + + // Update the share to be a file drop + cy.findByRole('list', { name: 'Link shares' }) + .findAllByRole('listitem') + .first() + .findByRole('button', { name: /Actions/i }) + .click() + cy.findByRole('menuitem', { name: /Customize link/i }) + .should('be.visible') + .click() + cy.get('[data-cy-files-sharing-share-permissions-bundle]') + .should('be.visible') + cy.get('[data-cy-files-sharing-share-permissions-bundle="file-drop"]') + .click() + + // save the update + cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') + cy.findByRole('button', { name: 'Update share' }) + .click() + cy.wait('@updateShare') + }) + }) + + beforeEach(() => { + cy.logout() + cy.visit(shareUrl) + }) + + it('Cannot see share content', () => { + cy.contains(`Upload files to ${shareName}`) + .should('be.visible') + + // foo exists + cy.userFileExists(user, `${shareName}/foo.txt`).should('be.gt', 0) + // but is not visible + getRowForFile('foo.txt') + .should('not.exist') + }) + + it('Can only see upload files and upload folders menu entries', () => { + cy.contains(`Upload files to ${shareName}`) + .should('be.visible') + + cy.findByRole('button', { name: 'New' }) + .should('be.visible') + .click() + // See upload actions + cy.findByRole('menuitem', { name: 'Upload files' }) + .should('be.visible') + cy.findByRole('menuitem', { name: 'Upload folders' }) + .should('be.visible') + // But no other + cy.findByRole('menu') + .findAllByRole('menuitem') + .should('have.length', 2) + }) + + it('Can only see dedicated upload button', () => { + cy.contains(`Upload files to ${shareName}`) + .should('be.visible') + + cy.findByRole('button', { name: 'Upload' }) + .should('be.visible') + .click() + // See upload actions + cy.findByRole('menuitem', { name: 'Upload files' }) + .should('be.visible') + cy.findByRole('menuitem', { name: 'Upload folders' }) + .should('be.visible') + // But no other + cy.findByRole('menu') + .findAllByRole('menuitem') + .should('have.length', 2) + }) + + it('Can upload files', () => { + cy.contains(`Upload files to ${shareName}`) + .should('be.visible') + + const { promise, resolve } = Promise.withResolvers() + cy.intercept('PUT', '**/public.php/dav/files/**', (request) => { + if (request.url.includes('first.txt')) { + // just continue the first one + request.continue() + } else { + // We delay the second one until we checked that the progress bar is visible + request.on('response', async () => { await promise }) + } + }).as('uploadFile') + + cy.get('[data-cy-files-sharing-file-drop] input[type="file"]') + .should('exist') + .selectFile([ + { fileName: 'first.txt', contents: Buffer.from('8 bytes!') }, + { fileName: 'second.md', contents: Buffer.from('x'.repeat(128)) }, + ], { force: true }) + + cy.wait('@uploadFile') + + cy.findByRole('progressbar') + .should('be.visible') + .and((el) => { expect(Number.parseInt(el.attr('value') ?? '0')).be.gte(50) }) + // continue second request + .then(() => resolve(null)) + + cy.wait('@uploadFile') + + // Check files uploaded + cy.userFileExists(user, `${shareName}/first.txt`).should('eql', 8) + cy.userFileExists(user, `${shareName}/second.md`).should('eql', 128) + }) + + describe('Terms of service', { testIsolation: true }, () => { + before(() => cy.runOccCommand('config:app:set --value "TEST: Some disclaimer text" --type string core shareapi_public_link_disclaimertext')) + beforeEach(() => cy.visit(shareUrl)) + after(() => cy.runOccCommand('config:app:delete core shareapi_public_link_disclaimertext')) + + it('shows ToS on file-drop view', () => { + cy.contains(`Upload files to ${shareName}`) + .should('be.visible') + .should('contain.text', 'agree to the terms of service') + cy.findByRole('button', { name: /Terms of service/i }) + .should('be.visible') + .click() + + cy.findByRole('dialog', { name: 'Terms of service' }) + .should('contain.text', 'TEST: Some disclaimer text') + // close + .findByRole('button', { name: 'Close' }) + .click() + + cy.findByRole('dialog', { name: 'Terms of service' }) + .should('not.exist') + }) + }) +}) diff --git a/cypress/e2e/files_sharing/public-share/view_view-only-no-download.cy.ts b/cypress/e2e/files_sharing/public-share/view_view-only-no-download.cy.ts new file mode 100644 index 00000000000..abcb9ccae62 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/view_view-only-no-download.cy.ts @@ -0,0 +1,104 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { getActionButtonForFile, getRowForFile, navigateToFolder } from '../../files/FilesUtils.ts' +import { openSharingPanel } from '../FilesSharingUtils.ts' + +describe('files_sharing: Public share - View only', { testIsolation: true }, () => { + + let shareUrl: string + const shareName = 'shared' + + before(() => { + cy.createRandomUser().then(($user) => { + cy.mkdir($user, `/${shareName}`) + cy.mkdir($user, `/${shareName}/subfolder`) + cy.uploadContent($user, new Blob([]), 'text/plain', `/${shareName}/foo.txt`) + cy.uploadContent($user, new Blob([]), 'text/plain', `/${shareName}/subfolder/bar.txt`) + cy.login($user) + // open the files app + cy.visit('/apps/files') + // open the sidebar + openSharingPanel(shareName) + // create the share + cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') + cy.findByRole('button', { name: 'Create a new share link' }) + .click() + // extract the link + cy.wait('@createShare').should(({ response }) => { + const { ocs } = response?.body ?? {} + shareUrl = ocs?.data.url + expect(shareUrl).to.match(/^http:\/\//) + }) + + // Update the share to be a view-only-no-download share + cy.findByRole('list', { name: 'Link shares' }) + .findAllByRole('listitem') + .first() + .findByRole('button', { name: /Actions/i }) + .click() + cy.findByRole('menuitem', { name: /Customize link/i }) + .should('be.visible') + .click() + cy.get('[data-cy-files-sharing-share-permissions-bundle]') + .should('be.visible') + cy.get('[data-cy-files-sharing-share-permissions-bundle="read-only"]') + .click() + cy.findByRole('checkbox', { name: 'Hide download' }) + .check({ force: true }) + // save the update + cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') + cy.findByRole('button', { name: 'Update share' }) + .click() + cy.wait('@updateShare') + }) + }) + + beforeEach(() => { + cy.logout() + cy.visit(shareUrl) + }) + + it('Can see the files list', () => { + // foo exists + getRowForFile('foo.txt') + .should('be.visible') + }) + + it('But no actions available', () => { + // foo exists + getRowForFile('foo.txt') + .should('be.visible') + // but no actions + getActionButtonForFile('foo.txt') + .should('not.exist') + + // TODO: We really need Viewer in the server repo. + // So we could at least test viewing images + }) + + it('Can navigate to subfolder', () => { + getRowForFile('subfolder') + .should('be.visible') + + navigateToFolder('subfolder') + + getRowForFile('bar.txt') + .should('be.visible') + + // but also no actions + getActionButtonForFile('bar.txt') + .should('not.exist') + }) + + it('Cannot upload files', () => { + // wait for file list to be ready + getRowForFile('foo.txt') + .should('be.visible') + + cy.contains('button', 'New') + .should('be.visible') + .and('be.disabled') + }) +}) diff --git a/cypress/e2e/files_sharing/public-share/view_view-only.cy.ts b/cypress/e2e/files_sharing/public-share/view_view-only.cy.ts new file mode 100644 index 00000000000..4a8aa6b89a3 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/view_view-only.cy.ts @@ -0,0 +1,107 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { getActionsForFile, getRowForFile, navigateToFolder } from '../../files/FilesUtils.ts' +import { openSharingPanel } from '../FilesSharingUtils.ts' + +describe('files_sharing: Public share - View only', { testIsolation: true }, () => { + + let shareUrl: string + const shareName = 'shared' + + before(() => { + cy.createRandomUser().then(($user) => { + cy.mkdir($user, `/${shareName}`) + cy.mkdir($user, `/${shareName}/subfolder`) + cy.uploadContent($user, new Blob(['content']), 'text/plain', `/${shareName}/foo.txt`) + cy.uploadContent($user, new Blob(['content']), 'text/plain', `/${shareName}/subfolder/bar.txt`) + cy.login($user) + // open the files app + cy.visit('/apps/files') + // open the sidebar + openSharingPanel(shareName) + // create the share + cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') + cy.findByRole('button', { name: 'Create a new share link' }) + .click() + // extract the link + cy.wait('@createShare').should(({ response }) => { + const { ocs } = response?.body ?? {} + shareUrl = ocs?.data.url + expect(shareUrl).to.match(/^http:\/\//) + }) + + // Update the share to be a view-only-no-download share + cy.findByRole('list', { name: 'Link shares' }) + .findAllByRole('listitem') + .first() + .findByRole('button', { name: /Actions/i }) + .click() + cy.findByRole('menuitem', { name: /Customize link/i }) + .should('be.visible') + .click() + cy.get('[data-cy-files-sharing-share-permissions-bundle]') + .should('be.visible') + cy.get('[data-cy-files-sharing-share-permissions-bundle="read-only"]') + .click() + // save the update + cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') + cy.findByRole('button', { name: 'Update share' }) + .click() + cy.wait('@updateShare') + }) + }) + + beforeEach(() => { + cy.logout() + cy.visit(shareUrl) + }) + + it('Can see the files list', () => { + // foo exists + getRowForFile('foo.txt') + .should('be.visible') + }) + + it('Can navigate to subfolder', () => { + getRowForFile('subfolder') + .should('be.visible') + + navigateToFolder('subfolder') + + getRowForFile('bar.txt') + .should('be.visible') + }) + + it('Cannot upload files', () => { + // wait for file list to be ready + getRowForFile('foo.txt') + .should('be.visible') + + cy.contains('button', 'New') + .should('be.visible') + .and('be.disabled') + }) + + it('Only download action is actions available', () => { + getActionsForFile('foo.txt') + .should('be.visible') + .click() + + // Only the download action + cy.findByRole('menuitem', { name: 'Download' }) + .should('be.visible') + cy.findAllByRole('menuitem') + .should('have.length', 1) + + // Can download + cy.findByRole('menuitem', { name: 'Download' }).click() + // check a file is downloaded + const downloadsFolder = Cypress.config('downloadsFolder') + cy.readFile(`${downloadsFolder}/foo.txt`, 'utf-8', { timeout: 15000 }) + .should('exist') + .and('have.length.gt', 5) + .and('contain', 'content') + }) +}) |