From: skjnldsv Date: Thu, 7 Nov 2024 18:00:35 +0000 (+0100) Subject: chore(files): add selection cypress tests X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=54954bf934cf11e484ef586ad726a50b7ec99a84;p=nextcloud-server.git chore(files): add selection cypress tests Signed-off-by: skjnldsv --- diff --git a/cypress/e2e/files/FilesUtils.ts b/cypress/e2e/files/FilesUtils.ts index 182972ee44c..f435272b9a2 100644 --- a/cypress/e2e/files/FilesUtils.ts +++ b/cypress/e2e/files/FilesUtils.ts @@ -33,13 +33,22 @@ export const triggerInlineActionForFile = (filename: string, actionId: string) = } export const selectAllFiles = () => { - cy.get('[data-cy-files-list-selection-checkbox]').findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-files-list-selection-checkbox]') + .findByRole('checkbox', { checked: false }) + .click({ force: true }) +} +export const deselectAllFiles = () => { + cy.get('[data-cy-files-list-selection-checkbox]') + .findByRole('checkbox', { checked: true }) + .click({ force: true }) } -export const selectRowForFile = (filename: string) => { + +export const selectRowForFile = (filename: string, options: Partial = {}) => { getRowForFile(filename) .find('[data-cy-files-list-row-checkbox]') .findByRole('checkbox') - .click({ force: true }) + // don't use click to avoid triggering side effects events + .trigger('change', { ...options, force: true }) .should('be.checked') cy.get('[data-cy-files-list-selection-checkbox]').findByRole('checkbox').should('satisfy', (elements) => { return elements.length === 1 && (elements[0].checked === true || elements[0].indeterminate === true) diff --git a/cypress/e2e/files/files-copy-move.cy.ts b/cypress/e2e/files/files-copy-move.cy.ts new file mode 100644 index 00000000000..086248eef3c --- /dev/null +++ b/cypress/e2e/files/files-copy-move.cy.ts @@ -0,0 +1,177 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getRowForFile, moveFile, copyFile, navigateToFolder } from './FilesUtils.ts' + +describe('Files: Move or copy files', { testIsolation: true }, () => { + let currentUser + beforeEach(() => { + cy.createRandomUser().then((user) => { + currentUser = user + cy.login(user) + }) + }) + afterEach(() => { + // nice to have cleanup + cy.deleteUser(currentUser) + }) + + + it('Can copy a file to new folder', () => { + // Prepare initial state + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + .mkdir(currentUser, '/new-folder') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('original.txt', 'new-folder') + + navigateToFolder('new-folder') + + cy.url().should('contain', 'dir=/new-folder') + getRowForFile('original.txt').should('be.visible') + getRowForFile('new-folder').should('not.exist') + }) + + it('Can move a file to new folder', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + .mkdir(currentUser, '/new-folder') + cy.login(currentUser) + cy.visit('/apps/files') + + moveFile('original.txt', 'new-folder') + + // wait until visible again + getRowForFile('new-folder').should('be.visible') + + // original should be moved -> not exist anymore + getRowForFile('original.txt').should('not.exist') + navigateToFolder('new-folder') + + cy.url().should('contain', 'dir=/new-folder') + getRowForFile('original.txt').should('be.visible') + getRowForFile('new-folder').should('not.exist') + }) + + /** + * Test for https://github.com/nextcloud/server/issues/41768 + */ + it('Can move a file to folder with similar name', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original') + .mkdir(currentUser, '/original folder') + cy.login(currentUser) + cy.visit('/apps/files') + + moveFile('original', 'original folder') + + // wait until visible again + getRowForFile('original folder').should('be.visible') + + // original should be moved -> not exist anymore + getRowForFile('original').should('not.exist') + navigateToFolder('original folder') + + cy.url().should('contain', 'dir=/original%20folder') + getRowForFile('original').should('be.visible') + getRowForFile('original folder').should('not.exist') + }) + + it('Can move a file to its parent folder', () => { + cy.mkdir(currentUser, '/new-folder') + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/new-folder/original.txt') + cy.login(currentUser) + cy.visit('/apps/files') + + navigateToFolder('new-folder') + cy.url().should('contain', 'dir=/new-folder') + + moveFile('original.txt', '/') + + // wait until visible again + cy.get('main').contains('No files in here').should('be.visible') + + // original should be moved -> not exist anymore + getRowForFile('original.txt').should('not.exist') + + cy.visit('/apps/files') + getRowForFile('new-folder').should('be.visible') + getRowForFile('original.txt').should('be.visible') + }) + + it('Can copy a file to same folder', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('original.txt', '.') + + getRowForFile('original.txt').should('be.visible') + getRowForFile('original (copy).txt').should('be.visible') + }) + + it('Can copy a file multiple times to same folder', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original (copy).txt') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('original.txt', '.') + + getRowForFile('original.txt').should('be.visible') + getRowForFile('original (copy 2).txt').should('be.visible') + }) + + /** + * Test that a copied folder with a dot will be renamed correctly ('foo.bar' -> 'foo.bar (copy)') + * Test for: https://github.com/nextcloud/server/issues/43843 + */ + it('Can copy a folder to same folder', () => { + cy.mkdir(currentUser, '/foo.bar') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('foo.bar', '.') + + getRowForFile('foo.bar').should('be.visible') + getRowForFile('foo.bar (copy)').should('be.visible') + }) + + /** Test for https://github.com/nextcloud/server/issues/43329 */ + context('escaping file and folder names', () => { + it('Can handle files with special characters', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + .mkdir(currentUser, '/can\'t say') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('original.txt', 'can\'t say') + + navigateToFolder('can\'t say') + + cy.url().should('contain', 'dir=/can%27t%20say') + getRowForFile('original.txt').should('be.visible') + getRowForFile('can\'t say').should('not.exist') + }) + + /** + * If escape is set to false (required for test above) then "foo" would result in "foo" if sanitizing is not disabled + * We should disable it as vue already escapes the text when using v-text + */ + it('does not incorrectly sanitize file names', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') + .mkdir(currentUser, '/foo') + cy.login(currentUser) + cy.visit('/apps/files') + + copyFile('original.txt', 'foo') + + navigateToFolder('foo') + + cy.url().should('contain', 'dir=/%3Ca%20href%3D%22%23%22%3Efoo') + getRowForFile('original.txt').should('be.visible') + getRowForFile('foo').should('not.exist') + }) + }) +}) diff --git a/cypress/e2e/files/files-selection.cy.ts b/cypress/e2e/files/files-selection.cy.ts new file mode 100644 index 00000000000..04991b74fb8 --- /dev/null +++ b/cypress/e2e/files/files-selection.cy.ts @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { User } from '@nextcloud/cypress' +import { deselectAllFiles, selectAllFiles, selectRowForFile } from './FilesUtils' + +const files = { + 'image.jpg': 'image/jpeg', + 'document.pdf': 'application/pdf', + 'archive.zip': 'application/zip', + 'audio.mp3': 'audio/mpeg', + 'video.mp4': 'video/mp4', + 'readme.md': 'text/markdown', + 'welcome.txt': 'text/plain', +} +const filesCount = Object.keys(files).length + +describe('files: Select all files', { testIsolation: true }, () => { + let user: User + + before(() => { + cy.createRandomUser().then(($user) => { + user = $user + Object.keys(files).forEach((file) => { + cy.uploadContent(user, new Blob(), files[file], '/' + file) + }) + }) + }) + + beforeEach(() => { + cy.login(user) + cy.visit('/apps/files') + }) + + it('Can select and unselect all files', () => { + cy.get('[data-cy-files-list-row-fileid]').should('have.length', filesCount) + cy.get('[data-cy-files-list-row-checkbox]').should('have.length', filesCount) + + selectAllFiles() + + cy.get('.files-list__selected').should('have.text', '7 selected') + cy.get('[data-cy-files-list-row-checkbox]').findByRole('checkbox').should('be.checked') + + deselectAllFiles() + + cy.get('.files-list__selected').should('not.exist') + cy.get('[data-cy-files-list-row-checkbox]').findByRole('checkbox').should('not.be.checked') + }) + + it('Can select some files randomly', () => { + const randomFiles = Object.keys(files).reduce((acc, file) => { + if (Math.random() > 0.1) { + acc.push(file) + } + return acc + }, [] as string[]) + + randomFiles.forEach(name => selectRowForFile(name)) + + cy.get('.files-list__selected').should('have.text', `${randomFiles.length} selected`) + cy.get('[data-cy-files-list-row-checkbox] input[type="checkbox"]:checked').should('have.length', randomFiles.length) + }) + + it('Can select range of files with shift key', () => { + cy.get('[data-cy-files-list-row-checkbox]').should('have.length', filesCount) + selectRowForFile('audio.mp3') + cy.window().trigger('keydown', { shiftKey: true }) + selectRowForFile('readme.md', { shiftKey: true }) + cy.window().trigger('keyup', { shiftKey: false }) + + cy.get('.files-list__selected').should('have.text', '4 selected') + cy.get('[data-cy-files-list-row-checkbox] input[type="checkbox"]:checked').should('have.length', 4) + + }) +}) diff --git a/cypress/e2e/files/files-sorting.cy.ts b/cypress/e2e/files/files-sorting.cy.ts new file mode 100644 index 00000000000..250c5f195a6 --- /dev/null +++ b/cypress/e2e/files/files-sorting.cy.ts @@ -0,0 +1,270 @@ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +describe('Files: Sorting the file list', { testIsolation: true }, () => { + let currentUser + beforeEach(() => { + cy.createRandomUser().then((user) => { + currentUser = user + cy.login(user) + }) + }) + + it('Files are sorted by name ascending by default', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1 first.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/z last.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/A.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/Ä.txt') + .mkdir(currentUser, '/m') + .mkdir(currentUser, '/4') + cy.login(currentUser) + cy.visit('/apps/files') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('4') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('m') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 first.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('A.txt') + break + case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('Ä.txt') + break + case 5: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 6: expect($row.attr('data-cy-files-list-row-name')).to.eq('z last.txt') + break + } + }) + }) + + /** + * Regression test of https://github.com/nextcloud/server/issues/45829 + */ + it('Filesnames with numbers are sorted by name ascending by default', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/name.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_03.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_02.txt') + .uploadContent(currentUser, new Blob(), 'text/plain', '/name_01.txt') + cy.login(currentUser) + cy.visit('/apps/files') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('name.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_01.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_02.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_03.txt') + break + } + }) + }) + + it('Can sort by size', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1 tiny.txt') + .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z big.txt') + .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a medium.txt') + .mkdir(currentUser, '/folder') + cy.login(currentUser) + cy.visit('/apps/files') + + // click sort button + cy.get('th').contains('button', 'Size').click() + // sorting is set + cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'ascending') + // Files are sorted + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('folder') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 tiny.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('a medium.txt') + break + case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('z big.txt') + break + } + }) + + // click sort button + cy.get('th').contains('button', 'Size').click() + // sorting is set + cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'descending') + // Files are sorted + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('folder') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z big.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('a medium.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 tiny.txt') + break + } + }) + }) + + it('Can sort by mtime', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1.txt', Date.now() / 1000 - 86400 - 1000) + .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z.txt', Date.now() / 1000 - 86400) + .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a.txt', Date.now() / 1000 - 86400 - 500) + cy.login(currentUser) + cy.visit('/apps/files') + + // click sort button + cy.get('th').contains('button', 'Modified').click() + // sorting is set + cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'ascending') + // Files are sorted + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') // uploaded right now + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') // fake time of yesterday + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') // fake time of yesterday and few minutes + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') // fake time of yesterday and ~15 minutes ago + break + } + }) + + // reverse order + cy.get('th').contains('button', 'Modified').click() + cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'descending') + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') // uploaded right now + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') // fake time of yesterday + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') // fake time of yesterday and few minutes + break + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') // fake time of yesterday and ~15 minutes ago + break + } + }) + }) + + it('Favorites are sorted first', () => { + cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1.txt', Date.now() / 1000 - 86400 - 1000) + .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z.txt', Date.now() / 1000 - 86400) + .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a.txt', Date.now() / 1000 - 86400 - 500) + .setFileAsFavorite(currentUser, '/a.txt') + cy.login(currentUser) + cy.visit('/apps/files') + + cy.log('By name - ascending') + cy.get('th').contains('button', 'Name').click() + cy.contains('th', 'Name').should('have.attr', 'aria-sort', 'ascending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + } + }) + + cy.log('By name - descending') + cy.get('th').contains('button', 'Name').click() + cy.contains('th', 'Name').should('have.attr', 'aria-sort', 'descending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + } + }) + + cy.log('By size - ascending') + cy.get('th').contains('button', 'Size').click() + cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'ascending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + } + }) + + cy.log('By size - descending') + cy.get('th').contains('button', 'Size').click() + cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'descending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + } + }) + + cy.log('By mtime - ascending') + cy.get('th').contains('button', 'Modified').click() + cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'ascending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + } + }) + + cy.log('By mtime - descending') + cy.get('th').contains('button', 'Modified').click() + cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'descending') + + cy.get('[data-cy-files-list-row]').each(($row, index) => { + switch (index) { + case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') + break + case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') + break + case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') + break + case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') + break + } + }) + }) +}) diff --git a/cypress/e2e/files/files_copy-move.cy.ts b/cypress/e2e/files/files_copy-move.cy.ts deleted file mode 100644 index 086248eef3c..00000000000 --- a/cypress/e2e/files/files_copy-move.cy.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { getRowForFile, moveFile, copyFile, navigateToFolder } from './FilesUtils.ts' - -describe('Files: Move or copy files', { testIsolation: true }, () => { - let currentUser - beforeEach(() => { - cy.createRandomUser().then((user) => { - currentUser = user - cy.login(user) - }) - }) - afterEach(() => { - // nice to have cleanup - cy.deleteUser(currentUser) - }) - - - it('Can copy a file to new folder', () => { - // Prepare initial state - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - .mkdir(currentUser, '/new-folder') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('original.txt', 'new-folder') - - navigateToFolder('new-folder') - - cy.url().should('contain', 'dir=/new-folder') - getRowForFile('original.txt').should('be.visible') - getRowForFile('new-folder').should('not.exist') - }) - - it('Can move a file to new folder', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - .mkdir(currentUser, '/new-folder') - cy.login(currentUser) - cy.visit('/apps/files') - - moveFile('original.txt', 'new-folder') - - // wait until visible again - getRowForFile('new-folder').should('be.visible') - - // original should be moved -> not exist anymore - getRowForFile('original.txt').should('not.exist') - navigateToFolder('new-folder') - - cy.url().should('contain', 'dir=/new-folder') - getRowForFile('original.txt').should('be.visible') - getRowForFile('new-folder').should('not.exist') - }) - - /** - * Test for https://github.com/nextcloud/server/issues/41768 - */ - it('Can move a file to folder with similar name', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original') - .mkdir(currentUser, '/original folder') - cy.login(currentUser) - cy.visit('/apps/files') - - moveFile('original', 'original folder') - - // wait until visible again - getRowForFile('original folder').should('be.visible') - - // original should be moved -> not exist anymore - getRowForFile('original').should('not.exist') - navigateToFolder('original folder') - - cy.url().should('contain', 'dir=/original%20folder') - getRowForFile('original').should('be.visible') - getRowForFile('original folder').should('not.exist') - }) - - it('Can move a file to its parent folder', () => { - cy.mkdir(currentUser, '/new-folder') - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/new-folder/original.txt') - cy.login(currentUser) - cy.visit('/apps/files') - - navigateToFolder('new-folder') - cy.url().should('contain', 'dir=/new-folder') - - moveFile('original.txt', '/') - - // wait until visible again - cy.get('main').contains('No files in here').should('be.visible') - - // original should be moved -> not exist anymore - getRowForFile('original.txt').should('not.exist') - - cy.visit('/apps/files') - getRowForFile('new-folder').should('be.visible') - getRowForFile('original.txt').should('be.visible') - }) - - it('Can copy a file to same folder', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('original.txt', '.') - - getRowForFile('original.txt').should('be.visible') - getRowForFile('original (copy).txt').should('be.visible') - }) - - it('Can copy a file multiple times to same folder', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original (copy).txt') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('original.txt', '.') - - getRowForFile('original.txt').should('be.visible') - getRowForFile('original (copy 2).txt').should('be.visible') - }) - - /** - * Test that a copied folder with a dot will be renamed correctly ('foo.bar' -> 'foo.bar (copy)') - * Test for: https://github.com/nextcloud/server/issues/43843 - */ - it('Can copy a folder to same folder', () => { - cy.mkdir(currentUser, '/foo.bar') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('foo.bar', '.') - - getRowForFile('foo.bar').should('be.visible') - getRowForFile('foo.bar (copy)').should('be.visible') - }) - - /** Test for https://github.com/nextcloud/server/issues/43329 */ - context('escaping file and folder names', () => { - it('Can handle files with special characters', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - .mkdir(currentUser, '/can\'t say') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('original.txt', 'can\'t say') - - navigateToFolder('can\'t say') - - cy.url().should('contain', 'dir=/can%27t%20say') - getRowForFile('original.txt').should('be.visible') - getRowForFile('can\'t say').should('not.exist') - }) - - /** - * If escape is set to false (required for test above) then "foo" would result in "foo" if sanitizing is not disabled - * We should disable it as vue already escapes the text when using v-text - */ - it('does not incorrectly sanitize file names', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/original.txt') - .mkdir(currentUser, '/foo') - cy.login(currentUser) - cy.visit('/apps/files') - - copyFile('original.txt', 'foo') - - navigateToFolder('foo') - - cy.url().should('contain', 'dir=/%3Ca%20href%3D%22%23%22%3Efoo') - getRowForFile('original.txt').should('be.visible') - getRowForFile('foo').should('not.exist') - }) - }) -}) diff --git a/cypress/e2e/files/files_sorting.cy.ts b/cypress/e2e/files/files_sorting.cy.ts deleted file mode 100644 index 250c5f195a6..00000000000 --- a/cypress/e2e/files/files_sorting.cy.ts +++ /dev/null @@ -1,270 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -describe('Files: Sorting the file list', { testIsolation: true }, () => { - let currentUser - beforeEach(() => { - cy.createRandomUser().then((user) => { - currentUser = user - cy.login(user) - }) - }) - - it('Files are sorted by name ascending by default', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1 first.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/z last.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/A.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/Ä.txt') - .mkdir(currentUser, '/m') - .mkdir(currentUser, '/4') - cy.login(currentUser) - cy.visit('/apps/files') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('4') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('m') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 first.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('A.txt') - break - case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('Ä.txt') - break - case 5: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 6: expect($row.attr('data-cy-files-list-row-name')).to.eq('z last.txt') - break - } - }) - }) - - /** - * Regression test of https://github.com/nextcloud/server/issues/45829 - */ - it('Filesnames with numbers are sorted by name ascending by default', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/name.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/name_03.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/name_02.txt') - .uploadContent(currentUser, new Blob(), 'text/plain', '/name_01.txt') - cy.login(currentUser) - cy.visit('/apps/files') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('name.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_01.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_02.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('name_03.txt') - break - } - }) - }) - - it('Can sort by size', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1 tiny.txt') - .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z big.txt') - .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a medium.txt') - .mkdir(currentUser, '/folder') - cy.login(currentUser) - cy.visit('/apps/files') - - // click sort button - cy.get('th').contains('button', 'Size').click() - // sorting is set - cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'ascending') - // Files are sorted - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('folder') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 tiny.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('a medium.txt') - break - case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('z big.txt') - break - } - }) - - // click sort button - cy.get('th').contains('button', 'Size').click() - // sorting is set - cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'descending') - // Files are sorted - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('folder') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z big.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('a medium.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 4: expect($row.attr('data-cy-files-list-row-name')).to.eq('1 tiny.txt') - break - } - }) - }) - - it('Can sort by mtime', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1.txt', Date.now() / 1000 - 86400 - 1000) - .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z.txt', Date.now() / 1000 - 86400) - .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a.txt', Date.now() / 1000 - 86400 - 500) - cy.login(currentUser) - cy.visit('/apps/files') - - // click sort button - cy.get('th').contains('button', 'Modified').click() - // sorting is set - cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'ascending') - // Files are sorted - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') // uploaded right now - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') // fake time of yesterday - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') // fake time of yesterday and few minutes - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') // fake time of yesterday and ~15 minutes ago - break - } - }) - - // reverse order - cy.get('th').contains('button', 'Modified').click() - cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'descending') - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') // uploaded right now - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') // fake time of yesterday - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') // fake time of yesterday and few minutes - break - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') // fake time of yesterday and ~15 minutes ago - break - } - }) - }) - - it('Favorites are sorted first', () => { - cy.uploadContent(currentUser, new Blob(), 'text/plain', '/1.txt', Date.now() / 1000 - 86400 - 1000) - .uploadContent(currentUser, new Blob(['a'.repeat(1024)]), 'text/plain', '/z.txt', Date.now() / 1000 - 86400) - .uploadContent(currentUser, new Blob(['a'.repeat(512)]), 'text/plain', '/a.txt', Date.now() / 1000 - 86400 - 500) - .setFileAsFavorite(currentUser, '/a.txt') - cy.login(currentUser) - cy.visit('/apps/files') - - cy.log('By name - ascending') - cy.get('th').contains('button', 'Name').click() - cy.contains('th', 'Name').should('have.attr', 'aria-sort', 'ascending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - } - }) - - cy.log('By name - descending') - cy.get('th').contains('button', 'Name').click() - cy.contains('th', 'Name').should('have.attr', 'aria-sort', 'descending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - } - }) - - cy.log('By size - ascending') - cy.get('th').contains('button', 'Size').click() - cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'ascending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - } - }) - - cy.log('By size - descending') - cy.get('th').contains('button', 'Size').click() - cy.contains('th', 'Size').should('have.attr', 'aria-sort', 'descending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - } - }) - - cy.log('By mtime - ascending') - cy.get('th').contains('button', 'Modified').click() - cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'ascending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - } - }) - - cy.log('By mtime - descending') - cy.get('th').contains('button', 'Modified').click() - cy.contains('th', 'Modified').should('have.attr', 'aria-sort', 'descending') - - cy.get('[data-cy-files-list-row]').each(($row, index) => { - switch (index) { - case 0: expect($row.attr('data-cy-files-list-row-name')).to.eq('a.txt') - break - case 1: expect($row.attr('data-cy-files-list-row-name')).to.eq('1.txt') - break - case 2: expect($row.attr('data-cy-files-list-row-name')).to.eq('z.txt') - break - case 3: expect($row.attr('data-cy-files-list-row-name')).to.eq('welcome.txt') - break - } - }) - }) -})