-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: Add end-to-end tests for public page header actions
Signed-off-by: Ferdinand Thiessen <[email protected]>
- Loading branch information
Showing
5 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
219 changes: 219 additions & 0 deletions
219
cypress/e2e/files_sharing/public-share-header-menu.cy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/*! | ||
* 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' | ||
|
||
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() | ||
|
||
beforeEach(() => { | ||
cy.logout() | ||
cy.visit(shareUrl) | ||
}) | ||
|
||
it('Can download all files', () => { | ||
// Check the button | ||
cy.get('header') | ||
.findByRole('button', { name: 'Download all files' }) | ||
.should('be.visible') | ||
cy.get('header') | ||
.findByRole('button', { name: 'Download all files' }) | ||
.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([ | ||
`${shareName}/`, | ||
`${shareName}/foo.txt`, | ||
`${shareName}/subfolder/`, | ||
`${shareName}/subfolder/bar.txt`, | ||
])) | ||
}) | ||
|
||
it('Can copy direct link', () => { | ||
// Check the button | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.should('be.visible') | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.click() | ||
// See the menu | ||
cy.findByRole('menu', { name: /More action/i }) | ||
.should('be.visible') | ||
// see correct link in item | ||
cy.findByRole('menuitem', { name: /Direct link/i }) | ||
.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 }) | ||
.click() | ||
cy.findByRole('menu', { name: /More actions/i }) | ||
.should('not.exist') | ||
}) | ||
|
||
it('Can create federated share', () => { | ||
// Check the button | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.should('be.visible') | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.click() | ||
// See the menu | ||
cy.findByRole('menu', { name: /More action/i }) | ||
.should('be.visible') | ||
// see correct item | ||
cy.findByRole('menuitem', { name: /Add to your/i }) | ||
.should('be.visible') | ||
.click() | ||
// see the dialog | ||
cy.findByRole('dialog', { name: /Add to your Nextcloud/i }) | ||
.should('be.visible') | ||
cy.findByRole('dialog', { name: /Add to your Nextcloud/i }).within(() => { | ||
cy.findByRole('textbox') | ||
.type('[email protected]') | ||
// create share | ||
cy.intercept('POST', '**/apps/federatedfilesharing/createFederatedShare') | ||
.as('createFederatedShare') | ||
cy.findByRole('button', { name: 'Create share' }) | ||
.click() | ||
cy.wait('@createFederatedShare') | ||
}) | ||
}) | ||
|
||
it('Has user feedback while creating federated share', () => { | ||
// Check the button | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.should('be.visible') | ||
.click() | ||
cy.findByRole('menuitem', { name: /Add to your/i }) | ||
.should('be.visible') | ||
.click() | ||
// see the dialog | ||
cy.findByRole('dialog', { name: /Add to your Nextcloud/i }).should('be.visible').within(() => { | ||
cy.findByRole('textbox') | ||
.type('[email protected]') | ||
// 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 }) | ||
}).as('createFederatedShare') | ||
// create the share | ||
cy.findByRole('button', { name: 'Create share' }) | ||
.click() | ||
// see that while the share is created the button is disabled | ||
cy.findByRole('button', { name: 'Create share' }) | ||
.should('be.disabled') | ||
.then(() => { | ||
// continue the request | ||
resolve(null) | ||
}) | ||
cy.wait('@createFederatedShare') | ||
// see that the button is no longer disabled | ||
cy.findByRole('button', { name: 'Create share' }) | ||
.should('not.be.disabled') | ||
}) | ||
}) | ||
|
||
it('Has input validation for federated share', () => { | ||
// Check the button | ||
cy.get('header') | ||
.findByRole('button', { name: /More actions/i }) | ||
.should('be.visible') | ||
.click() | ||
// see correct item | ||
cy.findByRole('menuitem', { name: /Add to your/i }) | ||
.should('be.visible') | ||
.click() | ||
// see the dialog | ||
cy.findByRole('dialog', { name: /Add to your Nextcloud/i }).should('be.visible').within(() => { | ||
// Check domain only | ||
cy.findByRole('textbox') | ||
.type('nextcloud.local') | ||
cy.findByRole('textbox') | ||
.should(haveValidity(/user/i)) | ||
// Check no valid domain | ||
cy.findByRole('textbox') | ||
.type('{selectAll}user@invalid') | ||
cy.findByRole('textbox') | ||
.should(haveValidity(/invalid.+url/i)) | ||
}) | ||
}) | ||
|
||
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.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() | ||
|
||
// 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`, | ||
])) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/*! | ||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
import { ZipReader } from '@zip.js/zip.js' | ||
/** | ||
* Assert that a file contains a list of expected files | ||
* @param expectedFiles List of expected filenames | ||
* @example | ||
* ```js | ||
* cy.readFile('file', null, { ... }) | ||
* .should(zipFileContains(['file.txt'])) | ||
* ``` | ||
*/ | ||
export function zipFileContains(expectedFiles: string[]) { | ||
return async (buffer: Buffer) => { | ||
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) | ||
console.info('Zip contains entries:', entries) | ||
expect(entries).to.deep.equal(expectedFiles) | ||
} | ||
} | ||
|
||
/** | ||
* Check validity of an input element | ||
* @param validity The expected validity message (empty string means it is valid) | ||
* @example | ||
* ```js | ||
* cy.findByRole('textbox') | ||
* .should(haveValidity(/must not be empty/i)) | ||
* ``` | ||
*/ | ||
export const haveValidity = (validity: string | RegExp) => { | ||
if (typeof validity === 'string') { | ||
return (el: JQuery<HTMLElement>) => expect((el.get(0) as HTMLInputElement).validationMessage).to.equal(validity) | ||
} | ||
return (el: JQuery<HTMLElement>) => expect((el.get(0) as HTMLInputElement).validationMessage).to.match(validity) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters