mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
test: Add end-to-end tests for new public share Vue UI
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
be884eeaec
commit
4a90d5328c
21 changed files with 879 additions and 120 deletions
|
|
@ -2,12 +2,8 @@
|
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { beforeEach } from 'vitest'
|
||||
|
||||
window.OC = { ...window.OC }
|
||||
window.OCA = { ...window.OCA }
|
||||
window.OCP = { ...window.OCP }
|
||||
|
||||
beforeEach(() => {
|
||||
window.location = new URL('http://nextcloud.local')
|
||||
})
|
||||
window._oc_webroot = ''
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ describe('Edit locally action execute tests', () => {
|
|||
data: { ocs: { data: { token: 'foobar' } } },
|
||||
}))
|
||||
const showError = vi.spyOn(nextcloudDialogs, 'showError')
|
||||
const windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
|
|
@ -138,7 +139,7 @@ describe('Edit locally action execute tests', () => {
|
|||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://nextcloud.local/ocs/v2.php/apps/files/api/v1/openlocaleditor?format=json', { path: '/foobar.txt' })
|
||||
expect(showError).toBeCalledTimes(0)
|
||||
expect(window.location.href).toBe('nc://open/test@nextcloud.local/foobar.txt?token=foobar')
|
||||
expect(windowOpenSpy).toBeCalledWith('nc://open/test@nextcloud.local/foobar.txt?token=foobar', '_self')
|
||||
})
|
||||
|
||||
test('Edit locally fails and shows error', async () => {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const openLocalClient = async function(path: string) {
|
|||
let url = `nc://open/${uid}@` + window.location.host + encodePath(path)
|
||||
url += '?token=' + result.data.ocs.data.token
|
||||
|
||||
window.location.href = url
|
||||
window.open(url, '_self')
|
||||
} catch (error) {
|
||||
showError(t('files', 'Failed to redirect to client'))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,19 @@ export default defineConfig({
|
|||
|
||||
on('task', { removeDirectory })
|
||||
|
||||
// This allows to store global data (e.g. the name of a snapshot)
|
||||
// because Cypress.env() and other options are local to the current spec file.
|
||||
const data = {}
|
||||
on('task', {
|
||||
setVariable({ key, value }) {
|
||||
data[key] = value
|
||||
return null
|
||||
},
|
||||
getVariable({ key }) {
|
||||
return data[key] ?? null
|
||||
},
|
||||
})
|
||||
|
||||
// Disable spell checking to prevent rendering differences
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ export const configureNextcloud = async function() {
|
|||
// Saving DB state
|
||||
console.log('├─ Creating init DB snapshot...')
|
||||
await runExec(container, ['cp', '/var/www/html/data/owncloud.db', '/var/www/html/data/owncloud.db-init'], true)
|
||||
console.log('├─ Creating init data backup...')
|
||||
await runExec(container, ['tar', 'cf', 'data-init.tar', 'admin'], true, undefined, '/var/www/html/data')
|
||||
|
||||
console.log('└─ Nextcloud is now ready to use 🎉')
|
||||
}
|
||||
|
|
@ -277,9 +279,11 @@ const runExec = async function(
|
|||
command: string[],
|
||||
verbose = false,
|
||||
user = 'www-data',
|
||||
workdir?: string,
|
||||
): Promise<string> {
|
||||
const exec = await container.exec({
|
||||
Cmd: command,
|
||||
WorkingDir: workdir,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
User: user,
|
||||
|
|
@ -296,7 +300,7 @@ const runExec = async function(
|
|||
stream.on('data', str => {
|
||||
str = str.trim()
|
||||
// Remove non printable characters
|
||||
.replace(/[^\x20-\x7E]+/g, '')
|
||||
.replace(/[^\x0A\x0D\x20-\x7E]+/g, '')
|
||||
// Remove non alphanumeric leading characters
|
||||
.replace(/^[^a-z]/gi, '')
|
||||
output += str
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-r
|
|||
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 getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).findByRole('button', { name: 'Actions' })
|
||||
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).findByRole('button', { name: 'Actions' })
|
||||
|
||||
export const triggerActionForFileId = (fileid: number, actionId: string) => {
|
||||
getActionButtonForFileId(fileid).click()
|
||||
|
|
@ -34,7 +34,7 @@ export const moveFile = (fileName: string, dirPath: string) => {
|
|||
|
||||
cy.get('.file-picker').within(() => {
|
||||
// intercept the copy so we can wait for it
|
||||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile')
|
||||
cy.intercept('MOVE', /\/(remote|public)\.php\/dav\/files\//).as('moveFile')
|
||||
|
||||
if (dirPath === '/') {
|
||||
// select home folder
|
||||
|
|
@ -65,7 +65,7 @@ export const copyFile = (fileName: string, dirPath: string) => {
|
|||
|
||||
cy.get('.file-picker').within(() => {
|
||||
// intercept the copy so we can wait for it
|
||||
cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile')
|
||||
cy.intercept('COPY', /\/(remote|public)\.php\/dav\/files\//).as('copyFile')
|
||||
|
||||
if (dirPath === '/') {
|
||||
// select home folder
|
||||
|
|
@ -95,7 +95,7 @@ export const renameFile = (fileName: string, newFileName: string) => {
|
|||
triggerActionForFile(fileName, 'rename')
|
||||
|
||||
// intercept the move so we can wait for it
|
||||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile')
|
||||
cy.intercept('MOVE', /\/(remote|public)\.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}`)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
49
cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts
Normal file
49
cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts
Normal file
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
141
cypress/e2e/files_sharing/public-share/download-files.cy.ts
Normal file
141
cypress/e2e/files_sharing/public-share/download-files.cy.ts
Normal file
|
|
@ -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>')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
32
cypress/e2e/files_sharing/public-share/rename-files.cy.ts
Normal file
32
cypress/e2e/files_sharing/public-share/rename-files.cy.ts
Normal file
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
119
cypress/e2e/files_sharing/public-share/setup-public-share.ts
Normal file
119
cypress/e2e/files_sharing/public-share/setup-public-share.ts
Normal file
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
169
cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts
Normal file
169
cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts
Normal file
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
107
cypress/e2e/files_sharing/public-share/view_view-only.cy.ts
Normal file
107
cypress/e2e/files_sharing/public-share/view_view-only.cy.ts
Normal file
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
@ -66,6 +66,8 @@ declare global {
|
|||
*/
|
||||
runOccCommand(command: string, options?: Partial<Cypress.ExecOptions>): Cypress.Chainable<Cypress.Exec>,
|
||||
|
||||
userFileExists(user: string, path: string): Cypress.Chainable<number>
|
||||
|
||||
/**
|
||||
* Create a snapshot of the current database
|
||||
*/
|
||||
|
|
@ -75,7 +77,11 @@ declare global {
|
|||
* Restore a snapshot of the database
|
||||
* Default is the post-setup state
|
||||
*/
|
||||
restoreDB(snapshot?: string): Cypress.Chainable,
|
||||
restoreDB(snapshot?: string): Cypress.Chainable
|
||||
|
||||
backupData(users?: string[]): Cypress.Chainable<string>
|
||||
|
||||
restoreData(snapshot?: string): Cypress.Chainable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +91,7 @@ Cypress.env('baseUrl', url)
|
|||
|
||||
/**
|
||||
* Enable or disable a user
|
||||
* TODO: standardise in @nextcloud/cypress
|
||||
* TODO: standardize in @nextcloud/cypress
|
||||
*
|
||||
* @param {User} user the user to dis- / enable
|
||||
* @param {boolean} enable True if the user should be enable, false to disable
|
||||
|
|
@ -112,7 +118,7 @@ Cypress.Commands.add('enableUser', (user: User, enable = true) => {
|
|||
|
||||
/**
|
||||
* cy.uploadedFile - uploads a file from the fixtures folder
|
||||
* TODO: standardise in @nextcloud/cypress
|
||||
* TODO: standardize in @nextcloud/cypress
|
||||
*
|
||||
* @param {User} user the owner of the file, e.g. admin
|
||||
* @param {string} fixture the fixture file name, e.g. image1.jpg
|
||||
|
|
@ -188,7 +194,7 @@ Cypress.Commands.add('mkdir', (user: User, target: string) => {
|
|||
|
||||
/**
|
||||
* cy.uploadedContent - uploads a raw content
|
||||
* TODO: standardise in @nextcloud/cypress
|
||||
* TODO: standardize in @nextcloud/cypress
|
||||
*
|
||||
* @param {User} user the owner of the file, e.g. admin
|
||||
* @param {Blob} blob the content to upload
|
||||
|
|
@ -288,6 +294,13 @@ Cypress.Commands.add('runOccCommand', (command: string, options?: Partial<Cypres
|
|||
return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-server php ./occ ${command}`, options)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('userFileExists', (user: string, path: string) => {
|
||||
user.replaceAll('"', '\\"')
|
||||
path.replaceAll('"', '\\"').replaceAll(/^\/+/gm, '')
|
||||
return cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server stat --printf="%s" "data/${user}/files/${path}"`, { failOnNonZeroExit: true })
|
||||
.then((exec) => Number.parseInt(exec.stdout || '0'))
|
||||
})
|
||||
|
||||
Cypress.Commands.add('backupDB', (): Cypress.Chainable<string> => {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db /var/www/html/data/owncloud.db-${randomString}`)
|
||||
|
|
@ -299,3 +312,18 @@ Cypress.Commands.add('restoreDB', (snapshot: string = 'init') => {
|
|||
cy.exec(`docker exec --user www-data nextcloud-cypress-tests-server cp /var/www/html/data/owncloud.db-${snapshot} /var/www/html/data/owncloud.db`)
|
||||
cy.log(`Restored snapshot ${snapshot}`)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('backupData', (users: string[] = ['admin']) => {
|
||||
const snapshot = Math.random().toString(36).substring(7)
|
||||
const toBackup = users.map((user) => `'${user.replaceAll('\\', '').replaceAll('\'', '\\\'')}'`).join(' ')
|
||||
cy.exec(`docker exec --user www-data rm /var/www/html/data/data-${snapshot}.tar`, { failOnNonZeroExit: false })
|
||||
cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests-server tar cf /var/www/html/data/data-${snapshot}.tar ${toBackup}`)
|
||||
return cy.wrap(snapshot as string)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('restoreData', (snapshot?: string) => {
|
||||
snapshot = snapshot ?? 'init'
|
||||
snapshot.replaceAll('\\', '').replaceAll('"', '\\"')
|
||||
cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests-server rm -vfr $(tar --exclude='*/*' -tf '/var/www/html/data/data-${snapshot}.tar')`)
|
||||
cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests-server tar -xf '/var/www/html/data/data-${snapshot}.tar'`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ export function zipFileContains(expectedFiles: string[]) {
|
|||
const blob = new Blob([buffer])
|
||||
const zip = new ZipReader(blob.stream())
|
||||
// check the real file names
|
||||
const entries = (await zip.getEntries()).map((e) => e.filename)
|
||||
const entries = (await zip.getEntries()).map((e) => e.filename).sort()
|
||||
console.info('Zip contains entries:', entries)
|
||||
expect(entries).to.deep.equal(expectedFiles)
|
||||
expect(entries).to.deep.equal(expectedFiles.sort())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -27,7 +27,7 @@
|
|||
"@nextcloud/moment": "^1.3.1",
|
||||
"@nextcloud/password-confirmation": "^5.1.1",
|
||||
"@nextcloud/paths": "^2.2.1",
|
||||
"@nextcloud/router": "^3.0.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/sharing": "^0.2.3",
|
||||
"@nextcloud/upload": "^1.6.0",
|
||||
"@nextcloud/vue": "^8.17.1",
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
"@nextcloud/moment": "^1.3.1",
|
||||
"@nextcloud/password-confirmation": "^5.1.1",
|
||||
"@nextcloud/paths": "^2.2.1",
|
||||
"@nextcloud/router": "^3.0.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/sharing": "^0.2.3",
|
||||
"@nextcloud/upload": "^1.6.0",
|
||||
"@nextcloud/vue": "^8.17.1",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export default defineConfig({
|
|||
test: {
|
||||
include: ['{apps,core}/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
||||
environment: 'jsdom',
|
||||
environmentOptions: {
|
||||
jsdom: {
|
||||
url: 'http://nextcloud.local',
|
||||
},
|
||||
},
|
||||
coverage: {
|
||||
include: ['apps/*/src/**', 'core/src/**'],
|
||||
exclude: ['**.spec.*', '**.test.*', '**.cy.*', 'core/src/tests/**'],
|
||||
|
|
|
|||
Loading…
Reference in a new issue