Skip to content
Draft
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
66 changes: 40 additions & 26 deletions cypress/e2e/files/FilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,25 @@
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' })
// Fully atomic selectors — a single cy.get() from root prevents "subject no longer attached"
// errors when the file list re-renders (e.g. due to Vue reactivity while opening the sidebar).
// NcActions renders its trigger button with aria-label="Actions" by default (see @nextcloud/vue NcActions ariaLabel prop default).
export const getActionButtonForFileId = (fileid: number) =>

Check failure on line 21 in cypress/e2e/files/FilesUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Top-level functions should be declared with function keyword
cy.get(`[data-cy-files-list-row-fileid="${fileid}"] [data-cy-files-list-row-actions] button[aria-label="Actions"]`)

Check failure on line 22 in cypress/e2e/files/FilesUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Expected no linebreak before this expression
export const getActionButtonForFile = (filename: string) =>

Check failure on line 23 in cypress/e2e/files/FilesUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Top-level functions should be declared with function keyword
cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"] [data-cy-files-list-row-actions] button[aria-label="Actions"]`)

Check failure on line 24 in cypress/e2e/files/FilesUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Expected no linebreak before this expression

/**
*
* @param fileid
* @param actionId
*/
export function getActionEntryForFileId(fileid: number, actionId: string) {
// Use a combined selector inside .then() to avoid chaining .find() on a potentially
// stale menu subject — a single cy.get() with descendant combinator is re-queried atomically.
return getActionButtonForFileId(fileid)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`)
.should('exist')
.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
.then((menuId) => cy.get(`#${menuId} [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}

/**
Expand All @@ -37,11 +42,11 @@
* @param actionId
*/
export function getActionEntryForFile(file: string, actionId: string) {
// Use a combined selector inside .then() to avoid chaining .find() on a potentially
// stale menu subject — a single cy.get() with descendant combinator is re-queried atomically.
return getActionButtonForFile(file)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`)
.should('exist')
.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
.then((menuId) => cy.get(`#${menuId} [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}

/**
Expand Down Expand Up @@ -69,13 +74,15 @@
*/
export function triggerActionForFileId(fileid: number, actionId: string) {
getActionButtonForFileId(fileid)
.should('be.visible')
.scrollIntoView()
getActionButtonForFileId(fileid)
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFileId(fileid, actionId)
.find('button')
// Single atomic cy.get() from root — avoids "subject no longer attached" when the
// file list re-renders (Vue reactivity) while the dropdown is open.
// force: true to handle brief animation/overlay states during menu transitions.
cy.get(`[role="menu"] [data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`)
.should('be.visible')
.click()
.click({ force: true })
}

/**
Expand All @@ -85,13 +92,15 @@
*/
export function triggerActionForFile(filename: string, actionId: string) {
getActionButtonForFile(filename)
.should('be.visible')
.scrollIntoView()
getActionButtonForFile(filename)
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFile(filename, actionId)
.find('button')
// Single atomic cy.get() from root — avoids "subject no longer attached" when the
// file list re-renders (Vue reactivity) while the dropdown is open.
// force: true to handle brief animation/overlay states during menu transitions.
cy.get(`[role="menu"] [data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`)
.should('be.visible')
.click()
.click({ force: true })
}

/**
Expand All @@ -100,8 +109,9 @@
* @param actionId
*/
export function triggerInlineActionForFileId(fileid: number, actionId: string) {
getActionsForFileId(fileid)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
// Atomic selector — inline NcActionButton renders the button as the root element,
// so button[data-cy-files-list-row-action] is correct for inline (non-menu) actions.
cy.get(`[data-cy-files-list-row-fileid="${fileid}"] button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}
Expand All @@ -111,8 +121,9 @@
* @param actionId
*/
export function triggerInlineActionForFile(filename: string, actionId: string) {
getActionsForFile(filename)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
// Atomic selector — inline NcActionButton renders the button as the root element,
// so button[data-cy-files-list-row-action] is correct for inline (non-menu) actions.
cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"] button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}
Expand Down Expand Up @@ -140,9 +151,8 @@
* @param options
*/
export function selectRowForFile(filename: string, options: Partial<Cypress.ClickOptions> = {}) {
getRowForFile(filename)
.find('[data-cy-files-list-row-checkbox]')
.findByRole('checkbox')
// Atomic selector — avoids chained .find() on a potentially stale row subject.
cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"] [data-cy-files-list-row-checkbox] input[type="checkbox"]`)

Check failure on line 155 in cypress/e2e/files/FilesUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line
// don't use click to avoid triggering side effects events
.trigger('change', { ...options, force: true })
.should('be.checked')
Expand Down Expand Up @@ -260,8 +270,9 @@
// 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')
// Atomic selector — avoids chained .find() on a row subject that may have re-rendered
// when entering rename mode (the link element is replaced by an input).
cy.get(`[data-cy-files-list-row-name="${CSS.escape(fileName)}"] [data-cy-files-list-row-name] input`)
.type(`{selectAll}${newFileName}{enter}`)

cy.wait('@moveFile')
Expand All @@ -278,7 +289,10 @@
continue
}

getRowForFile(directory).should('be.visible').find('[data-cy-files-list-row-name-link]').click()
// Atomic selector — avoids chained .find() on a potentially stale row subject.
cy.get(`[data-cy-files-list-row-name="${CSS.escape(directory)}"] [data-cy-files-list-row-name-link]`)
.should('be.visible')
.click()
}
}

Expand Down
Loading