Skip to content

Commit a42b69e

Browse files
skjnldsvnextcloud-command
authored andcommitted
fix(files): always ask for confirmation if trashbin app is disabled
Signed-off-by: skjnldsv <[email protected]> Signed-off-by: nextcloud-command <[email protected]> Signed-off-by: nextcloud-command <[email protected]>
1 parent e61807c commit a42b69e

13 files changed

+377
-133
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
import type { Capabilities } from '../../apps/files/src/types'
6+
7+
export const getCapabilities = (): Capabilities => {
8+
return {
9+
files: {
10+
bigfilechunking: true,
11+
blacklisted_files: [],
12+
forbidden_filename_basenames: [],
13+
forbidden_filename_characters: [],
14+
forbidden_filename_extensions: [],
15+
forbidden_filenames: [],
16+
undelete: true,
17+
version_deletion: true,
18+
version_labeling: true,
19+
versioning: true,
20+
},
21+
}
22+
}

apps/files/src/actions/deleteAction.spec.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ describe('Delete action conditions tests', () => {
111111
expect(action.displayName([file], trashbinView)).toBe('Delete permanently')
112112
})
113113

114+
test('Trashbin disabled displayName', () => {
115+
jest.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
116+
return {
117+
files: {},
118+
}
119+
})
120+
expect(action.displayName([file], view)).toBe('Delete permanently')
121+
expect(capabilities.getCapabilities).toBeCalledTimes(1)
122+
})
123+
114124
test('Shared root node displayName', () => {
115125
expect(action.displayName([file2], view)).toBe('Leave this share')
116126
expect(action.displayName([folder2], view)).toBe('Leave this share')
@@ -181,6 +191,9 @@ describe('Delete action enabled tests', () => {
181191
})
182192

183193
describe('Delete action execute tests', () => {
194+
afterEach(() => {
195+
jest.restoreAllMocks()
196+
})
184197
test('Delete action', async () => {
185198
jest.spyOn(axios, 'delete')
186199
jest.spyOn(eventBus, 'emit')
@@ -241,9 +254,125 @@ describe('Delete action execute tests', () => {
241254
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
242255
})
243256

257+
test('Delete action batch large set', async () => {
258+
jest.spyOn(axios, 'delete')
259+
jest.spyOn(eventBus, 'emit')
260+
261+
// Emulate the confirmation dialog to always confirm
262+
const confirmMock = jest.fn().mockImplementation((a, b, c, resolve) => resolve(true))
263+
// @ts-expect-error We only mock what needed
264+
window.OC = { dialogs: { confirmDestructive: confirmMock } }
265+
266+
const file1 = new File({
267+
id: 1,
268+
source: 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt',
269+
owner: 'test',
270+
mime: 'text/plain',
271+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
272+
})
273+
274+
const file2 = new File({
275+
id: 2,
276+
source: 'https://cloud.domain.com/remote.php/dav/files/test/bar.txt',
277+
owner: 'test',
278+
mime: 'text/plain',
279+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
280+
})
281+
282+
const file3 = new File({
283+
id: 3,
284+
source: 'https://cloud.domain.com/remote.php/dav/files/test/baz.txt',
285+
owner: 'test',
286+
mime: 'text/plain',
287+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
288+
})
289+
290+
const file4 = new File({
291+
id: 4,
292+
source: 'https://cloud.domain.com/remote.php/dav/files/test/qux.txt',
293+
owner: 'test',
294+
mime: 'text/plain',
295+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
296+
})
297+
298+
const file5 = new File({
299+
id: 5,
300+
source: 'https://cloud.domain.com/remote.php/dav/files/test/quux.txt',
301+
owner: 'test',
302+
mime: 'text/plain',
303+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
304+
})
305+
306+
const exec = await action.execBatch!([file1, file2, file3, file4, file5], view, '/')
307+
308+
// Enough nodes to trigger a confirmation dialog
309+
expect(confirmMock).toBeCalledTimes(1)
310+
311+
expect(exec).toStrictEqual([true, true, true, true, true])
312+
expect(axios.delete).toBeCalledTimes(5)
313+
expect(axios.delete).toHaveBeenNthCalledWith(1, 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt')
314+
expect(axios.delete).toHaveBeenNthCalledWith(2, 'https://cloud.domain.com/remote.php/dav/files/test/bar.txt')
315+
expect(axios.delete).toHaveBeenNthCalledWith(3, 'https://cloud.domain.com/remote.php/dav/files/test/baz.txt')
316+
expect(axios.delete).toHaveBeenNthCalledWith(4, 'https://cloud.domain.com/remote.php/dav/files/test/qux.txt')
317+
expect(axios.delete).toHaveBeenNthCalledWith(5, 'https://cloud.domain.com/remote.php/dav/files/test/quux.txt')
318+
319+
expect(eventBus.emit).toBeCalledTimes(5)
320+
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
321+
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
322+
expect(eventBus.emit).toHaveBeenNthCalledWith(3, 'files:node:deleted', file3)
323+
expect(eventBus.emit).toHaveBeenNthCalledWith(4, 'files:node:deleted', file4)
324+
expect(eventBus.emit).toHaveBeenNthCalledWith(5, 'files:node:deleted', file5)
325+
})
326+
327+
test('Delete action batch trashbin disabled', async () => {
328+
jest.spyOn(axios, 'delete')
329+
jest.spyOn(eventBus, 'emit')
330+
jest.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
331+
return {
332+
files: {},
333+
}
334+
})
335+
336+
// Emulate the confirmation dialog to always confirm
337+
const confirmMock = jest.fn().mockImplementation((a, b, c, resolve) => resolve(true))
338+
// @ts-expect-error We only mock what needed
339+
window.OC = { dialogs: { confirmDestructive: confirmMock } }
340+
341+
const file1 = new File({
342+
id: 1,
343+
source: 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt',
344+
owner: 'test',
345+
mime: 'text/plain',
346+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
347+
})
348+
349+
const file2 = new File({
350+
id: 2,
351+
source: 'https://cloud.domain.com/remote.php/dav/files/test/bar.txt',
352+
owner: 'test',
353+
mime: 'text/plain',
354+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
355+
})
356+
357+
const exec = await action.execBatch!([file1, file2], view, '/')
358+
359+
// Will trigger a confirmation dialog because trashbin app is disabled
360+
expect(confirmMock).toBeCalledTimes(1)
361+
362+
expect(exec).toStrictEqual([true, true])
363+
expect(axios.delete).toBeCalledTimes(2)
364+
expect(axios.delete).toHaveBeenNthCalledWith(1, 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt')
365+
expect(axios.delete).toHaveBeenNthCalledWith(2, 'https://cloud.domain.com/remote.php/dav/files/test/bar.txt')
366+
367+
expect(eventBus.emit).toBeCalledTimes(2)
368+
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
369+
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
370+
})
371+
244372
test('Delete fails', async () => {
245373
jest.spyOn(axios, 'delete').mockImplementation(() => { throw new Error('Mock error') })
246374
jest.spyOn(logger, 'error').mockImplementation(() => jest.fn())
375+
jest.spyOn(eventBus, 'emit')
247376

248377
const file = new File({
249378
id: 1,
@@ -262,4 +391,36 @@ describe('Delete action execute tests', () => {
262391
expect(eventBus.emit).toBeCalledTimes(0)
263392
expect(logger.error).toBeCalledTimes(1)
264393
})
394+
395+
test('Delete is cancelled', async () => {
396+
jest.spyOn(axios, 'delete')
397+
jest.spyOn(eventBus, 'emit')
398+
jest.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
399+
return {
400+
files: {},
401+
}
402+
})
403+
404+
// Emulate the confirmation dialog to always confirm
405+
const confirmMock = jest.fn().mockImplementation((a, b, c, resolve) => resolve(false))
406+
// @ts-expect-error We only mock what needed
407+
window.OC = { dialogs: { confirmDestructive: confirmMock } }
408+
409+
const file1 = new File({
410+
id: 1,
411+
source: 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt',
412+
owner: 'test',
413+
mime: 'text/plain',
414+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
415+
})
416+
417+
const exec = await action.execBatch!([file1], view, '/')
418+
419+
expect(confirmMock).toBeCalledTimes(1)
420+
421+
expect(exec).toStrictEqual([null])
422+
expect(axios.delete).toBeCalledTimes(0)
423+
424+
expect(eventBus.emit).toBeCalledTimes(0)
425+
})
265426
})

0 commit comments

Comments
 (0)