Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>tags/v28.0.0beta1
expect(action.enabled!([file], view)).toBe(true) | 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', () => { | test('Disabled for non-dav ressources', () => { | ||||
const file = new File({ | const file = new File({ | ||||
id: 1, | id: 1, |
/** | |||||
* @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) | |||||
}) | |||||
}) |
import type { Node } from '@nextcloud/files' | import type { Node } from '@nextcloud/files' | ||||
import type { Navigation } from '../../../files/src/services/Navigation' | import type { Navigation } from '../../../files/src/services/Navigation' | ||||
import { emit } from '@nextcloud/event-bus' | |||||
import { generateOcsUrl } from '@nextcloud/router' | import { generateOcsUrl } from '@nextcloud/router' | ||||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||||
import { translatePlural as n } from '@nextcloud/l10n' | import { translatePlural as n } from '@nextcloud/l10n' | ||||
import axios from '@nextcloud/axios' | import axios from '@nextcloud/axios' | ||||
import CheckSvg from '@mdi/svg/svg/check.svg?raw' | import CheckSvg from '@mdi/svg/svg/check.svg?raw' | ||||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction' | |||||
import { pendingSharesViewId } from '../views/shares' | import { pendingSharesViewId } from '../views/shares' | ||||
import { emit } from '@nextcloud/event-bus' | |||||
export const action = new FileAction({ | export const action = new FileAction({ | ||||
id: 'accept-share', | id: 'accept-share', | ||||
displayName: (nodes: Node[]) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length), | displayName: (nodes: Node[]) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length), | ||||
iconSvgInline: () => CheckSvg, | iconSvgInline: () => CheckSvg, | ||||
enabled: (files, view) => view.id === pendingSharesViewId, | |||||
enabled: (nodes, view) => nodes.length > 0 && view.id === pendingSharesViewId, | |||||
async exec(node: Node) { | async exec(node: Node) { | ||||
try { | try { |
/** | |||||
* @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) | |||||
}) | |||||
}) |
import { emit } from '@nextcloud/event-bus' | import { emit } from '@nextcloud/event-bus' | ||||
import { generateOcsUrl } from '@nextcloud/router' | import { generateOcsUrl } from '@nextcloud/router' | ||||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||||
import { translatePlural as n } from '@nextcloud/l10n' | import { translatePlural as n } from '@nextcloud/l10n' | ||||
import axios from '@nextcloud/axios' | import axios from '@nextcloud/axios' | ||||
import CloseSvg from '@mdi/svg/svg/close.svg?raw' | import CloseSvg from '@mdi/svg/svg/close.svg?raw' | ||||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction' | |||||
import { pendingSharesViewId } from '../views/shares' | import { pendingSharesViewId } from '../views/shares' | ||||
export const action = new FileAction({ | export const action = new FileAction({ | ||||
return false | return false | ||||
} | } | ||||
if (nodes.length === 0) { | |||||
return false | |||||
} | |||||
// disable rejecting group shares from the pending list because they anyway | // disable rejecting group shares from the pending list because they anyway | ||||
// land back into that same list after rejecting them | // land back into that same list after rejecting them | ||||
if (nodes.some(node => node.attributes.remote_id | if (nodes.some(node => node.attributes.remote_id | ||||
&& node.attributes.share_type === window.OC.Share.SHARE_TYPE_REMOTE_GROUP)) { | && node.attributes.share_type === window.OC.Share.SHARE_TYPE_REMOTE_GROUP)) { | ||||
return false | return false | ||||
} | } | ||||
return true | return true | ||||
}, | }, | ||||
/** | |||||
* @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) | |||||
}) | |||||
}) |
import type { Node } from '@nextcloud/files' | import type { Node } from '@nextcloud/files' | ||||
import type { Navigation } from '../../../files/src/services/Navigation' | import type { Navigation } from '../../../files/src/services/Navigation' | ||||
import { emit } from '@nextcloud/event-bus' | |||||
import { generateOcsUrl } from '@nextcloud/router' | import { generateOcsUrl } from '@nextcloud/router' | ||||
import { registerFileAction, FileAction } from '@nextcloud/files' | |||||
import { translatePlural as n } from '@nextcloud/l10n' | import { translatePlural as n } from '@nextcloud/l10n' | ||||
import axios from '@nextcloud/axios' | import axios from '@nextcloud/axios' | ||||
import ArrowULeftTopSvg from '@mdi/svg/svg/arrow-u-left-top.svg?raw' | 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 { deletedSharesViewId } from '../views/shares' | ||||
import { emit } from '@nextcloud/event-bus' | |||||
export const action = new FileAction({ | export const action = new FileAction({ | ||||
id: 'restore-share', | id: 'restore-share', | ||||
iconSvgInline: () => ArrowULeftTopSvg, | iconSvgInline: () => ArrowULeftTopSvg, | ||||
enabled: (nodes, view) => view.id === deletedSharesViewId, | |||||
enabled: (nodes, view) => nodes.length > 0 && view.id === deletedSharesViewId, | |||||
async exec(node: Node) { | async exec(node: Node) { | ||||
try { | try { |
*/ | */ | ||||
// register default shares types | // register default shares types | ||||
if (!window.OC) { | |||||
window.OC = {} | |||||
} | |||||
Object.assign(window.OC, { | Object.assign(window.OC, { | ||||
Share: { | Share: { | ||||
SHARE_TYPE_USER: 0, | SHARE_TYPE_USER: 0, |
// TODO: find a way to consolidate this in one place, with webpack.common.js | // TODO: find a way to consolidate this in one place, with webpack.common.js | ||||
const ignorePatterns = [ | const ignorePatterns = [ | ||||
'@buttercup/fetch', | |||||
'@juliushaertl', | '@juliushaertl', | ||||
'@mdi/svg', | '@mdi/svg', | ||||
'@nextcloud/vue', | '@nextcloud/vue', | ||||
'strip-ansi', | 'strip-ansi', | ||||
'tributejs', | 'tributejs', | ||||
'vue-material-design-icons', | 'vue-material-design-icons', | ||||
'webdav', | |||||
] | ] | ||||
const config: Config = { | const config: Config = { |