Skip to content

Commit ce472e6

Browse files
committed
test(files): Add e2e tests for live photo sync
Signed-off-by: Louis Chemineau <[email protected]>
1 parent 143efc6 commit ce472e6

File tree

11 files changed

+301
-41
lines changed

11 files changed

+301
-41
lines changed

apps/files/src/views/Settings.vue

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,28 @@
2626
@update:open="onClose">
2727
<!-- Settings API-->
2828
<NcAppSettingsSection id="settings" :name="t('files', 'Files settings')">
29-
<NcCheckboxRadioSwitch :checked="userConfig.sort_favorites_first"
29+
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_favorites_first"
30+
:checked="userConfig.sort_favorites_first"
3031
@update:checked="setConfig('sort_favorites_first', $event)">
3132
{{ t('files', 'Sort favorites first') }}
3233
</NcCheckboxRadioSwitch>
33-
<NcCheckboxRadioSwitch :checked="userConfig.sort_folders_first"
34+
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_folders_first"
35+
:checked="userConfig.sort_folders_first"
3436
@update:checked="setConfig('sort_folders_first', $event)">
3537
{{ t('files', 'Sort folders before files') }}
3638
</NcCheckboxRadioSwitch>
37-
<NcCheckboxRadioSwitch :checked="userConfig.show_hidden"
39+
<NcCheckboxRadioSwitch data-cy-files-settings-setting="show_hidden"
40+
:checked="userConfig.show_hidden"
3841
@update:checked="setConfig('show_hidden', $event)">
3942
{{ t('files', 'Show hidden files') }}
4043
</NcCheckboxRadioSwitch>
41-
<NcCheckboxRadioSwitch :checked="userConfig.crop_image_previews"
44+
<NcCheckboxRadioSwitch data-cy-files-settings-setting="crop_image_previews"
45+
:checked="userConfig.crop_image_previews"
4246
@update:checked="setConfig('crop_image_previews', $event)">
4347
{{ t('files', 'Crop image previews') }}
4448
</NcCheckboxRadioSwitch>
4549
<NcCheckboxRadioSwitch v-if="enableGridView"
50+
data-cy-files-settings-setting="grid_view"
4651
:checked="userConfig.grid_view"
4752
@update:checked="setConfig('grid_view', $event)">
4853
{{ t('files', 'Enable the grid view') }}

apps/files/src/views/Sidebar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<template>
2424
<NcAppSidebar v-if="file"
2525
ref="sidebar"
26+
cy-data-sidebar
2627
v-bind="appSidebar"
2728
:force-menu="true"
2829
@close="close"

cypress/e2e/files/FilesUtils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,31 @@
2020
*
2121
*/
2222

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

26+
export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')
2527
export const getActionsForFile = (filename: string) => getRowForFile(filename).find('[data-cy-files-list-row-actions]')
2628

29+
export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).find('button[aria-label="Actions"]')
2730
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).find('button[aria-label="Actions"]')
2831

32+
export const triggerActionForFileId = (fileid: number, actionId: string) => {
33+
getActionButtonForFileId(fileid).click()
34+
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
35+
}
2936
export const triggerActionForFile = (filename: string, actionId: string) => {
3037
getActionButtonForFile(filename).click()
3138
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
3239
}
3340

41+
export const triggerInlineActionForFileId = (fileid: number, actionId: string) => {
42+
getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
43+
}
44+
export const triggerInlineActionForFile = (filename: string, actionId: string) => {
45+
getActionsForFile(filename).get(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
46+
}
47+
3448
export const moveFile = (fileName: string, dirName: string) => {
3549
getRowForFile(fileName).should('be.visible')
3650
triggerActionForFile(fileName, 'move-copy')
@@ -85,6 +99,23 @@ export const copyFile = (fileName: string, dirName: string) => {
8599
})
86100
}
87101

102+
export const renameFile = (fileName: string, newFileName: string) => {
103+
getRowForFile(fileName)
104+
triggerActionForFile(fileName, 'rename')
105+
106+
// intercept the move so we can wait for it
107+
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile')
108+
109+
getRowForFile(fileName).find('[data-cy-files-list-row-name] input').clear()
110+
getRowForFile(fileName).find('[data-cy-files-list-row-name] input').type(`${newFileName}{enter}`)
111+
112+
cy.wait('@moveFile')
113+
}
114+
88115
export const navigateToFolder = (folderName: string) => {
89116
getRowForFile(folderName).should('be.visible').find('[data-cy-files-list-row-name-link]').click()
90117
}
118+
119+
export const closeSidebar = () => {
120+
cy.get('[cy-data-sidebar] .app-sidebar__close').click()
121+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/**
2+
* @copyright Copyright (c) 2024 Louis Chmn <[email protected]>
3+
*
4+
* @author Louis Chmn <[email protected]>
5+
*
6+
* @license AGPL-3.0-or-later
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
import type { User } from '@nextcloud/cypress'
24+
import { closeSidebar, copyFile, getRowForFile, getRowForFileId, renameFile, triggerActionForFile, triggerInlineActionForFileId } from './FilesUtils'
25+
26+
/**
27+
*
28+
* @param label
29+
*/
30+
function refreshView(label: string) {
31+
cy.intercept('PROPFIND', /\/remote.php\/dav\//).as('propfind')
32+
cy.get('[data-cy-files-content-breadcrumbs]').contains(label).click()
33+
cy.wait('@propfind')
34+
}
35+
36+
/**
37+
*
38+
* @param user
39+
* @param fileName
40+
* @param domain
41+
* @param requesttoken
42+
* @param metadata
43+
*/
44+
function setMetadata(user: User, fileName: string, domain: string, requesttoken: string, metadata: object) {
45+
cy.request({
46+
method: 'PROPPATCH',
47+
url: `http://${domain}/remote.php/dav/files/${user.userId}/${fileName}`,
48+
auth: { user: user.userId, pass: user.password },
49+
headers: {
50+
requesttoken,
51+
},
52+
body: `<?xml version="1.0"?>
53+
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
54+
<d:set>
55+
<d:prop>
56+
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
57+
</d:prop>
58+
</d:set>
59+
</d:propertyupdate>`,
60+
})
61+
}
62+
63+
describe('Files: Live photos', { testIsolation: true }, () => {
64+
let currentUser: User
65+
let randomFileName: string
66+
let jpgFileId: number
67+
let movFileId: number
68+
let hostname: string
69+
let requesttoken: string
70+
71+
before(() => {
72+
cy.createRandomUser().then((user) => {
73+
currentUser = user
74+
cy.login(currentUser)
75+
cy.visit('/apps/files')
76+
})
77+
78+
cy.url().then(url => { hostname = new URL(url).hostname })
79+
})
80+
81+
beforeEach(() => {
82+
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10)
83+
84+
cy.uploadContent(currentUser, new Blob(['jpg file'], { type: 'image/jpg' }), 'image/jpg', `/${randomFileName}.jpg`)
85+
.then(response => { jpgFileId = parseInt(response.headers['oc-fileid']) })
86+
cy.uploadContent(currentUser, new Blob(['mov file'], { type: 'video/mov' }), 'video/mov', `/${randomFileName}.mov`)
87+
.then(response => { movFileId = parseInt(response.headers['oc-fileid']) })
88+
89+
cy.login(currentUser)
90+
cy.visit('/apps/files')
91+
92+
cy.get('head').invoke('attr', 'data-requesttoken').then(_requesttoken => { requesttoken = _requesttoken as string })
93+
94+
cy.then(() => {
95+
setMetadata(currentUser, `${randomFileName}.jpg`, hostname, requesttoken, { 'nc:metadata-files-live-photo': movFileId })
96+
setMetadata(currentUser, `${randomFileName}.mov`, hostname, requesttoken, { 'nc:metadata-files-live-photo': jpgFileId })
97+
})
98+
99+
cy.then(() => {
100+
cy.visit(`/apps/files/files/${jpgFileId}`) // Refresh and scroll to the .jpg file.
101+
closeSidebar()
102+
})
103+
})
104+
105+
it('Only renders the .jpg file', () => {
106+
getRowForFileId(jpgFileId).should('have.length', 1)
107+
getRowForFileId(movFileId).should('have.length', 0)
108+
})
109+
110+
context("'Show hidden files' is enabled", () => {
111+
before(() => {
112+
cy.login(currentUser)
113+
cy.visit('/apps/files')
114+
cy.get('[data-cy-files-navigation-settings-button]').click()
115+
// Force:true because the checkbox is hidden by the pretty UI.
116+
cy.get('[data-cy-files-settings-setting="show_hidden"] input').check({ force: true })
117+
})
118+
119+
it("Shows both files when 'Show hidden files' is enabled", () => {
120+
getRowForFileId(jpgFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.jpg`)
121+
getRowForFileId(movFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.mov`)
122+
})
123+
124+
it('Copies both files when copying the .jpg', () => {
125+
copyFile(`${randomFileName}.jpg`, '.')
126+
refreshView('All files')
127+
128+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
129+
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
130+
getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
131+
getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
132+
})
133+
134+
it('Copies both files when copying the .mov', () => {
135+
copyFile(`${randomFileName}.mov`, '.')
136+
refreshView('All files')
137+
138+
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
139+
getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
140+
getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
141+
})
142+
143+
it('Moves files when moving the .jpg', () => {
144+
renameFile(`${randomFileName}.jpg`, `${randomFileName}_moved.jpg`)
145+
refreshView('All files')
146+
147+
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
148+
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
149+
})
150+
151+
it('Moves files when moving the .mov', () => {
152+
renameFile(`${randomFileName}.mov`, `${randomFileName}_moved.mov`)
153+
refreshView('All files')
154+
155+
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
156+
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
157+
})
158+
159+
it('Deletes files when deleting the .jpg', () => {
160+
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
161+
refreshView('All files')
162+
163+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
164+
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
165+
166+
cy.visit('/apps/files/trashbin')
167+
168+
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.jpg\\.d[0-9]+$`))
169+
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.mov\\.d[0-9]+$`))
170+
})
171+
172+
it('Block deletion when deleting the .mov', () => {
173+
triggerActionForFile(`${randomFileName}.mov`, 'delete')
174+
refreshView('All files')
175+
176+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
177+
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
178+
179+
cy.visit('/apps/files/trashbin')
180+
181+
getRowForFileId(jpgFileId).should('have.length', 0)
182+
getRowForFileId(movFileId).should('have.length', 0)
183+
})
184+
185+
it('Restores files when restoring the .jpg', () => {
186+
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
187+
cy.visit('/apps/files/trashbin')
188+
triggerInlineActionForFileId(jpgFileId, 'restore')
189+
refreshView('Deleted files')
190+
191+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
192+
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
193+
194+
cy.visit('/apps/files')
195+
196+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
197+
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
198+
})
199+
200+
it('Blocks restoration when restoring the .mov', () => {
201+
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
202+
cy.visit('/apps/files/trashbin')
203+
triggerInlineActionForFileId(movFileId, 'restore')
204+
refreshView('Deleted files')
205+
206+
getRowForFileId(jpgFileId).should('have.length', 1)
207+
getRowForFileId(movFileId).should('have.length', 1)
208+
209+
cy.visit('/apps/files')
210+
211+
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
212+
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
213+
})
214+
})
215+
})

cypress/e2e/files_sharing/filesSharingUtils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,35 +58,43 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
5858
if (shareSettings.download !== undefined) {
5959
cy.get('[data-cy-files-sharing-share-permissions-checkbox="download"]').find('input').as('downloadCheckbox')
6060
if (shareSettings.download) {
61+
// Force:true because the checkbox is hidden by the pretty UI.
6162
cy.get('@downloadCheckbox').check({ force: true, scrollBehavior: 'nearest' })
6263
} else {
64+
// Force:true because the checkbox is hidden by the pretty UI.
6365
cy.get('@downloadCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
6466
}
6567
}
6668

6769
if (shareSettings.read !== undefined) {
6870
cy.get('[data-cy-files-sharing-share-permissions-checkbox="read"]').find('input').as('readCheckbox')
6971
if (shareSettings.read) {
72+
// Force:true because the checkbox is hidden by the pretty UI.
7073
cy.get('@readCheckbox').check({ force: true, scrollBehavior: 'nearest' })
7174
} else {
75+
// Force:true because the checkbox is hidden by the pretty UI.
7276
cy.get('@readCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
7377
}
7478
}
7579

7680
if (shareSettings.update !== undefined) {
7781
cy.get('[data-cy-files-sharing-share-permissions-checkbox="update"]').find('input').as('updateCheckbox')
7882
if (shareSettings.update) {
83+
// Force:true because the checkbox is hidden by the pretty UI.
7984
cy.get('@updateCheckbox').check({ force: true, scrollBehavior: 'nearest' })
8085
} else {
86+
// Force:true because the checkbox is hidden by the pretty UI.
8187
cy.get('@updateCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
8288
}
8389
}
8490

8591
if (shareSettings.delete !== undefined) {
8692
cy.get('[data-cy-files-sharing-share-permissions-checkbox="delete"]').find('input').as('deleteCheckbox')
8793
if (shareSettings.delete) {
94+
// Force:true because the checkbox is hidden by the pretty UI.
8895
cy.get('@deleteCheckbox').check({ force: true, scrollBehavior: 'nearest' })
8996
} else {
97+
// Force:true because the checkbox is hidden by the pretty UI.
9098
cy.get('@deleteCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
9199
}
92100
}

cypress/e2e/files_versions/version_restoration.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('Versions restoration', () => {
113113
auth: { user: recipient.userId, pass: recipient.password },
114114
headers: {
115115
cookie: '',
116-
Destination: 'https://nextcloud_server1.test/remote.php/dav/versions/admin/restore/target',
116+
Destination: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/restore/target`,
117117
},
118118
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
119119
failOnStatusCode: false,

0 commit comments

Comments
 (0)