From 0846a701d81d7221169b8f18624a8a8791b1aff0 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 11 Nov 2021 15:07:06 +0100 Subject: [PATCH] Add unit tests for new ActionMenuItem component --- .../enhancement-contextmenu-multiple-files | 1 + .../src/components/ActionMenuItem.vue | 16 ++- .../src/mixins/actions/delete.js | 3 +- .../tests/__fixtures__/fileActions.js | 97 ++++++++++-------- .../unit/components/ActionMenuItem.spec.js | 98 +++++++++++++++++++ .../SelectedResources/BatchActions.spec.js | 28 +----- .../components/AppBar/Upload/FileDrop.spec.js | 2 +- .../SideBar/Actions/FileActions.spec.js | 2 +- 8 files changed, 176 insertions(+), 71 deletions(-) create mode 100644 packages/web-app-files/tests/unit/components/ActionMenuItem.spec.js 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) {