mirror of https://github.com/nextcloud/server.git
Merge pull request #38806 from nextcloud/feat/f2v/actions-1
commit
32bbe3db81
@ -1,15 +0,0 @@
|
||||
codecov:
|
||||
branch: master
|
||||
ci:
|
||||
- drone.nextcloud.com
|
||||
- !scrutinizer-ci.com
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "70...100"
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
|
||||
comment: off
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
export const getCurrentUser = function() {
|
||||
return {
|
||||
uid: 'test',
|
||||
displayName: 'Test',
|
||||
isAdmin: false,
|
||||
}
|
||||
}
|
||||
|
||||
export const getRequestToken = function() {
|
||||
return 'test-token-1234'
|
||||
}
|
||||
|
||||
export const onRequestTokenUpdate = function() {}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
delete: async () => ({ status: 200, data: {} }),
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
export default 'SvgMock'
|
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { action } from './deleteAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
import logger from '../logger'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
const trashbinView = {
|
||||
id: 'trashbin',
|
||||
name: 'Trashbin',
|
||||
} as Navigation
|
||||
|
||||
describe('Delete action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('delete')
|
||||
expect(action.displayName([], view)).toBe('Delete')
|
||||
expect(action.iconSvgInline([], view)).toBe('SvgMock')
|
||||
expect(action.order).toBe(100)
|
||||
})
|
||||
|
||||
test('Default trashbin view values', () => {
|
||||
expect(action.displayName([], trashbinView)).toBe('Delete permanently')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Delete action enabled tests', () => {
|
||||
test('Enabled with DELETE permissions', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled without DELETE permissions', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without nodes', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled if not all nodes can be deleted', () => {
|
||||
const folder1 = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.DELETE,
|
||||
})
|
||||
const folder2 = new Folder({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Bar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder1], view)).toBe(true)
|
||||
expect(action.enabled!([folder2], view)).toBe(false)
|
||||
expect(action.enabled!([folder1, folder2], view)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Delete action execute tests', () => {
|
||||
test('Delete action', async () => {
|
||||
jest.spyOn(axios, 'delete')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.delete).toBeCalledTimes(1)
|
||||
expect(axios.delete).toBeCalledWith('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Delete action batch', async () => {
|
||||
jest.spyOn(axios, 'delete')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
|
||||
})
|
||||
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file1, file2], view, '/')
|
||||
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
expect(axios.delete).toBeCalledTimes(2)
|
||||
expect(axios.delete).toHaveBeenNthCalledWith(1, 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt')
|
||||
expect(axios.delete).toHaveBeenNthCalledWith(2, 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(2)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
|
||||
})
|
||||
|
||||
test('Delete fails', async () => {
|
||||
jest.spyOn(axios, 'delete').mockImplementation(() => { throw new Error('Mock error') })
|
||||
jest.spyOn(logger, 'error').mockImplementation(() => jest.fn())
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ | Permission.UPDATE | Permission.DELETE,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
|
||||
expect(exec).toBe(false)
|
||||
expect(axios.delete).toBeCalledTimes(1)
|
||||
expect(axios.delete).toBeCalledWith('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(0)
|
||||
expect(logger.error).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { action } from './downloadAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
import logger from '../logger'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
describe('Download action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('download')
|
||||
expect(action.displayName([], view)).toBe('Download')
|
||||
expect(action.iconSvgInline([], view)).toBe('SvgMock')
|
||||
expect(action.order).toBe(30)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Download action enabled tests', () => {
|
||||
test('Enabled with READ permissions', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled without READ permissions', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled if not all nodes have READ permissions', () => {
|
||||
const folder1 = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
const folder2 = new Folder({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Bar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder1], view)).toBe(true)
|
||||
expect(action.enabled!([folder2], view)).toBe(false)
|
||||
expect(action.enabled!([folder1, folder2], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without nodes', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Download action execute tests', () => {
|
||||
const link = {
|
||||
click: jest.fn(),
|
||||
} as unknown as HTMLAnchorElement
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(document, 'createElement').mockImplementation(() => link)
|
||||
})
|
||||
|
||||
test('Download single file', async () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(link.download).toEqual('')
|
||||
expect(link.href).toEqual('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
|
||||
expect(link.click).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Download single file with batch', async () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file], view, '/')
|
||||
|
||||
// Silent action
|
||||
expect(exec).toStrictEqual([null])
|
||||
expect(link.download).toEqual('')
|
||||
expect(link.href).toEqual('https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt')
|
||||
expect(link.click).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Download single folder', async () => {
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/FooBar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(folder, view, '/')
|
||||
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(link.download).toEqual('')
|
||||
expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2F&files=%5B%22FooBar%22%5D&downloadStartSecret=')).toBe(true)
|
||||
expect(link.click).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Download multiple nodes', async () => {
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Dir/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
const file2 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Dir/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file1, file2], view, '/Dir')
|
||||
|
||||
// Silent action
|
||||
expect(exec).toStrictEqual([null, null])
|
||||
expect(link.download).toEqual('')
|
||||
expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2FDir&files=%5B%22foo.txt%22%2C%22bar.txt%22%5D&downloadStartSecret=')).toBe(true)
|
||||
expect(link.click).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { Permission, Node, FileType } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import ArrowDown from '@mdi/svg/svg/arrow-down.svg?raw'
|
||||
|
||||
import { registerFileAction, FileAction } from '../services/FileAction'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
|
||||
const triggerDownload = function(url: string) {
|
||||
const hiddenElement = document.createElement('a')
|
||||
hiddenElement.download = ''
|
||||
hiddenElement.href = url
|
||||
hiddenElement.click()
|
||||
}
|
||||
|
||||
const downloadNodes = function(dir: string, nodes: Node[]) {
|
||||
const secret = Math.random().toString(36).substring(2)
|
||||
const url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', {
|
||||
dir,
|
||||
secret,
|
||||
files: JSON.stringify(nodes.map(node => node.basename)),
|
||||
})
|
||||
triggerDownload(url)
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'download',
|
||||
displayName: () => t('files', 'Download'),
|
||||
iconSvgInline: () => ArrowDown,
|
||||
|
||||
enabled(nodes: Node[]) {
|
||||
return nodes.length > 0 && nodes
|
||||
.map(node => node.permissions)
|
||||
.every(permission => (permission & Permission.READ) !== 0)
|
||||
},
|
||||
|
||||
async exec(node: Node, view: Navigation, dir: string) {
|
||||
if (node.type === FileType.Folder) {
|
||||
downloadNodes(dir, [node])
|
||||
return null
|
||||
}
|
||||
|
||||
triggerDownload(node.source)
|
||||
return null
|
||||
},
|
||||
|
||||
async execBatch(nodes: Node[], view: Navigation, dir: string) {
|
||||
if (nodes.length === 1) {
|
||||
this.exec(nodes[0], view, dir)
|
||||
return [null]
|
||||
}
|
||||
|
||||
downloadNodes(dir, nodes)
|
||||
return new Array(nodes.length).fill(null)
|
||||
},
|
||||
|
||||
order: 30,
|
||||
})
|
||||
|
||||
registerFileAction(action)
|
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { action } from './openFolderAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
describe('Open folder action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/FooBar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('open-folder')
|
||||
expect(action.displayName([folder], view)).toBe('Open folder FooBar')
|
||||
expect(action.iconSvgInline([], view)).toBe('SvgMock')
|
||||
expect(action.default).toBe(true)
|
||||
expect(action.order).toBe(-100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open folder action enabled tests', () => {
|
||||
test('Enabled for folders', () => {
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/FooBar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled for non-dav ressources', () => {
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://domain.com/data/FooBar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled if more than one node', () => {
|
||||
const folder1 = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
const folder2 = new Folder({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Bar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder1, folder2], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled for files', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without READ permissions', () => {
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder], view)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open folder action execute tests', () => {
|
||||
test('Open folder', async () => {
|
||||
const goToRouteMock = jest.fn()
|
||||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
|
||||
|
||||
const folder = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/FooBar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(folder, view, '/')
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(goToRouteMock).toBeCalledTimes(1)
|
||||
expect(goToRouteMock).toBeCalledWith(null, null, { dir: '/FooBar' })
|
||||
})
|
||||
|
||||
test('Open folder fails without node', async () => {
|
||||
const goToRouteMock = jest.fn()
|
||||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
|
||||
|
||||
// @ts-ignore null as Node
|
||||
const exec = await action.exec(null, view, '/')
|
||||
expect(exec).toBe(false)
|
||||
expect(goToRouteMock).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
test('Open folder fails without Folder', async () => {
|
||||
const goToRouteMock = jest.fn()
|
||||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
expect(exec).toBe(false)
|
||||
expect(goToRouteMock).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { action } from './sidebarAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
import logger from '../logger'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
describe('Open sidebar action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('details')
|
||||
expect(action.displayName([], view)).toBe('Details')
|
||||
expect(action.iconSvgInline([], view)).toBe('SvgMock')
|
||||
expect(action.default).toBe(true)
|
||||
expect(action.order).toBe(-50)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open folder action enabled tests', () => {
|
||||
test('Enabled for ressources within user root folder', () => {
|
||||
window.OCA = { Files: { Sidebar: {} } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled if more than one node', () => {
|
||||
window.OCA = { Files: { Sidebar: {} } }
|
||||
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
const file2 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file1, file2], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled if no Sidebar', () => {
|
||||
window.OCA = {}
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled for non-dav ressources', () => {
|
||||
window.OCA = { Files: { Sidebar: {} } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://domain.com/documents/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open sidebar action exec tests', () => {
|
||||
test('Open sidebar', async () => {
|
||||
const openMock = jest.fn()
|
||||
window.OCA = { Files: { Sidebar: { open: openMock } } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(openMock).toBeCalledWith('/foobar.txt')
|
||||
})
|
||||
|
||||
test('Open sidebar fails', async () => {
|
||||
const openMock = jest.fn(() => { throw new Error('Mock error') })
|
||||
window.OCA = { Files: { Sidebar: { open: openMock } } }
|
||||
jest.spyOn(logger, 'error').mockImplementation(() => jest.fn())
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
expect(exec).toBe(false)
|
||||
expect(openMock).toBeCalledTimes(1)
|
||||
expect(logger.error).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
@ -0,0 +1,7 @@
|
||||
codecov:
|
||||
branch: master
|
||||
ci:
|
||||
- drone.nextcloud.com
|
||||
- '!scrutinizer-ci.com'
|
||||
|
||||
comment: false
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue