diff --git a/packages/web-app-files/src/views/LocationPicker.vue b/packages/web-app-files/src/views/LocationPicker.vue index b89db246f3f..a56fc85306d 100644 --- a/packages/web-app-files/src/views/LocationPicker.vue +++ b/packages/web-app-files/src/views/LocationPicker.vue @@ -3,7 +3,11 @@

-

+


diff --git a/packages/web-app-files/tests/unit/views/LocationPicker.spec.js b/packages/web-app-files/tests/unit/views/LocationPicker.spec.js new file mode 100644 index 00000000000..810bd465118 --- /dev/null +++ b/packages/web-app-files/tests/unit/views/LocationPicker.spec.js @@ -0,0 +1,489 @@ +import { createFile, getStore, localVue } from './views.setup.js' +import LocationPicker from '@files/src/views/LocationPicker.vue' +import { mount, RouterLinkStub, shallowMount } from '@vue/test-utils' + +const component = { ...LocationPicker, mounted: jest.fn() } + +const defaultStubs = { + 'oc-breadcrumb': true, + 'oc-grid': true, + 'oc-button': true, + 'oc-icon': true, + 'oc-table-files': true +} + +localVue.prototype.$client.files = { + list: async () => [], + move: async () => {} +} +localVue.prototype.$client.publicFiles = { + list: async () => [], + move: async () => {} +} + +const router = { + push: jest.fn(), + afterEach: jest.fn(), + currentRoute: { + query: {} + } +} + +const listLoaderStub = 'list-loader-stub' +const breadcrumbStub = 'oc-breadcrumb-stub' +const listInfoStub = 'list-info-stub' +const filesPaginationStub = 'pagination-stub' +const translateStub = 'translate-stub' + +const selectors = { + selectionInfo: '.location-picker-selection-info', + currentHint: '[data-testid="location-picker-current-hint"]', + cancelButton: '#location-picker-btn-cancel', + confirmButton: '#location-picker-btn-confirm', + filesView: '#files-view', + noContentMessage: '#files-location-picker-empty', + filesTable: '#files-location-picker-table', + paginationCurrentItem: '.oc-pagination-list-item-current' +} + +describe('LocationPicker', () => { + describe('files app bar', () => { + it.each([ + { action: 'move', item: '' }, + { action: 'copy', item: '' }, + { action: 'move', item: '/parent/child' }, + { action: 'move', item: '/parent' }, + { action: 'copy', item: '/parent/child' }, + { action: 'copy', item: '/parent' }, + { action: 'random-action', item: '/सिम्पल फोल्डर/टेश्ट' }, + { action: '', item: '/parent/child' }, + { action: true, item: '/parent/child' }, + { action: {}, item: '/parent/child' }, + { + action: 'random-action', + item: '/सिम्पल फोल्डर/टेश्ट फोल्डर' + }, + { + action: 'random-action', + item: '/123/456' + }, + { + action: 'random-action', + item: '/😂/🔥' + } + ])('should show location picker selection info according to route action and item', (input) => { + const wrapper = getShallowWrapper({ + $route: getRoute({ action: input.action, item: input.item }) + }) + + const selection = wrapper.find(selectors.selectionInfo) + + expect(selection.exists()).toBeTruthy() + expect(selection).toMatchSnapshot() + }) + it.each(['copy', 'move'])( + 'should show current hint according to the current route action', + (action) => { + const wrapper = getShallowWrapper({ $route: getRoute({ action: action }) }) + const currentHint = wrapper.find(selectors.currentHint) + + expect(currentHint.exists()).toBeTruthy() + expect(currentHint).toMatchSnapshot() + } + ) + describe('should display breadcrumb with items', () => { + it.each(['parent/child', 'parent//child', '/parent/child', '/parent/child/'])( + 'if route params context is public', + (input) => { + const route = getRoute({ context: 'public', item: input }) + const wrapper = getShallowWrapper({ $route: route }) + const breadcrumbItems = wrapper.find(breadcrumbStub).props().items + + expect(breadcrumbItems.length).toBe(2) + expect(breadcrumbItems[0]).toMatchObject({ + index: 0, + text: 'Public link', + to: { + name: route.name, + params: { + action: route.params.action, + item: 'parent' + }, + query: { + resource: [route.query.resource] + } + } + }) + expect(breadcrumbItems[1]).toMatchObject({ + index: 2, + text: 'child' + }) + expect(breadcrumbItems[1].to).toBeUndefined() + } + ) + it.each(['parent/child', 'parent//child', '/parent/child', '/parent/child/'])( + 'if route params context is not public', + (input) => { + const route = getRoute({ item: input }) + const wrapper = getShallowWrapper({ $route: route }) + const breadcrumbItems = wrapper.find(breadcrumbStub).props().items + + expect(breadcrumbItems.length).toBe(3) + expect(breadcrumbItems[0]).toMatchObject({ + index: 0, + text: 'All files', + to: { + name: route.name, + params: { action: route.params.action, item: '/' }, + query: { + resource: [route.query.resource] + } + } + }) + expect(breadcrumbItems[1]).toMatchObject({ + index: 1, + text: 'parent', + to: { + name: route.name, + params: { action: route.params.action, item: 'parent' }, + query: { + resource: [route.query.resource] + } + } + }) + expect(breadcrumbItems[2]).toMatchObject({ + index: 2, + text: 'child' + }) + expect(breadcrumbItems[2].to).toBeUndefined() + } + ) + }) + describe('cancel button', () => { + it('should show the location picker cancel button', () => { + const wrapper = getShallowWrapper() + const cancelButton = wrapper.find(selectors.cancelButton) + + expect(cancelButton.exists()).toBeTruthy() + expect(cancelButton.find(translateStub).exists()).toBeTruthy() + }) + it('should call "leaveLocationPicker" method when clicked', async () => { + const spyLeaveLocationPicker = jest.spyOn(LocationPicker.methods, 'leaveLocationPicker') + const spyRouterPush = jest.spyOn(router, 'push') + + const wrapper = getMountedWrapper() + const cancelButton = wrapper.find(selectors.cancelButton) + + await cancelButton.trigger('click') + + expect(spyLeaveLocationPicker).toHaveBeenCalledTimes(1) + expect(spyLeaveLocationPicker).toHaveBeenCalledWith('/some/item') + expect(spyRouterPush).toHaveBeenCalledTimes(1) + expect(spyRouterPush).toHaveBeenCalledWith({ + name: 'files-spaces-personal-home', + params: { + item: '/some/item', + storage: 'home' + } + }) + }) + }) + describe('should show the location picker confirm button', () => { + it('should be disabled if no current folder', () => { + const wrapper = getShallowWrapper() + + const confirmButton = wrapper.find(selectors.confirmButton) + + expect(confirmButton.exists()).toBeTruthy() + expect(confirmButton.attributes().disabled).toBe('true') + }) + it('should be disabled if current folder does not have "canCreate" property', () => { + const route = getRoute() + const store = createStore({ currentFolder: { canCreate: jest.fn(() => false) } }) + const wrapper = getShallowWrapper({ store: store, $route: route }) + + const confirmButton = wrapper.find(selectors.confirmButton) + + expect(confirmButton.exists()).toBeTruthy() + expect(confirmButton.attributes().disabled).toBe('true') + }) + it.each([ + { action: 'move', expectedText: 'Move here' }, + { action: 'copy', expectedText: 'Paste here' }, + { action: 'anything else', expectedText: 'Confirm' } + ])('should set button text according to current action', (input) => { + const wrapper = getShallowWrapper({ + store: createStore({ currentFolder: { canCreate: jest.fn(() => true) } }), + $route: getRoute({ action: input.action }) + }) + + const confirmButton = wrapper.find(selectors.confirmButton) + + expect(confirmButton.exists()).toBeTruthy() + expect(confirmButton.attributes().disabled).toBeUndefined() + expect(confirmButton.text()).toBe(input.expectedText) + }) + it('should call "confirmAction" method when clicked', async () => { + const spyConfirmAction = jest.spyOn(LocationPicker.methods, 'confirmAction') + const wrapper = getMountedWrapper({ + store: createStore({ currentFolder: { canCreate: jest.fn(() => true) } }) + }) + const confirmButton = wrapper.find(selectors.confirmButton) + + await confirmButton.trigger('click') + expect(spyConfirmAction).toHaveBeenCalledTimes(1) + }) + }) + }) + + describe('files view', () => { + describe('when the view is still loading', () => { + let filesView + beforeEach(() => { + const wrapper = getShallowWrapper({ loading: true }) + filesView = wrapper.find(selectors.filesView) + }) + it('should show list loader', () => { + expect(filesView.find(listLoaderStub).exists()).toBeTruthy() + expect(filesView.find(listLoaderStub)).toMatchSnapshot() + }) + it('should not show no content message', () => { + expect(filesView.find(selectors.noContentMessage).exists()).toBeFalsy() + }) + it('should not show no location picker table', () => { + expect(filesView.find(selectors.filesTable).exists()).toBeFalsy() + }) + }) + describe('when the view is not loading anymore', () => { + const wrapper = getShallowWrapper() + const filesView = wrapper.find(selectors.filesView) + it('should not show list loader', () => { + expect(filesView.find(listLoaderStub).exists()).toBeFalsy() + }) + it('should show no content message if active files length is less than one', () => { + expect(filesView.find(selectors.noContentMessage).exists()).toBeTruthy() + expect(filesView.find(selectors.noContentMessage)).toMatchSnapshot() + }) + describe('when active files length is one or more', () => { + const expectedResources = [ + { id: 100, name: 'file0.txt', type: 'file', path: '/home/file0.txt' } + ] + const store = createStore({ + totalFilesCount: { files: 2, folders: 0 }, + totalFilesSize: 0 + }) + const route = getRoute({ resource: '/home/file0.txt' }) + const wrapper = getShallowWrapper({ + store: store, + $route: route, + setup: { + paginatedResources: expectedResources + } + }) + const filesView = wrapper.find(selectors.filesView) + + it('should not show the no content message component', () => { + expect(filesView.find(selectors.noContentMessage).exists()).toBeFalsy() + }) + it('should show location picker table', () => { + expect(filesView.find(selectors.filesTable).exists()).toBeTruthy() + + const actualProps = filesView.find(selectors.filesTable).props() + expect(actualProps.resources).toMatchObject(expectedResources) + expect(actualProps.targetRoute).toMatchObject({ + name: route.name, + query: { + resource: [route.query.resource] + }, + params: { action: route.params.action } + }) + expect(actualProps.disabled).toMatchObject([expectedResources[0].id]) + }) + it.each([ + { + id: 100, + name: 'file0.txt', + type: 'file', + path: '/home/file0.txt', + expectedState: [100] + }, + { + id: 101, + name: 'some-resource', + type: 'file', + path: '/home/some-resource', + expectedState: [101] + }, + { + id: 102, + name: 'some-resource', + type: 'folder', + path: '/home/some-resource', + expectedState: [102] + }, + { + id: 103, + name: 'simple-folder', + type: 'folder', + path: '/simple-folder', + expectedState: [] + } + ])( + 'should set location item as disabled if not a folder or if its path is included in the active resource', + (input) => { + const route = getRoute({ resource: '/home/some-resource' }) + const wrapper = getShallowWrapper({ + data: { loading: false }, + $route: route, + setup: { + paginatedResources: [input] + } + }) + + const filesView = wrapper.find(selectors.filesView) + expect(filesView.find(selectors.filesTable).props().disabled).toMatchObject( + input.expectedState + ) + } + ) + + it('should show pagination if there is one or more pages', () => { + const expectedResources = [createFile({ id: 100 }), createFile({ id: 101 })] + const store = createStore({ + totalFilesCount: { files: 1000, folders: 1000 }, + totalFilesSize: 100000 + }) + const route = getRoute({ resource: '/home/some-resource' }) + const setup = { + paginatedResources: expectedResources, + paginationPages: 2, + paginationPage: 1 + } + const wrapper = getMountedWrapper({ + data: { loading: false }, + store: store, + $route: route, + setup: setup + }) + const filesView = wrapper.find(selectors.filesView) + const tablePagination = filesView.find(filesPaginationStub) + + expect(tablePagination.exists()).toBeTruthy() + expect(tablePagination.props()).toMatchObject({ + pages: setup.paginationPages, + currentPage: setup.paginationPage + }) + }) + + it('should load list info component with correct prop values', () => { + const expectedResources = [createFile({ id: 100 }), createFile({ id: 101 })] + const store = createStore({ + totalFilesCount: { files: 1, folders: 1 }, + totalFilesSize: 100 + }) + const route = getRoute({ resource: '/home/some-resource' }) + const wrapper = getMountedWrapper({ + data: { loading: false }, + store: store, + $route: route, + setup: { + paginatedResources: expectedResources + } + }) + const filesView = wrapper.find(selectors.filesView) + const listInfo = filesView.find(listInfoStub) + + expect(listInfo.exists()).toBeTruthy() + expect(listInfo.props()).toMatchObject({ + files: store.getters['Files/totalFilesCount'].files, + folders: store.getters['Files/totalFilesCount'].folders, + size: store.getters['Files/totalFilesSize'] + }) + }) + }) + }) + }) + + function getRoute({ + name = 'some-name', + path = '/some/path', + action = 'move', + context = null, + item = '/some/item', + resource = 'some-resource' + } = {}) { + return { + name: name, + path: path, + params: { + action: action, + item: item, + context: context + }, + query: { + resource: resource + } + } + } + + function createStore({ + publicLinkPassword = null, + currentFolder = null, + totalFilesCount = { files: null, folders: null }, + totalFilesSize = null, + generalThemeName = '' + } = {}) { + return getStore({ + publicLinkPassword: publicLinkPassword, + currentFolder: currentFolder, + totalFilesSize: totalFilesSize, + totalFilesCount: totalFilesCount, + generalThemeName: generalThemeName + }) + } + + function mountOptions(store, $route, loading, setup = {}, stubs = defaultStubs) { + return { + localVue, + store: store, + stubs, + mocks: { + $route, + $router: router + }, + setup: () => ({ + navigateToTargetTask: { + isRunning: loading, + perform: jest.fn() + }, + ...setup + }) + } + } + + function getShallowWrapper({ + store = createStore(), + $route = getRoute(), + loading = false, + setup + } = {}) { + return shallowMount(component, mountOptions(store, $route, loading, setup)) + } + + function getMountedWrapper({ + store = createStore(), + $route = getRoute(), + loading = false, + setup + } = {}) { + return mount( + component, + mountOptions(store, $route, loading, setup, { + 'oc-button': false, + 'list-info': true, + pagination: true, + RouterLink: RouterLinkStub + }) + ) + } +}) diff --git a/packages/web-app-files/tests/unit/views/__snapshots__/LocationPicker.spec.js.snap b/packages/web-app-files/tests/unit/views/__snapshots__/LocationPicker.spec.js.snap new file mode 100644 index 00000000000..44b15646ad6 --- /dev/null +++ b/packages/web-app-files/tests/unit/views/__snapshots__/LocationPicker.spec.js.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationPicker files app bar should show current hint according to the current route action 1`] = ` +

Navigate into the desired folder and copy selected resources into it. + You can navigate into a folder by clicking on its name. + To navigate back, you can click on the breadcrumbs. + Resources will be copied into the folder where you are currently located.

+`; + +exports[`LocationPicker files app bar should show current hint according to the current route action 2`] = ` +

Navigate into the desired folder and move selected resources into it. + You can navigate into a folder by clicking on its name. + To navigate back, you can click on the breadcrumbs. + Resources will be moved into the folder where you are currently located.

+`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 1`] = `

Move into »All files«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 2`] = `

Copy into »All files«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 3`] = `

Move into »child«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 4`] = `

Move into »parent«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 5`] = `

Copy into »child«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 6`] = `

Copy into »parent«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 7`] = `

Copy into »टेश्ट«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 8`] = `

Copy into »child«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 9`] = `

Copy into »child«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 10`] = `

Copy into »child«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 11`] = `

Copy into »टेश्ट फोल्डर«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 12`] = `

Copy into »456«

`; + +exports[`LocationPicker files app bar should show location picker selection info according to route action and item 13`] = `

Copy into »🔥«

`; + +exports[`LocationPicker files view when the view is not loading anymore should show no content message if active files length is less than one 1`] = ``; + +exports[`LocationPicker files view when the view is still loading should show list loader 1`] = ``; diff --git a/packages/web-app-files/tests/unit/views/views.setup.js b/packages/web-app-files/tests/unit/views/views.setup.js index df4ff54c8a9..82b342d941e 100644 --- a/packages/web-app-files/tests/unit/views/views.setup.js +++ b/packages/web-app-files/tests/unit/views/views.setup.js @@ -46,7 +46,6 @@ export const getRouter = ({ query = {} }) => ({ export const getStore = function ({ highlightedFile = null, disablePreviews = true, - currentPage = null, activeFiles = [], pages = null, @@ -61,7 +60,11 @@ export const getStore = function ({ davProperties = [], publicLinkPassword = null, slogan = null, - user = null + user = null, + generalThemeName = '', + isOcis = true, + selectedResourcesForMove = null, + locationPickerTargetFolder = null } = {}) { return createStore(Vuex.Store, { state: { @@ -77,6 +80,7 @@ export const getStore = function ({ login: loginLogo }, general: { + name: generalThemeName, slogan: slogan } }, @@ -85,7 +89,7 @@ export const getStore = function ({ } }), getToken: () => '', - isOcis: () => true, + isOcis: () => isOcis, homeFolder: () => '/', user: () => user }, @@ -101,7 +105,11 @@ export const getStore = function ({ resource: null, filesPageLimit: 100, files: [], - activeFiles: activeFiles + activeFiles: activeFiles, + currentFolder: currentFolder, + currentPage: currentPage, + selectedResourcesForMove: selectedResourcesForMove, + locationPickerTargetFolder: locationPickerTargetFolder }, getters: { totalFilesCount: () => totalFilesCount, @@ -133,7 +141,8 @@ export const getStore = function ({ SET_FILE_SELECTION: () => {} }, actions: { - loadIndicators: () => {} + loadIndicators: () => {}, + loadFiles: () => {} }, namespaced: true, modules: {