Skip to content

Commit b5a3fc5

Browse files
committed
test(files): Make scrolling tests independent from magic values
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 5251b25 commit b5a3fc5

File tree

4 files changed

+214
-79
lines changed

4 files changed

+214
-79
lines changed

cypress/e2e/core-utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,43 @@ export enum UnifiedSearchFilter {
4848
export function getUnifiedSearchFilter(filter: UnifiedSearchFilter) {
4949
return getUnifiedSearchModal().find(`[data-cy-unified-search-filters] [data-cy-unified-search-filter="${CSS.escape(filter)}"]`)
5050
}
51+
52+
/**
53+
* Assertion that an element is fully within the current viewport.
54+
* @param $el The element
55+
* @param expected If the element is expected to be fully in viewport or not fully
56+
* @example
57+
* ```js
58+
* cy.get('#my-element')
59+
* .should(beFullyInViewport)
60+
* ```
61+
*/
62+
export function beFullyInViewport($el: JQuery<HTMLElement>, expected = true) {
63+
const { top, left, bottom, right } = $el.get(0)!.getBoundingClientRect()
64+
const innerHeight = Cypress.$('body').innerHeight()!
65+
const innerWidth = Cypress.$('body').innerWidth()!
66+
const fullyVisible = top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth
67+
68+
console.debug(`fullyVisible: ${fullyVisible}, top: ${top >= 0}, left: ${left >= 0}, bottom: ${bottom <= innerHeight}, right: ${right <= innerWidth}`)
69+
70+
if (expected) {
71+
// eslint-disable-next-line no-unused-expressions
72+
expect(fullyVisible, 'Fully within viewport').to.be.true
73+
} else {
74+
// eslint-disable-next-line no-unused-expressions
75+
expect(fullyVisible, 'Not fully within viewport').to.be.false
76+
}
77+
}
78+
79+
/**
80+
* Opposite of `beFullyInViewport` - resolves when element is not or only partially in viewport.
81+
* @param $el The element
82+
* @example
83+
* ```js
84+
* cy.get('#my-element')
85+
* .should(notBeFullyInViewport)
86+
* ```
87+
*/
88+
export function notBeFullyInViewport($el: JQuery<HTMLElement>) {
89+
return beFullyInViewport($el, false)
90+
}

cypress/e2e/files/FilesUtils.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import type { User } from '@nextcloud/cypress'
7+
68
export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
79
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
810

@@ -176,3 +178,73 @@ export const haveValidity = (validity: string | RegExp) => {
176178
}
177179
return (el: JQuery<HTMLElement>) => expect((el.get(0) as HTMLInputElement).validationMessage).to.match(validity)
178180
}
181+
182+
export const deleteFileWithRequest = (user: User, path: string) => {
183+
// Ensure path starts with a slash and has no double slashes
184+
path = `/${path}`.replace(/\/+/g, '/')
185+
186+
cy.request('/csrftoken').then(({ body }) => {
187+
const requestToken = body.token
188+
cy.request({
189+
method: 'DELETE',
190+
url: `${Cypress.env('baseUrl')}/remote.php/dav/files/${user.userId}` + path,
191+
headers: {
192+
requestToken,
193+
},
194+
retryOnStatusCodeFailure: true,
195+
})
196+
})
197+
}
198+
199+
export const triggerFileListAction = (actionId: string) => {
200+
cy.get(`button[data-cy-files-list-action="${CSS.escape(actionId)}"]`).last()
201+
.should('exist').click({ force: true })
202+
}
203+
204+
export const reloadCurrentFolder = () => {
205+
cy.intercept('PROPFIND', /\/remote.php\/dav\//).as('propfind')
206+
cy.get('[data-cy-files-content-breadcrumbs]').findByRole('button', { description: 'Reload current directory' }).click()
207+
cy.wait('@propfind')
208+
}
209+
210+
/**
211+
* Enable the grid mode for the files list.
212+
* Will fail if already enabled!
213+
*/
214+
export function enableGridMode() {
215+
cy.intercept('**/apps/files/api/v1/config/grid_view').as('setGridMode')
216+
cy.findByRole('button', { name: 'Switch to grid view' })
217+
.should('be.visible')
218+
.click()
219+
cy.wait('@setGridMode')
220+
}
221+
222+
/**
223+
* Calculate the needed viewport height to limit the visible rows of the file list.
224+
* Requires a logged in user.
225+
*
226+
* @param rows The number of rows that should be displayed at the same time
227+
*/
228+
export function calculateViewportHeight(rows: number): Cypress.Chainable<number> {
229+
cy.visit('/apps/files')
230+
231+
return cy.get('[data-cy-files-list]')
232+
.should('be.visible')
233+
.then((filesList) => {
234+
const windowHeight = Cypress.$('body').outerHeight()!
235+
// Size of other page elements
236+
const outerHeight = Math.ceil(windowHeight - filesList.outerHeight()!)
237+
// Size of before and filters
238+
const beforeHeight = Math.ceil(Cypress.$('.files-list__before').outerHeight()!)
239+
const filterHeight = Math.ceil(Cypress.$('.files-list__filters').outerHeight()!)
240+
// Size of the table header
241+
const tableHeaderHeight = Math.ceil(Cypress.$('[data-cy-files-list-thead]').outerHeight()!)
242+
// table row height
243+
const rowHeight = Math.ceil(Cypress.$('[data-cy-files-list-tbody] tr').outerHeight()!)
244+
245+
// sum it up
246+
const viewportHeight = outerHeight + beforeHeight + filterHeight + tableHeaderHeight + rows * rowHeight
247+
cy.log(`Calculated viewport height: ${viewportHeight} (${outerHeight} + ${beforeHeight} + ${filterHeight} + ${tableHeaderHeight} + ${rows} * ${rowHeight})`)
248+
return cy.wrap(viewportHeight)
249+
})
250+
}

cypress/e2e/files/files-renaming.cy.ts

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
*/
55

66
import type { User } from '@nextcloud/cypress'
7-
import { getRowForFile, haveValidity, renameFile, triggerActionForFile } from './FilesUtils'
7+
import { calculateViewportHeight, getRowForFile, haveValidity, renameFile, triggerActionForFile } from './FilesUtils'
88

99
describe('files: Rename nodes', { testIsolation: true }, () => {
1010
let user: User
1111

1212
beforeEach(() => cy.createRandomUser().then(($user) => {
1313
user = $user
1414

15+
// remove welcome file
16+
cy.rm(user, '/welcome.txt')
17+
// create a file called "file.txt"
1518
cy.uploadContent(user, new Blob([]), 'text/plain', '/file.txt')
19+
20+
// login and visit files app
1621
cy.login(user)
1722
cy.visit('/apps/files')
1823
}))
@@ -113,34 +118,6 @@ describe('files: Rename nodes', { testIsolation: true }, () => {
113118
.should('not.exist')
114119
})
115120

116-
/**
117-
* This is a regression test of: https://github.com/nextcloud/server/issues/47438
118-
* The issue was that the renaming state was not reset when the new name moved the file out of the view of the current files list
119-
* due to virtual scrolling the renaming state was not changed then by the UI events (as the component was taken out of DOM before any event handling).
120-
*/
121-
it('correctly resets renaming state', () => {
122-
for (let i = 1; i <= 20; i++) {
123-
cy.uploadContent(user, new Blob([]), 'text/plain', `/file${i}.txt`)
124-
}
125-
cy.viewport(1200, 500) // 500px is smaller then 20 * 50 which is the place that the files take up
126-
cy.login(user)
127-
cy.visit('/apps/files')
128-
129-
getRowForFile('file.txt').should('be.visible')
130-
// Z so it is shown last
131-
renameFile('file.txt', 'zzz.txt')
132-
// not visible any longer
133-
getRowForFile('zzz.txt').should('not.be.visible')
134-
// scroll file list to bottom
135-
cy.get('[data-cy-files-list]').scrollTo('bottom')
136-
cy.screenshot()
137-
// The file is no longer in rename state
138-
getRowForFile('zzz.txt')
139-
.should('be.visible')
140-
.findByRole('textbox', { name: 'Filename' })
141-
.should('not.exist')
142-
})
143-
144121
it('cancel renaming on esc press', () => {
145122
// All are visible by default
146123
getRowForFile('file.txt').should('be.visible')
@@ -179,4 +156,38 @@ describe('files: Rename nodes', { testIsolation: true }, () => {
179156
.find('input[type="text"]')
180157
.should('not.exist')
181158
})
159+
160+
/**
161+
* This is a regression test of: https://github.com/nextcloud/server/issues/47438
162+
* The issue was that the renaming state was not reset when the new name moved the file out of the view of the current files list
163+
* due to virtual scrolling the renaming state was not changed then by the UI events (as the component was taken out of DOM before any event handling).
164+
*/
165+
it('correctly resets renaming state', () => {
166+
// Create 19 additional files
167+
for (let i = 1; i <= 19; i++) {
168+
cy.uploadContent(user, new Blob([]), 'text/plain', `/file${i}.txt`)
169+
}
170+
171+
// Calculate and setup a viewport where only the first 4 files are visible, causing 6 rows to be rendered
172+
cy.viewport(768, 500)
173+
cy.login(user)
174+
calculateViewportHeight(4)
175+
.then((height) => cy.viewport(768, height))
176+
177+
cy.visit('/apps/files')
178+
179+
getRowForFile('file.txt').should('be.visible')
180+
// Z so it is shown last
181+
renameFile('file.txt', 'zzz.txt')
182+
// not visible any longer
183+
getRowForFile('zzz.txt').should('not.exist')
184+
// scroll file list to bottom
185+
cy.get('[data-cy-files-list]').scrollTo('bottom')
186+
cy.screenshot()
187+
// The file is no longer in rename state
188+
getRowForFile('zzz.txt')
189+
.should('be.visible')
190+
.findByRole('textbox', { name: 'Filename' })
191+
.should('not.exist')
192+
})
182193
})

0 commit comments

Comments
 (0)