Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 58 additions & 37 deletions cypress/e2e/files/FilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,74 @@
*/

import type { User } from '@nextcloud/cypress'
import { ACTION_COPY_MOVE } from "../../../apps/files/src/actions/moveOrCopyAction"
import { ACTION_COPY_MOVE } from '../../../apps/files/src/actions/moveOrCopyAction.ts'

export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)

export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')
export const getActionsForFile = (filename: string) => getRowForFile(filename).find('[data-cy-files-list-row-actions]')
// Atomic query so the lookup is retried as a whole when rows re-render
// (chained .find() can fail with "subject no longer attached" mid-render).
export const getActionsForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"] [data-cy-files-list-row-actions]`)
export const getActionsForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"] [data-cy-files-list-row-actions]`)

export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).findByRole('button', { name: 'Actions' })
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).findByRole('button', { name: 'Actions' })

const searchForActionInRow = (row: JQuery<HTMLElement>, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
const action = row.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
if (action.length > 0) {
cy.log('Found action in row')
return cy.wrap(action)
}

// Else look in the action menu
const menuButtonId = row.find('button[aria-controls]').attr('aria-controls')
if (menuButtonId === undefined) {
return cy.wrap(Cypress.$())
}
export const getActionEntryForFileId = (fileid: number, actionId: string) => {
return getActionButtonForFileId(fileid)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`).find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}

// eslint-disable-next-line no-unused-expressions
expect(menuButtonId).not.to.be.undefined
return cy.get(`#${menuButtonId} [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
export const getActionEntryForFile = (file: string, actionId: string) => {
return getActionButtonForFile(file)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`).find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}

export const getActionEntryForFileId = (fileid: number, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
// If we cannot find the action in the row, it might be in the action menu
return getRowForFileId(fileid).should('be.visible')
.then(row => searchForActionInRow(row, actionId))
export const getInlineActionEntryForFileId = (fileid: number, actionId: string) => {
return cy.get(`[data-cy-files-list-row-fileid="${fileid}"] [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}
export const getActionEntryForFile = (filename: string, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
// If we cannot find the action in the row, it might be in the action menu
return getRowForFile(filename).should('be.visible')
.then(row => searchForActionInRow(row, actionId))

export const getInlineActionEntryForFile = (file: string, actionId: string) => {
return cy.get(`[data-cy-files-list-row-name="${CSS.escape(file)}"] [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}

export const triggerActionForFileId = (fileid: number, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFileId(fileid).click({ force: true })
getActionButtonForFileId(fileid)
.as('actionButton')
.scrollIntoView()
cy.get('@actionButton')
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFileId(fileid, actionId)
.find('button').last()
.should('exist').click({ force: true })
.find('button')
.should('be.visible')
.click()
}

export const triggerActionForFile = (filename: string, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFile(filename).click({ force: true })
getActionButtonForFile(filename)
.as('actionButton')
.scrollIntoView()
cy.get('@actionButton')
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFile(filename, actionId)
.find('button').last()
.should('exist').click({ force: true })
.find('button')
.should('be.visible')
.click()
}

export const triggerInlineActionForFileId = (fileid: number, actionId: string) => {
getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
getActionsForFileId(fileid)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}
export const triggerInlineActionForFile = (filename: string, actionId: string) => {
getActionsForFile(filename).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
getActionsForFile(filename)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}

export const selectAllFiles = () => {
Expand Down Expand Up @@ -166,12 +174,17 @@ export const copyFile = (fileName: string, dirPath: string) => {

export const renameFile = (fileName: string, newFileName: string) => {
getRowForFile(fileName)
.should('exist')
.scrollIntoView()

triggerActionForFile(fileName, 'rename')

// intercept the move so we can wait for it
cy.intercept('MOVE', /\/(remote|public)\.php\/dav\/files\//).as('moveFile')

getRowForFile(fileName).find('[data-cy-files-list-row-name] input').type(`{selectAll}${newFileName}{enter}`)
getRowForFile(fileName)
.find('[data-cy-files-list-row-name] input')
.type(`{selectAll}${newFileName}{enter}`)

cy.wait('@moveFile')
}
Expand All @@ -182,8 +195,10 @@ export const navigateToFolder = (dirPath: string) => {
if (directory === '') {
continue
}

getRowForFile(directory).should('be.visible').find('[data-cy-files-list-row-name-link]').click()
}

}

export const closeSidebar = () => {
Expand Down Expand Up @@ -281,6 +296,12 @@ export function enableGridMode() {
export function calculateViewportHeight(rows: number): Cypress.Chainable<number> {
cy.visit('/apps/files')

cy.get('[data-cy-files-list]')
.should('be.visible')

cy.get('[data-cy-files-list-tbody] tr', { timeout: 5000 })
.and('be.visible')

return cy.get('[data-cy-files-list]')
.should('be.visible')
.then((filesList) => {
Expand Down
14 changes: 7 additions & 7 deletions cypress/e2e/files/files-actions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSe
import { ACTION_COPY_MOVE } from '../../../apps/files/src/actions/moveOrCopyAction'
import { ACTION_DELETE } from '../../../apps/files/src/actions/deleteAction'
import { ACTION_DETAILS } from '../../../apps/files/src/actions/sidebarAction'
import { ACTION_SHARING_STATUS } from '../../../apps/files_sharing/src/files_actions/sharingStatusAction'

declare global {
interface Window {
Expand All @@ -24,7 +23,6 @@ const expectedDefaultActionsIDs = [
ACTION_COPY_MOVE,
ACTION_DELETE,
ACTION_DETAILS,
ACTION_SHARING_STATUS,
]
const expectedDefaultSelectionActionsIDs = [
ACTION_COPY_MOVE,
Expand Down Expand Up @@ -94,7 +92,9 @@ describe('Files: Actions', { testIsolation: true }, () => {
})

// Open the menu
getActionButtonForFileId(fileId).click({ force: true })
getActionButtonForFileId(fileId)
.scrollIntoView()
.click({ force: true })

// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
Expand All @@ -104,8 +104,8 @@ describe('Files: Actions', { testIsolation: true }, () => {

// Click on the parent action
getActionEntryForFileId(fileId, 'nested-action')
.find('button').last()
.should('exist').click({ force: true })
.should('be.visible')
.click()

// Check we have the children and the back button but not the parent
getActionEntryForFileId(fileId, 'nested-action').should('not.exist')
Expand All @@ -115,8 +115,8 @@ describe('Files: Actions', { testIsolation: true }, () => {

// Click on the back button
getActionEntryForFileId(fileId, 'menu-back')
.find('button').last()
.should('exist').click({ force: true })
.should('be.visible')
.click()

// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
Expand Down
9 changes: 6 additions & 3 deletions cypress/e2e/files/files-renaming.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,16 @@ describe('files: Rename nodes', { testIsolation: true }, () => {

cy.visit('/apps/files')

getRowForFile('file.txt').should('be.visible')
getRowForFile('file.txt')
.should('be.visible')
// Z so it is shown last
renameFile('file.txt', 'zzz.txt')
// not visible any longer
getRowForFile('zzz.txt').should('not.exist')
getRowForFile('zzz.txt')
.should('not.exist')
// scroll file list to bottom
cy.get('[data-cy-files-list]').scrollTo('bottom')
cy.get('[data-cy-files-list]')
.scrollTo('bottom')
cy.screenshot()
// The file is no longer in rename state
getRowForFile('zzz.txt')
Expand Down
11 changes: 6 additions & 5 deletions cypress/e2e/files_external/files-user-credentials.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { User } from '@nextcloud/cypress'
import { AuthBackend, createStorageWithConfig, StorageBackend } from './StorageUtils'
import { getActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils'
import { getInlineActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils'

import { ACTION_CREDENTIALS_EXTERNAL_STORAGE } from '../../../apps/files_external/src/actions/enterCredentialsAction'
import { handlePasswordConfirmation } from '../settings/usersUtils'
Expand Down Expand Up @@ -76,7 +76,8 @@ describe('Files user credentials', { testIsolation: true }, () => {
// Auth dialog should be closed and the set credentials button should be gone
authDialog.should('not.exist', { timeout: 2000 })

getActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE)
.should('not.exist')

// Finally, the storage should be accessible
cy.visit('/apps/files')
Expand Down Expand Up @@ -125,7 +126,7 @@ describe('Files user credentials', { testIsolation: true }, () => {

// Auth dialog should be closed and the set credentials button should be gone
cy.get('@authDialog').should('not.exist', { timeout: 2000 })
getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')

// Finally, the storage should be accessible
cy.visit('/apps/files')
Expand All @@ -144,8 +145,8 @@ describe('Files user credentials', { testIsolation: true }, () => {
getRowForFile('storage2').should('be.visible')

// Since we already have set the credentials, the action should not be present
getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getActionEntryForFile('storage2', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage2', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')

// Finally, the storage should be accessible
cy.visit('/apps/files')
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/files_sharing/FilesSharingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
export function openSharingPanel(fileName: string) {
triggerActionForFile(fileName, 'details')

cy.get('#app-sidebar-vue')
.get('[aria-controls="tab-sharing"]')
cy.get('[data-cy-sidebar]')
.find('[aria-controls="tab-sharing"]')
.click()
}

Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/files_sharing/note-to-recipient.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('files_sharing: Note to recipient', { testIsolation: true }, () => {
createShare('folder', sharee.userId, { read: true, download: true, note: 'Hello, this is the note.' })

// reload just to be sure
cy.reload()
cy.visit('/apps/files')

// open the sharing tab
openSharingPanel('folder')
Expand Down
27 changes: 11 additions & 16 deletions cypress/e2e/files_sharing/share-status-action.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import type { User } from '@nextcloud/cypress'
import { createShare } from './FilesSharingUtils.ts'
import { closeSidebar, enableGridMode, getActionButtonForFile, getRowForFile } from '../files/FilesUtils.ts'
import { closeSidebar, enableGridMode, getActionButtonForFile, getInlineActionEntryForFile, getRowForFile, getActionsForFile } from '../files/FilesUtils.ts'

describe('files_sharing: Sharing status action', { testIsolation: true }, () => {
/**
Expand All @@ -22,9 +22,8 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>

cy.visit('/apps/files')

getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
getRowForFile('folder').should('be.visible')
getActionsForFile('folder')
.findByRole('button', { name: 'Shared' })
.should('not.exist')
})
Expand All @@ -37,12 +36,11 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>
cy.visit('/apps/files')
})

getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
getRowForFile('folder').should('be.visible')
getActionsForFile('folder')
.findByRole('button', { name: /Show sharing options/ })
.should('be.visible')
.click()
.click({ force: true })

// check the click opened the sidebar
cy.get('[data-cy-sidebar]')
Expand Down Expand Up @@ -78,10 +76,9 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>
cy.login(user)
cy.visit('/apps/files')

getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
.findByRole('button', { name: /^Shared with/i })
getInlineActionEntryForFile('folder', 'sharing-status')
.should('have.attr', 'aria-label', `Shared with ${sharee.userId}`)
.should('have.attr', 'title', `Shared with ${sharee.userId}`)
.should('be.visible')
})

Expand All @@ -103,10 +100,8 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>
cy.login(sharee)
cy.visit('/apps/files')

getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
.findByRole('button', { name: `Shared by ${user.userId}` })
getInlineActionEntryForFile('folder', 'sharing-status')
.should('have.attr', 'aria-label', `Shared by ${user.userId}`)
.should('be.visible')
})

Expand Down
Loading