Skip to content

Commit 9b69ee6

Browse files
committed
fix(files): always ask for confirmation if trashbin app is disabled
Signed-off-by: skjnldsv <skjnldsv@protonmail.com> Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
1 parent d2a7a10 commit 9b69ee6

9 files changed

Lines changed: 381 additions & 108 deletions

File tree

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: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
import { action } from './deleteAction'
2323
import { expect } from '@jest/globals'
2424
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
25-
import eventBus from '@nextcloud/event-bus'
25+
import * as capabilities from '@nextcloud/capabilities'
2626
import axios from '@nextcloud/axios'
27+
import eventBus from '@nextcloud/event-bus'
2728

2829
import logger from '../logger'
2930

@@ -111,6 +112,16 @@ describe('Delete action conditions tests', () => {
111112
expect(action.displayName([file], trashbinView)).toBe('Delete permanently')
112113
})
113114

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

183194
describe('Delete action execute tests', () => {
195+
afterEach(() => {
196+
jest.restoreAllMocks()
197+
})
184198
test('Delete action', async () => {
185199
jest.spyOn(axios, 'delete')
186200
jest.spyOn(eventBus, 'emit')
@@ -235,9 +249,123 @@ describe('Delete action execute tests', () => {
235249
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
236250
})
237251

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

242370
const file = new File({
243371
id: 1,
@@ -256,4 +384,35 @@ describe('Delete action execute tests', () => {
256384
expect(eventBus.emit).toBeCalledTimes(0)
257385
expect(logger.error).toBeCalledTimes(1)
258386
})
387+
388+
test('Delete is cancelled', async () => {
389+
jest.spyOn(axios, 'delete')
390+
jest.spyOn(eventBus, 'emit')
391+
jest.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
392+
return {
393+
files: {},
394+
}
395+
})
396+
397+
// Emulate the confirmation dialog to always confirm
398+
const confirmMock = jest.fn().mockImplementation((a, b, c, resolve) => resolve(false))
399+
window.OC = { dialogs: { confirmDestructive: confirmMock } }
400+
401+
const file1 = new File({
402+
id: 1,
403+
source: 'https://cloud.domain.com/remote.php/dav/files/test/foo.txt',
404+
owner: 'test',
405+
mime: 'text/plain',
406+
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
407+
})
408+
409+
const exec = await action.execBatch!([file1], view, '/')
410+
411+
expect(confirmMock).toBeCalledTimes(1)
412+
413+
expect(exec).toStrictEqual([null])
414+
expect(axios.delete).toBeCalledTimes(0)
415+
416+
expect(eventBus.emit).toBeCalledTimes(0)
417+
})
259418
})

0 commit comments

Comments
 (0)