diff --git a/changelog/unreleased/enhancement-contextmenu-multiple-files b/changelog/unreleased/enhancement-contextmenu-multiple-files
index 2cfd942c5af..e1ea7ec4e1d 100644
--- a/changelog/unreleased/enhancement-contextmenu-multiple-files
+++ b/changelog/unreleased/enhancement-contextmenu-multiple-files
@@ -4,3 +4,4 @@ We have enabled batch actions in the context menu for when multiple resources ar
https://github.com/owncloud/web/pull/5973
https://github.com/owncloud/web/issues/5968
+https://github.com/owncloud/web/issues/5977
diff --git a/packages/web-app-files/src/components/ActionMenuItem.vue b/packages/web-app-files/src/components/ActionMenuItem.vue
index 4e9c7639533..1ef16a8c580 100644
--- a/packages/web-app-files/src/components/ActionMenuItem.vue
+++ b/packages/web-app-files/src/components/ActionMenuItem.vue
@@ -4,13 +4,23 @@
:is="action.componentType"
v-bind="getComponentProps(action, items)"
:class="['oc-text-bold', action.class]"
+ data-testid="action-handler"
v-on="getComponentListeners(action, items)"
>
-
-
- {{ action.label(filterParams) }}
+
+
+ {{
+ action.label(filterParams)
+ }}
diff --git a/packages/web-app-files/src/mixins/actions/delete.js b/packages/web-app-files/src/mixins/actions/delete.js
index e0264612d77..6fb1c10eade 100644
--- a/packages/web-app-files/src/mixins/actions/delete.js
+++ b/packages/web-app-files/src/mixins/actions/delete.js
@@ -35,6 +35,7 @@ export default {
},
{
// this menu item is ONLY for the trashbin (permanently delete a file/folder)
+ name: 'delete-permanent',
icon: 'delete',
label: () => this.$gettext('Delete'),
handler: this.$_delete_trigger,
@@ -45,7 +46,7 @@ export default {
return resources.length > 0
},
componentType: 'oc-button',
- class: 'oc-files-actions-delete-trigger'
+ class: 'oc-files-actions-delete-permanent-trigger'
}
]
}
diff --git a/packages/web-app-files/tests/__fixtures__/fileActions.js b/packages/web-app-files/tests/__fixtures__/fileActions.js
index 0eb312ce47b..831f3b5ee17 100644
--- a/packages/web-app-files/tests/__fixtures__/fileActions.js
+++ b/packages/web-app-files/tests/__fixtures__/fileActions.js
@@ -86,79 +86,98 @@ exports.apps = {
const fileActions = {
download: {
- label: 'Download',
- class: 'oc-files-actions-sidebar-download-trigger',
- selector: '.oc-files-actions-sidebar-download-trigger',
- handler: jest.fn()
+ name: 'download-file',
+ icon: 'file_download',
+ handler: jest.fn(),
+ label: () => 'Download',
+ componentType: 'oc-button',
+ class: 'oc-files-actions-download-file-trigger',
+ selector: '.oc-files-actions-download-file-trigger'
},
copy: {
- label: 'Copy',
- class: 'oc-files-actions-sidebar-copy-trigger',
- selector: '.oc-files-actions-sidebar-copy-trigger',
- handler: jest.fn()
+ name: 'copy',
+ icon: 'file_copy',
+ handler: jest.fn(),
+ label: () => 'Copy',
+ componentType: 'oc-button',
+ class: 'oc-files-actions-copy-trigger',
+ selector: '.oc-files-actions-copy-trigger'
},
rename: {
- label: 'Rename',
- class: 'oc-files-actions-sidebar-rename-trigger',
- selector: '.oc-files-actions-sidebar-rename-trigger',
- handler: jest.fn()
+ name: 'rename',
+ icon: 'edit',
+ handler: jest.fn(),
+ label: () => 'Rename',
+ componentType: 'oc-button',
+ class: 'oc-files-actions-rename-trigger',
+ selector: '.oc-files-actions--rename-trigger'
},
move: {
- label: 'Move',
- class: 'oc-files-actions-sidebar-move-trigger',
- selector: '.oc-files-actions-sidebar-move-trigger',
- handler: jest.fn()
+ name: 'move',
+ icon: 'folder-move',
+ handler: jest.fn(),
+ label: () => 'Move',
+ componentType: 'oc-button',
+ class: 'oc-files-actions-move-trigger',
+ selector: '.oc-files-actions-move-trigger'
},
delete: {
- label: 'Delete',
- class: 'oc-files-actions-sidebar-delete-trigger',
- selector: '.oc-files-actions-sidebar-delete-trigger',
- handler: jest.fn()
+ name: 'delete',
+ icon: 'delete',
+ handler: jest.fn(),
+ label: () => 'Delete',
+ componentType: 'oc-button',
+ class: 'oc-files-actions-delete-trigger',
+ selector: '.oc-files-actions-delete-trigger'
},
'markdown-editor': {
- label: 'Open in Markdown Editor',
- class: 'oc-files-actions-sidebar-markdown-editor-trigger',
- selector: '.oc-files-actions-sidebar-markdown-editor-trigger',
handler: jest.fn(),
+ label: () => 'Open in Markdown Editor',
+ class: 'oc-files-actions-markdown-editor-trigger',
+ selector: '.oc-files-actions-markdown-editor-trigger',
opensInNewWindow: true
},
'draw-io': {
- label: 'Open in DrawIO',
- class: 'oc-files-actions-sidebar-draw-io-trigger',
- selector: '.oc-files-actions-sidebar-draw-io-trigger',
handler: jest.fn(),
+ label: () => 'Open in DrawIO',
+ class: 'oc-files-actions-draw-io-trigger',
+ selector: '.oc-files-actions-draw-io-trigger',
opensInNewWindow: true
},
mediaviewer: {
- label: 'Open in MediaViewer',
- class: 'oc-files-actions-sidebar-mediaviewer-trigger',
- selector: '.oc-files-actions-sidebar-mediaviewer-trigger',
- handler: jest.fn()
+ handler: jest.fn(),
+ label: () => 'Open in MediaViewer',
+ class: 'oc-files-actions-mediaviewer-trigger',
+ selector: '.oc-files-actions-mediaviewer-trigger'
},
- 'open-folder': {
- label: 'Open Folder',
- class: 'oc-files-actions-sidebar-navigate-trigger',
- selector: '.oc-files-actions-sidebar-navigate-trigger',
- handler: jest.fn()
+ navigate: {
+ name: 'navigate',
+ icon: 'folder-open',
+ route: 'files-personal',
+ label: () => 'Open Folder',
+ componentType: 'router-link',
+ class: 'oc-files-actions-navigate-trigger',
+ selector: '.oc-files-actions-navigate-trigger'
}
}
exports.fileActions = fileActions
exports.getActions = function (actions = []) {
- const defaultActions = ['download', 'markdown-editor', 'draw-io', 'mediaviewer', 'open-folder']
+ const defaultActions = ['download', 'markdown-editor', 'draw-io', 'mediaviewer', 'navigate']
const res = []
for (const key of actions) {
const action = fileActions[key]
const actionObj = {
- icon: key,
+ name: action.name,
+ icon: action.icon || key,
handler: action.handler,
+ label: action.label,
isEnabled: () => true,
- label: () => action.label,
- componentType: 'oc-button',
+ componentType: action.componentType || 'oc-button',
class: action.class,
canBeDefault: defaultActions.indexOf(key) > -1,
opensInNewWindow: action.opensInNewWindow || false
diff --git a/packages/web-app-files/tests/unit/components/ActionMenuItem.spec.js b/packages/web-app-files/tests/unit/components/ActionMenuItem.spec.js
new file mode 100644
index 00000000000..8224e693664
--- /dev/null
+++ b/packages/web-app-files/tests/unit/components/ActionMenuItem.spec.js
@@ -0,0 +1,98 @@
+import { shallowMount, createLocalVue, mount } from '@vue/test-utils'
+import Vuex from 'vuex'
+import DesignSystem from 'owncloud-design-system'
+import GetTextPlugin from 'vue-gettext'
+
+import ActionMenuItem from '@files/src/components/ActionMenuItem'
+import { fileActions } from '../../__fixtures__/fileActions'
+
+const localVue = createLocalVue()
+localVue.use(Vuex)
+localVue.use(DesignSystem)
+localVue.use(GetTextPlugin, {
+ translations: 'does-not-matter.json',
+ silent: true
+})
+
+const selectors = {
+ handler: '[data-testid="action-handler"]',
+ icon: '[data-testid="action-icon"]',
+ img: '[data-testid="action-img"]',
+ label: '[data-testid="action-label"]',
+ srHint: '[data-testid="action-sr-hint"]'
+}
+
+describe('ActionMenuItem component', () => {
+ it('renders an icon if there is one defined in the action', () => {
+ const action = fileActions.download
+ const wrapper = getShallowWrapper(action)
+ expect(wrapper.find(selectors.icon).exists()).toBeTruthy()
+ expect(wrapper.find(selectors.icon).attributes().name).toBe(action.icon)
+ })
+ it('renders an image if there is one defined in the action', () => {
+ const action = { ...fileActions.download, img: 'https://owncloud.tld/img.png' }
+ const wrapper1 = getShallowWrapper(action)
+ expect(wrapper1.find(selectors.icon).exists()).toBeTruthy()
+ expect(wrapper1.find(selectors.icon).attributes().name).toBe(action.icon)
+ delete action.icon
+ const wrapper2 = getShallowWrapper(action)
+ expect(wrapper2.find(selectors.icon).exists()).toBeFalsy()
+ expect(wrapper2.find(selectors.img).exists()).toBeTruthy()
+ expect(wrapper2.find(selectors.img).attributes().src).toBe(action.img)
+ })
+ it('renders the action label', () => {
+ const action = fileActions.download
+ const wrapper = getShallowWrapper(action)
+ expect(wrapper.find(selectors.label).exists()).toBeTruthy()
+ expect(wrapper.find(selectors.label).text()).toBe(action.label())
+ })
+ describe('component is of type oc-button', () => {
+ it('calls the action handler on button click', async () => {
+ const action = fileActions.download
+ const spyHandler = jest.spyOn(action, 'handler')
+ const wrapper = getWrapper(action)
+ const button = wrapper.find(selectors.handler)
+ expect(button.exists()).toBeTruthy()
+ expect(button.element.tagName).toBe('BUTTON')
+ await button.trigger('click')
+ expect(spyHandler).toBeCalled()
+ })
+ })
+ describe('component is of type router-link', () => {
+ it('has a link', () => {
+ const action = fileActions.navigate
+ const wrapper = getWrapper(action)
+ const link = wrapper.find(selectors.handler)
+ expect(link.exists()).toBeTruthy()
+ expect(link.element.tagName).toBe('ROUTER-LINK-STUB')
+ // FIXME: to.name cannot be accessed, because the attributes().to holds a string containing `[object Object]`.
+ // That doesn't allow checking the name of the router-link.
+ // expect(link.attributes().href).toBe(action.route)
+ })
+ })
+})
+
+function getShallowWrapper(action, items = [], appearance = null) {
+ return shallowMount(ActionMenuItem, {
+ localVue,
+ propsData: {
+ action,
+ items,
+ ...(appearance && { appearance })
+ }
+ })
+}
+
+function getWrapper(action, items = [], appearance = null) {
+ return mount(ActionMenuItem, {
+ localVue,
+ stubs: {
+ 'router-link': true
+ },
+ propsData: {
+ action,
+ items,
+ ...(appearance && { appearance })
+ }
+ })
+}
diff --git a/packages/web-app-files/tests/unit/components/AppBar/SelectedResources/BatchActions.spec.js b/packages/web-app-files/tests/unit/components/AppBar/SelectedResources/BatchActions.spec.js
index d083e81c5f1..8986843dd10 100644
--- a/packages/web-app-files/tests/unit/components/AppBar/SelectedResources/BatchActions.spec.js
+++ b/packages/web-app-files/tests/unit/components/AppBar/SelectedResources/BatchActions.spec.js
@@ -33,32 +33,8 @@ describe('Batch Actions component', () => {
jest.resetModules()
})
- describe.each(['files-personal', 'files-favorites', 'files-public-list', 'files-shared-with-me'])(
- '%s page',
- (page) => {
- const $route = {
- name: page
- }
-
- it('should not display action buttons if no items are selected', () => {
- const store = createStore({ selected: [] })
- const wrapper = createShallowMountWrapper({
- store,
- mocks: {
- $route: {
- ...$route,
- meta: {
- hasBulkActions: false
- }
- }
- }
- })
- const actionButtons = wrapper.findAll(elSelector.ocButtonStub)
-
- expect(actionButtons.length).toEqual(0)
- })
- }
- )
+ it.todo('renders an empty list if there are no batch actions available')
+ it.todo('renders a button for each available batch action')
})
function createShallowMountWrapper(options = {}) {
diff --git a/packages/web-app-files/tests/unit/components/AppBar/Upload/FileDrop.spec.js b/packages/web-app-files/tests/unit/components/AppBar/Upload/FileDrop.spec.js
index 460eeec238d..d86b00a32a1 100644
--- a/packages/web-app-files/tests/unit/components/AppBar/Upload/FileDrop.spec.js
+++ b/packages/web-app-files/tests/unit/components/AppBar/Upload/FileDrop.spec.js
@@ -82,7 +82,7 @@ function createWrapper(options = {}) {
return shallowMount(FileDrop, {
localVue,
stubs: { translate: true, 'oc-dropzone': true },
- props: {
+ propsData: {
rootPath: '/',
path: '/'
},
diff --git a/packages/web-app-files/tests/unit/components/SideBar/Actions/FileActions.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Actions/FileActions.spec.js
index c7648eba4cb..91843bf9e7a 100644
--- a/packages/web-app-files/tests/unit/components/SideBar/Actions/FileActions.spec.js
+++ b/packages/web-app-files/tests/unit/components/SideBar/Actions/FileActions.spec.js
@@ -32,7 +32,7 @@ describe('FileActions', () => {
jest.clearAllMocks()
})
it('renders action handlers as clickable elements', async () => {
- const actions = ['copy', 'download', 'move', 'open-folder', 'markdown-editor']
+ const actions = ['copy', 'download', 'move', 'navigate', 'markdown-editor']
const wrapper = getWrapper(filesPersonalRoute, actions)
for (const button of actions) {