Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>tags/v28.0.0beta1
@@ -55,6 +55,19 @@ describe('View in folder action enabled tests', () => { | |||
expect(action.enabled!([file], view)).toBe(true) | |||
}) | |||
test('Disabled without 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 for non-dav ressources', () => { | |||
const file = new File({ | |||
id: 1, |
@@ -0,0 +1,223 @@ | |||
/** | |||
* @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 './acceptShareAction' | |||
import { expect } from '@jest/globals' | |||
import { File, Folder, Permission } from '@nextcloud/files' | |||
import { FileAction } from '../../../files/src/services/FileAction' | |||
import * as eventBus from '@nextcloud/event-bus' | |||
import axios from '@nextcloud/axios' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
import '../main' | |||
const view = { | |||
id: 'files', | |||
name: 'Files', | |||
} as Navigation | |||
const pendingShareView = { | |||
id: 'pendingshares', | |||
name: 'Pending shares', | |||
} as Navigation | |||
describe('Accept share action conditions tests', () => { | |||
test('Default values', () => { | |||
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).toBeInstanceOf(FileAction) | |||
expect(action.id).toBe('accept-share') | |||
expect(action.displayName([file], pendingShareView)).toBe('Accept share') | |||
expect(action.iconSvgInline([file], pendingShareView)).toBe('<svg>SvgMock</svg>') | |||
expect(action.default).toBeUndefined() | |||
expect(action.order).toBe(1) | |||
expect(action.inline).toBeDefined() | |||
expect(action.inline!(file, pendingShareView)).toBe(true) | |||
}) | |||
test('Default values for multiple files', () => { | |||
const file1 = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
const file2 = new File({ | |||
id: 2, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
expect(action.displayName([file1, file2], pendingShareView)).toBe('Accept shares') | |||
}) | |||
}) | |||
describe('Accept share action enabled tests', () => { | |||
test('Enabled with on pending shares view', () => { | |||
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], pendingShareView)).toBe(true) | |||
}) | |||
test('Disabled on wrong view', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], view)).toBe(false) | |||
}) | |||
test('Disabled without nodes', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], pendingShareView)).toBe(false) | |||
}) | |||
}) | |||
describe('Accept share action execute tests', () => { | |||
test('Accept share action', async () => { | |||
jest.spyOn(axios, 'post') | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(true) | |||
expect(axios.post).toBeCalledTimes(1) | |||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123') | |||
expect(eventBus.emit).toBeCalledTimes(1) | |||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file) | |||
}) | |||
test('Accept remote share action', async () => { | |||
jest.spyOn(axios, 'post') | |||
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, | |||
attributes: { | |||
id: 123, | |||
remote: 3, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(true) | |||
expect(axios.post).toBeCalledTimes(1) | |||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/123') | |||
expect(eventBus.emit).toBeCalledTimes(1) | |||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file) | |||
}) | |||
test('Accept share action batch', async () => { | |||
jest.spyOn(axios, 'post') | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
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, | |||
attributes: { | |||
id: 456, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.execBatch!([file1, file2], pendingShareView, '/') | |||
expect(exec).toStrictEqual([true, true]) | |||
expect(axios.post).toBeCalledTimes(2) | |||
expect(axios.post).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123') | |||
expect(axios.post).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/456') | |||
expect(eventBus.emit).toBeCalledTimes(2) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2) | |||
}) | |||
test('Accept fails', async () => { | |||
jest.spyOn(axios, 'post').mockImplementation(() => { throw new Error('Mock error') }) | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(false) | |||
expect(axios.post).toBeCalledTimes(1) | |||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123') | |||
expect(eventBus.emit).toBeCalledTimes(0) | |||
}) | |||
}) |
@@ -22,21 +22,21 @@ | |||
import type { Node } from '@nextcloud/files' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
import { emit } from '@nextcloud/event-bus' | |||
import { generateOcsUrl } from '@nextcloud/router' | |||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||
import { translatePlural as n } from '@nextcloud/l10n' | |||
import axios from '@nextcloud/axios' | |||
import CheckSvg from '@mdi/svg/svg/check.svg?raw' | |||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction' | |||
import { pendingSharesViewId } from '../views/shares' | |||
import { emit } from '@nextcloud/event-bus' | |||
export const action = new FileAction({ | |||
id: 'accept-share', | |||
displayName: (nodes: Node[]) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length), | |||
iconSvgInline: () => CheckSvg, | |||
enabled: (files, view) => view.id === pendingSharesViewId, | |||
enabled: (nodes, view) => nodes.length > 0 && view.id === pendingSharesViewId, | |||
async exec(node: Node) { | |||
try { |
@@ -0,0 +1,250 @@ | |||
/** | |||
* @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 './rejectShareAction' | |||
import { expect } from '@jest/globals' | |||
import { File, Folder, Permission } from '@nextcloud/files' | |||
import { FileAction } from '../../../files/src/services/FileAction' | |||
import * as eventBus from '@nextcloud/event-bus' | |||
import axios from '@nextcloud/axios' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
import '../main' | |||
const view = { | |||
id: 'files', | |||
name: 'Files', | |||
} as Navigation | |||
const pendingShareView = { | |||
id: 'pendingshares', | |||
name: 'Pending shares', | |||
} as Navigation | |||
describe('Reject share action conditions tests', () => { | |||
test('Default values', () => { | |||
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).toBeInstanceOf(FileAction) | |||
expect(action.id).toBe('reject-share') | |||
expect(action.displayName([file], pendingShareView)).toBe('Reject share') | |||
expect(action.iconSvgInline([file], pendingShareView)).toBe('<svg>SvgMock</svg>') | |||
expect(action.default).toBeUndefined() | |||
expect(action.order).toBe(2) | |||
expect(action.inline).toBeDefined() | |||
expect(action.inline!(file, pendingShareView)).toBe(true) | |||
}) | |||
test('Default values for multiple files', () => { | |||
const file1 = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
const file2 = new File({ | |||
id: 2, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
expect(action.displayName([file1, file2], pendingShareView)).toBe('Reject shares') | |||
}) | |||
}) | |||
describe('Reject share action enabled tests', () => { | |||
test('Enabled with on pending shares view', () => { | |||
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], pendingShareView)).toBe(true) | |||
}) | |||
test('Disabled on wrong view', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], view)).toBe(false) | |||
}) | |||
test('Disabled without nodes', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], pendingShareView)).toBe(false) | |||
}) | |||
test('Disabled if some nodes are remote group shares', () => { | |||
const folder1 = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
permissions: Permission.READ, | |||
attributes: { | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const folder2 = new Folder({ | |||
id: 2, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Bar/', | |||
owner: 'admin', | |||
permissions: Permission.READ, | |||
attributes: { | |||
remote_id: 1, | |||
share_type: window.OC.Share.SHARE_TYPE_REMOTE_GROUP, | |||
}, | |||
}) | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([folder1], pendingShareView)).toBe(true) | |||
expect(action.enabled!([folder2], pendingShareView)).toBe(false) | |||
expect(action.enabled!([folder1, folder2], pendingShareView)).toBe(false) | |||
}) | |||
}) | |||
describe('Reject share action execute tests', () => { | |||
test('Reject share 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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(true) | |||
expect(axios.delete).toBeCalledTimes(1) | |||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123') | |||
expect(eventBus.emit).toBeCalledTimes(1) | |||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file) | |||
}) | |||
test('Reject remote share 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, | |||
attributes: { | |||
id: 123, | |||
remote: 3, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(true) | |||
expect(axios.delete).toBeCalledTimes(1) | |||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/123') | |||
expect(eventBus.emit).toBeCalledTimes(1) | |||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file) | |||
}) | |||
test('Reject share 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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
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, | |||
attributes: { | |||
id: 456, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.execBatch!([file1, file2], pendingShareView, '/') | |||
expect(exec).toStrictEqual([true, true]) | |||
expect(axios.delete).toBeCalledTimes(2) | |||
expect(axios.delete).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123') | |||
expect(axios.delete).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/456') | |||
expect(eventBus.emit).toBeCalledTimes(2) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2) | |||
}) | |||
test('Reject fails', async () => { | |||
jest.spyOn(axios, 'delete').mockImplementation(() => { throw new Error('Mock error') }) | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, pendingShareView, '/') | |||
expect(exec).toBe(false) | |||
expect(axios.delete).toBeCalledTimes(1) | |||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123') | |||
expect(eventBus.emit).toBeCalledTimes(0) | |||
}) | |||
}) |
@@ -24,11 +24,11 @@ import type { Navigation } from '../../../files/src/services/Navigation' | |||
import { emit } from '@nextcloud/event-bus' | |||
import { generateOcsUrl } from '@nextcloud/router' | |||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||
import { translatePlural as n } from '@nextcloud/l10n' | |||
import axios from '@nextcloud/axios' | |||
import CloseSvg from '@mdi/svg/svg/close.svg?raw' | |||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction' | |||
import { pendingSharesViewId } from '../views/shares' | |||
export const action = new FileAction({ | |||
@@ -41,12 +41,17 @@ export const action = new FileAction({ | |||
return false | |||
} | |||
if (nodes.length === 0) { | |||
return false | |||
} | |||
// disable rejecting group shares from the pending list because they anyway | |||
// land back into that same list after rejecting them | |||
if (nodes.some(node => node.attributes.remote_id | |||
&& node.attributes.share_type === window.OC.Share.SHARE_TYPE_REMOTE_GROUP)) { | |||
return false | |||
} | |||
return true | |||
}, | |||
@@ -0,0 +1,196 @@ | |||
/** | |||
* @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 './restoreShareAction' | |||
import { expect } from '@jest/globals' | |||
import { File, Folder, Permission } from '@nextcloud/files' | |||
import { FileAction } from '../../../files/src/services/FileAction' | |||
import * as eventBus from '@nextcloud/event-bus' | |||
import axios from '@nextcloud/axios' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
import '../main' | |||
const view = { | |||
id: 'files', | |||
name: 'Files', | |||
} as Navigation | |||
const deletedShareView = { | |||
id: 'deletedshares', | |||
name: 'Deleted shares', | |||
} as Navigation | |||
describe('Restore share action conditions tests', () => { | |||
test('Default values', () => { | |||
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).toBeInstanceOf(FileAction) | |||
expect(action.id).toBe('restore-share') | |||
expect(action.displayName([file], deletedShareView)).toBe('Restore share') | |||
expect(action.iconSvgInline([file], deletedShareView)).toBe('<svg>SvgMock</svg>') | |||
expect(action.default).toBeUndefined() | |||
expect(action.order).toBe(1) | |||
expect(action.inline).toBeDefined() | |||
expect(action.inline!(file, deletedShareView)).toBe(true) | |||
}) | |||
test('Default values for multiple files', () => { | |||
const file1 = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
const file2 = new File({ | |||
id: 2, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
expect(action.displayName([file1, file2], deletedShareView)).toBe('Restore shares') | |||
}) | |||
}) | |||
describe('Restore share action enabled tests', () => { | |||
test('Enabled with on pending shares view', () => { | |||
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], deletedShareView)).toBe(true) | |||
}) | |||
test('Disabled on wrong view', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], view)).toBe(false) | |||
}) | |||
test('Disabled without nodes', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], deletedShareView)).toBe(false) | |||
}) | |||
}) | |||
describe('Restore share action execute tests', () => { | |||
test('Restore share action', async () => { | |||
jest.spyOn(axios, 'post') | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, deletedShareView, '/') | |||
expect(exec).toBe(true) | |||
expect(axios.post).toBeCalledTimes(1) | |||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123') | |||
expect(eventBus.emit).toBeCalledTimes(1) | |||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file) | |||
}) | |||
test('Restore share action batch', async () => { | |||
jest.spyOn(axios, 'post') | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
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, | |||
attributes: { | |||
id: 456, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.execBatch!([file1, file2], deletedShareView, '/') | |||
expect(exec).toStrictEqual([true, true]) | |||
expect(axios.post).toBeCalledTimes(2) | |||
expect(axios.post).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123') | |||
expect(axios.post).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/456') | |||
expect(eventBus.emit).toBeCalledTimes(2) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1) | |||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2) | |||
}) | |||
test('Restore fails', async () => { | |||
jest.spyOn(axios, 'post').mockImplementation(() => { throw new Error('Mock error') }) | |||
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, | |||
attributes: { | |||
id: 123, | |||
share_type: window.OC.Share.SHARE_TYPE_USER, | |||
}, | |||
}) | |||
const exec = await action.exec(file, deletedShareView, '/') | |||
expect(exec).toBe(false) | |||
expect(axios.post).toBeCalledTimes(1) | |||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123') | |||
expect(eventBus.emit).toBeCalledTimes(0) | |||
}) | |||
}) |
@@ -22,14 +22,14 @@ | |||
import type { Node } from '@nextcloud/files' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
import { emit } from '@nextcloud/event-bus' | |||
import { generateOcsUrl } from '@nextcloud/router' | |||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||
import { translatePlural as n } from '@nextcloud/l10n' | |||
import axios from '@nextcloud/axios' | |||
import ArrowULeftTopSvg from '@mdi/svg/svg/arrow-u-left-top.svg?raw' | |||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction' | |||
import { deletedSharesViewId } from '../views/shares' | |||
import { emit } from '@nextcloud/event-bus' | |||
export const action = new FileAction({ | |||
id: 'restore-share', | |||
@@ -37,7 +37,7 @@ export const action = new FileAction({ | |||
iconSvgInline: () => ArrowULeftTopSvg, | |||
enabled: (nodes, view) => view.id === deletedSharesViewId, | |||
enabled: (nodes, view) => nodes.length > 0 && view.id === deletedSharesViewId, | |||
async exec(node: Node) { | |||
try { |
@@ -22,6 +22,10 @@ | |||
*/ | |||
// register default shares types | |||
if (!window.OC) { | |||
window.OC = {} | |||
} | |||
Object.assign(window.OC, { | |||
Share: { | |||
SHARE_TYPE_USER: 0, |
@@ -23,6 +23,7 @@ import type { Config } from 'jest' | |||
// TODO: find a way to consolidate this in one place, with webpack.common.js | |||
const ignorePatterns = [ | |||
'@buttercup/fetch', | |||
'@juliushaertl', | |||
'@mdi/svg', | |||
'@nextcloud/vue', | |||
@@ -34,6 +35,7 @@ const ignorePatterns = [ | |||
'strip-ansi', | |||
'tributejs', | |||
'vue-material-design-icons', | |||
'webdav', | |||
] | |||
const config: Config = { |