From 3f445217ddc49701913bb851953ab4376e46a49f Mon Sep 17 00:00:00 2001 From: Pascal Wengerter Date: Tue, 26 Apr 2022 12:23:50 +0200 Subject: [PATCH] Redesign link list components to include inline edit & event driven architecture --- .../bugfix-public-link-password-enforcement | 7 + .../enhancement-redesign-link-sharing | 5 + packages/web-app-files/src/App.vue | 2 +- .../SideBar/Shared/CopyToClipboardButton.vue | 3 +- .../components/SideBar/Shares/FileLinks.vue | 358 ++++++++--- .../SideBar/Shares/Links/CreateForm.vue | 247 ++++++++ .../SideBar/Shares/Links/DetailsAndEdit.vue | 480 +++++++++++++++ .../SideBar/Shares/Links/NameAndCopy.vue | 64 ++ .../Shares/PublicLinks/LinkActions.vue | 104 ---- .../SideBar/Shares/PublicLinks/LinkEdit.vue | 443 -------------- .../SideBar/Shares/PublicLinks/LinkInfo.vue | 178 ------ .../SideBar/Shares/PublicLinks/ListItem.vue | 47 -- .../web-app-files/src/helpers/resources.ts | 2 +- packages/web-app-files/src/store/mutations.js | 25 - packages/web-app-files/src/store/state.js | 11 - .../specs/linkExpirationDate.spec.js | 38 +- .../SideBar/Shares/FileLinks.spec.js | 74 +-- .../SideBar/Shares/Links/CreateForm.spec.js | 3 + .../Shares/Links/DetailsAndEdit.spec.js | 86 +++ .../SideBar/Shares/Links/NameAndCopy.spec.js | 36 ++ .../__snapshots__/DetailsAndEdit.spec.js.snap | 63 ++ .../__snapshots__/NameAndCopy.spec.js.snap | 14 + .../Shares/PublicLinks/LinkActions.spec.js | 147 ----- .../Shares/PublicLinks/LinkEdit.spec.js | 578 ------------------ .../Shares/PublicLinks/LinkInfo.spec.js | 235 ------- .../Shares/PublicLinks/ListItem.spec.js | 53 -- .../__snapshots__/LinkEdit.spec.js.snap | 5 - .../publicLinkCreate.feature | 48 +- .../publicLinkEdit.feature | 72 +-- .../shareByPublicLinkDifferentRoles.feature | 8 +- .../shareByPublicLinkExpiringLinks.feature | 13 +- .../FilesPageElement/publicLinksDialog.js | 219 ++++++- .../stepDefinitions/publicLinkContext.js | 55 +- .../support/objects/app-files/link/actions.ts | 2 +- 34 files changed, 1619 insertions(+), 2106 deletions(-) create mode 100644 changelog/unreleased/bugfix-public-link-password-enforcement create mode 100644 changelog/unreleased/enhancement-redesign-link-sharing create mode 100644 packages/web-app-files/src/components/SideBar/Shares/Links/CreateForm.vue create mode 100644 packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue create mode 100644 packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue delete mode 100644 packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkActions.vue delete mode 100644 packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkEdit.vue delete mode 100644 packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkInfo.vue delete mode 100644 packages/web-app-files/src/components/SideBar/Shares/PublicLinks/ListItem.vue create mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/Links/CreateForm.spec.js create mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.js create mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.js create mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/DetailsAndEdit.spec.js.snap create mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/NameAndCopy.spec.js.snap delete mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkActions.spec.js delete mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkEdit.spec.js delete mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkInfo.spec.js delete mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/ListItem.spec.js delete mode 100644 packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/__snapshots__/LinkEdit.spec.js.snap diff --git a/changelog/unreleased/bugfix-public-link-password-enforcement b/changelog/unreleased/bugfix-public-link-password-enforcement new file mode 100644 index 00000000000..22e39166c67 --- /dev/null +++ b/changelog/unreleased/bugfix-public-link-password-enforcement @@ -0,0 +1,7 @@ +Bugfix: Password enforcement for public links + +Password enforcement for public links, which can be adjusted on a per-role basis, wasn't properly reflected in the UI. +We have made the necessary adjustments to only enforce passwords for public links with the permissions that require a password according to the backend settings. + +https://github.com/owncloud/web/issues/6323 +https://github.com/owncloud/web/pull/6749 diff --git a/changelog/unreleased/enhancement-redesign-link-sharing b/changelog/unreleased/enhancement-redesign-link-sharing new file mode 100644 index 00000000000..503b7199567 --- /dev/null +++ b/changelog/unreleased/enhancement-redesign-link-sharing @@ -0,0 +1,5 @@ +Enhancement: Redesign link sharing + +We have redesigned the public link list in the right sidebar. Links now can be edited in-line and have a similiar look-and-feel to people and group shares. + +https://github.com/owncloud/web/pull/6749 diff --git a/packages/web-app-files/src/App.vue b/packages/web-app-files/src/App.vue index 57036b40034..fb8aae9e5fd 100644 --- a/packages/web-app-files/src/App.vue +++ b/packages/web-app-files/src/App.vue @@ -13,7 +13,7 @@ id="files-sidebar" ref="filesSidebar" tabindex="-1" - class="oc-width-1-1 oc-width-1-3@m oc-width-1-4@xl" + class="oc-width-1-1 oc-width-2-5@m oc-width-1-4@xl" :sidebar-active-panel="sidebarActivePanel" @beforeDestroy="focusSideBar" @mounted="focusSideBar" diff --git a/packages/web-app-files/src/components/SideBar/Shared/CopyToClipboardButton.vue b/packages/web-app-files/src/components/SideBar/Shared/CopyToClipboardButton.vue index b03f85d4c34..ba777983161 100644 --- a/packages/web-app-files/src/components/SideBar/Shared/CopyToClipboardButton.vue +++ b/packages/web-app-files/src/components/SideBar/Shared/CopyToClipboardButton.vue @@ -3,6 +3,7 @@ v-oc-tooltip="label" :aria-label="label" appearance="raw" + :variation="copied ? 'success' : 'passive'" @click="copyValueToClipboard" > @@ -84,7 +85,7 @@ export default { diff --git a/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue b/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue new file mode 100644 index 00000000000..bbe82bbc163 --- /dev/null +++ b/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkActions.vue b/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkActions.vue deleted file mode 100644 index 8af8c5c9e84..00000000000 --- a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkActions.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkEdit.vue b/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkEdit.vue deleted file mode 100644 index 010233b3d34..00000000000 --- a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkEdit.vue +++ /dev/null @@ -1,443 +0,0 @@ - - diff --git a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkInfo.vue b/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkInfo.vue deleted file mode 100644 index 0a48d23eed6..00000000000 --- a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/LinkInfo.vue +++ /dev/null @@ -1,178 +0,0 @@ - - - diff --git a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/ListItem.vue deleted file mode 100644 index 569f25da1bd..00000000000 --- a/packages/web-app-files/src/components/SideBar/Shares/PublicLinks/ListItem.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/packages/web-app-files/src/helpers/resources.ts b/packages/web-app-files/src/helpers/resources.ts index 43b08345a1e..dc9eb028e2c 100644 --- a/packages/web-app-files/src/helpers/resources.ts +++ b/packages/web-app-files/src/helpers/resources.ts @@ -442,7 +442,7 @@ function _buildLink(link): Share { permissions: link.permissions, description, stime: link.stime, - name: typeof link.name === 'string' ? link.name : '', + name: typeof link.name === 'string' ? link.name : (link.token as string), password: !!(link.share_with && link.share_with_displayname), expiration: typeof link.expiration === 'string' diff --git a/packages/web-app-files/src/store/mutations.js b/packages/web-app-files/src/store/mutations.js index 2396e82beef..af49a67a8fc 100644 --- a/packages/web-app-files/src/store/mutations.js +++ b/packages/web-app-files/src/store/mutations.js @@ -1,6 +1,5 @@ import Vue from 'vue' import pickBy from 'lodash-es/pickBy' -import { DateTime } from 'luxon' import { set, has } from 'lodash-es' import { getIndicators } from '../helpers/statusIndicators' import { renameResource } from '../helpers/resources' @@ -243,30 +242,6 @@ export default { state.versions = versions }, - TRIGGER_PUBLIC_LINK_EDIT(state, link) { - // Adjust link for the edit - link = { - id: link.id, - name: link.name, - permissions: parseInt(link.permissions, 10), - hasPassword: link.password, - expireDate: - link.expiration !== null ? DateTime.fromISO(link.expiration).endOf('day').toISO() : null - } - - state.publicLinkInEdit = link - }, - - TRIGGER_PUBLIC_LINK_CREATE(state, { name, expireDate }) { - state.publicLinkInEdit = { - id: null, - name, - permissions: 1, - hasPassword: false, - expireDate - } - }, - LOAD_INDICATORS(state) { for (const resource of state.files) { const indicators = getIndicators(resource, state.sharesTree) diff --git a/packages/web-app-files/src/store/state.js b/packages/web-app-files/src/store/state.js index 2e33b8588c3..69fc5bb5393 100644 --- a/packages/web-app-files/src/store/state.js +++ b/packages/web-app-files/src/store/state.js @@ -34,17 +34,6 @@ export default { uploaded: [], actionsInProgress: [], - /** - * Public links - */ - publicLinkInEdit: { - id: null, - name: '', - permissions: 1, - hasPassword: false, - expireDate: null - }, - /** * View settings */ diff --git a/packages/web-app-files/tests/integration/specs/linkExpirationDate.spec.js b/packages/web-app-files/tests/integration/specs/linkExpirationDate.spec.js index a6999fc5058..3804e7e133d 100644 --- a/packages/web-app-files/tests/integration/specs/linkExpirationDate.spec.js +++ b/packages/web-app-files/tests/integration/specs/linkExpirationDate.spec.js @@ -51,7 +51,7 @@ const existingShares = [ share_type: 3, uid_owner: 'alice', displayname_owner: 'alice', - permissions: 16, + permissions: '1', stime: new Date().getTime(), expiration: DateTime.fromJSDate(getDateInFuture(2)).toFormat('yyyy-MM-dd HH:mm:ss'), uid_file_owner: 'alice', @@ -79,7 +79,7 @@ describe('Users can set expiration date when sharing via public link', () => { }) test('user can set a new expiration date', async () => { const component = renderComponent() - const { findByTestId, baseElement, getByTestId, findByText, queryByTestId } = component + const { findByTestId, baseElement, getByTestId, queryByTestId } = component const addBtn = await findByTestId('files-link-add-btn') expect(addBtn).toBeVisible() @@ -93,8 +93,6 @@ describe('Users can set expiration date when sharing via public link', () => { const newDate = getDateInFuture(2) await navigateToDate(newDate, component) - expect(await findByText('Expires in 2 days')).toBeVisible() - const shareBtn = getByTestId('new-files-link-btn') expect(shareBtn).toBeVisible() await fireEvent.click(shareBtn) @@ -106,7 +104,9 @@ describe('Users can set expiration date when sharing via public link', () => { expect(link).toBeVisible() expect( - within(getByTestId('files-link-id-1-expiration-date')).getByText('Expires in 2 days') + within(getByTestId('files-link-id-1')).getByLabelText('Expires in in 2 days', { + exact: false + }) ).toBeVisible() }) @@ -122,7 +122,7 @@ describe('Users can set expiration date when sharing via public link', () => { } } }) - const { findByTestId, getByTestId, findByText, queryByTestId } = component + const { findByTestId, getByTestId } = component const link = await findByTestId('files-link-id-1') expect(link).toBeVisible() @@ -131,23 +131,17 @@ describe('Users can set expiration date when sharing via public link', () => { expect(editBtn).toBeVisible() await fireEvent.click(editBtn) - expect(getByTestId('recipient-datepicker')).toBeVisible() - await fireEvent.click(getByTestId('recipient-datepicker-btn')) + const editExpiryDateBtn = getByTestId('files-link-id-1-edit-edit-expiration') + expect(editExpiryDateBtn).toBeVisible() + await fireEvent.click(editExpiryDateBtn) const newDate = getDateInFuture(4) await navigateToDate(newDate, component) - expect(await findByText('Expires in 4 days')).toBeVisible() - - const shareBtn = getByTestId('save-files-link-btn') - expect(shareBtn).toBeVisible() - await fireEvent.click(shareBtn) - await waitFor(() => { - return expect(queryByTestId('files-link-being-saved')).toBe(null) - }) - expect( - within(getByTestId('files-link-id-1-expiration-date')).getByText('Expires in 4 days') + within(getByTestId('files-link-id-1')).getByLabelText('Expires in in 4 days', { + exact: false + }) ).toBeVisible() }) @@ -172,15 +166,11 @@ describe('Users can set expiration date when sharing via public link', () => { expect(editBtn).toBeVisible() await fireEvent.click(editBtn) - const removeExpiryDateBtn = getByTestId('files-link-remove-expiration-date') + const removeExpiryDateBtn = getByTestId('files-link-id-1-edit-remove-expiration') expect(removeExpiryDateBtn).toBeVisible() await fireEvent.click(removeExpiryDateBtn) - - const shareBtn = getByTestId('save-files-link-btn') - expect(shareBtn).toBeVisible() - await fireEvent.click(shareBtn) await waitFor(() => { - return expect(queryByTestId('files-link-being-saved')).toBe(null) + return expect(queryByTestId('files-link-id-1-edit-remove-expiration')).toBe(null) }) expect(within(link).queryByTestId('files-link-id-1-expiration-date')).toBe(null) diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js index 507bc1ed260..c8096ee8445 100644 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js @@ -29,14 +29,16 @@ const defaultLinksList = [ indirect: false, name: 'public link 1', url: 'some-link-1', - path: '/file-1.txt' + path: '/file-1.txt', + permissions: '1' }, { id: '2', indirect: true, name: 'public link 2', url: 'some-link-2', - path: '/file-2.txt' + path: '/file-2.txt', + permissions: '1' } ] @@ -47,8 +49,8 @@ const selectors = { linkPrivate: '.oc-files-private-link-item' } -const listItemStubSelector = 'list-item-stub' -const linkEditStubSelector = 'link-edit-stub' +const linkListItemNameAndCopy = 'name-and-copy-stub' +const linkListItemDetailsAndEdit = 'details-and-edit-stub' const ocLoaderStubSelector = 'oc-loader-stub' describe('FileLinks', () => { @@ -58,9 +60,12 @@ describe('FileLinks', () => { const wrapper = getShallowWrapper(store) it('should render a list of links', () => { - const linkListItems = wrapper.findAll(listItemStubSelector) + const linkListItems = wrapper.findAll(linkListItemNameAndCopy) + const linkListItemsDetails = wrapper.findAll(linkListItemDetailsAndEdit) expect(linkListItems.length).toBe(2) + expect(linkListItemsDetails.length).toBe(2) + expect(linkListItems.at(0).props().link).toMatchObject({ id: '1', indirect: false, @@ -80,11 +85,9 @@ describe('FileLinks', () => { }) }) - it('should show the "no results" message if no links are provided', () => { + it('should not render link list if no links are provided', () => { const wrapper = getShallowWrapper(createStore({ links: [] })) - - expect(wrapper.find(selectors.linkNoResults).exists()).toBeTruthy() - expect(wrapper.find(selectors.linkNoResults).text()).toBe('No public links') + expect(wrapper.find('oc-list-stub').exists()).toBeFalsy() }) }) describe('when linksLoading is set to true', () => { @@ -117,31 +120,18 @@ describe('FileLinks', () => { describe('when the add-new-link button is clicked', () => { let wrapper const spyAddNewLink = jest.spyOn(FileLinks.methods, 'addNewLink') - const spyLinkCreateTrigger = jest.spyOn(mapMutations, 'TRIGGER_PUBLIC_LINK_CREATE') beforeEach(() => { - const store = createStore() + const store = createStore({ links: [] }) wrapper = getMountedWrapper(store) }) it('should call addNewLink', async () => { expect(spyAddNewLink).toHaveBeenCalledTimes(0) - expect(spyLinkCreateTrigger).toHaveBeenCalledTimes(0) await wrapper.find(selectors.linkAddButton).trigger('click') expect(spyAddNewLink).toHaveBeenCalledTimes(1) - expect(spyLinkCreateTrigger).toHaveBeenCalledTimes(1) - }) - - it('should show link edit component', async () => { - expect(wrapper.find(linkEditStubSelector).exists()).toBeFalsy() - expect(wrapper.vm.currentView).toBe('showLinks') - - await wrapper.find(selectors.linkAddButton).trigger('click') - - expect(wrapper.vm.currentView).toBe('editPublicLink') - expect(wrapper.find(linkEditStubSelector).exists()).toBeTruthy() }) }) }) @@ -161,16 +151,6 @@ describe('FileLinks', () => { expect(wrapper.find(selectors.noResharePermissions).exists()).toBeTruthy() }) }) - - describe('when the private link functionality is disabled', () => { - it('private link should not be visible', () => { - const store = createStore() - store.getters.capabilities.files.privateLinks = false - const wrapper = getShallowWrapper(store) - - expect(wrapper.find(selectors.linkPrivate).exists()).toBeFalsy() - }) - }) }) function createStore({ @@ -185,9 +165,8 @@ describe('FileLinks', () => { expireDate = { enabled: true, days: 1, - enforced: '1' - }, - privateLinks = false + enforced: false + } } = {}) { return new Vuex.Store({ getters: { @@ -204,13 +183,17 @@ describe('FileLinks', () => { })), capabilities: jest.fn(() => { return { - files: { - privateLinks: privateLinks - }, files_sharing: { public: { defaultPublicLinkShareName: 'public link name default', - expire_date: expireDate + expire_date: expireDate, + password: { + enforced_for: { + read_only: false, + upload_only: false, + read_write: false + } + } } } } @@ -244,9 +227,6 @@ describe('FileLinks', () => { localVue, store: store, stubs: stubs, - provide: { - changeView: jest.fn() - }, mocks: { $route: { params: {} @@ -259,13 +239,7 @@ describe('FileLinks', () => { return mount(FileLinks, { localVue, store: store, - stubs: { - 'link-edit': true, - 'list-item': true - }, - provide: { - changeView: jest.fn() - }, + stubs: {}, mocks: { $route: { params: {} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/CreateForm.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/CreateForm.spec.js new file mode 100644 index 00000000000..804443aa4c6 --- /dev/null +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/CreateForm.spec.js @@ -0,0 +1,3 @@ +describe('CreateForm', () => { + it.todo('does not receive tests since this component will be deleted within few days') +}) diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.js new file mode 100644 index 00000000000..13db93bad86 --- /dev/null +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/DetailsAndEdit.spec.js @@ -0,0 +1,86 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils' +import GetTextPlugin from 'vue-gettext' +import Vuex from 'vuex' +import DesignSystem from 'owncloud-design-system' +import stubs from '@/tests/unit/stubs' +import DetailsAndEdit from '@files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue' +import { LinkShareRoles } from '@files/src/helpers/share' + +const localVue = createLocalVue() +localVue.use(DesignSystem) +localVue.use(Vuex) +localVue.use(GetTextPlugin, { + translations: 'does-not-matter.json', + silent: true +}) + +const availableRoleOptions = LinkShareRoles.list(false, true).map((role) => { + return { + role, + name: role.name, + label: role.label + } +}) + +const exampleLink = { + name: 'Example link', + url: 'https://some-url.com/abc', + permissions: '1' +} + +describe('DetailsAndEdit component', () => { + describe('if user can not edit', () => { + it('does not render dropdown or edit button', () => { + const wrapper = getShallowMountedWrapper(exampleLink) + expect(wrapper).toMatchSnapshot() + }) + }) + + describe('if user can edit', () => { + it('renders dropdown and edit button', () => { + const wrapper = getShallowMountedWrapper(exampleLink, false, true) + expect(wrapper).toMatchSnapshot() + }) + + it.todo('test edit options, button clicks and event handling/propagation') + }) +}) + +function getShallowMountedWrapper(link, expireDateEnforced = false, modifiable = false) { + return shallowMount(DetailsAndEdit, { + propsData: { + availableRoleOptions, + expirationDate: { + enforced: expireDateEnforced, + default: null, + min: 'Wed Apr 01 2020 00:00:00 GMT+0000 (Coordinated Universal Time)', + max: null + }, + link, + modifiable, + passwordEnforced: { + read_only: false, + upload_only: false, + read_write: false + } + }, + store: createStore(), + directives: { + 'oc-tooltip': jest.fn() + }, + stubs: { + ...stubs, + 'oc-datepicker': true + } + }) +} + +function createStore() { + return new Vuex.Store({ + actions: { + showMessage: jest.fn(), + createModal: jest.fn(), + hideModal: jest.fn() + } + }) +} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.js new file mode 100644 index 00000000000..5f0aa0b63bf --- /dev/null +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.js @@ -0,0 +1,36 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils' +import GetTextPlugin from 'vue-gettext' +import DesignSystem from 'owncloud-design-system' +import NameAndCopy from '@files/src/components/SideBar/Shares/Links/NameAndCopy.vue' + +const localVue = createLocalVue() +localVue.use(DesignSystem) +localVue.use(GetTextPlugin, { + translations: 'does-not-matter.json', + silent: true +}) + +const exampleLink = { + name: 'Example link', + url: 'https://some-url.com/abc' +} + +describe('NameAndCopy', () => { + it('should show link info component', () => { + const wrapper = getShallowWrapper() + expect(wrapper).toMatchSnapshot() + }) +}) + +function getShallowWrapper() { + return shallowMount(NameAndCopy, { + localVue, + propsData: { + link: exampleLink + }, + stubs: { + 'oc-icon': true, + 'copy-to-clipboard-button': true + } + }) +} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/DetailsAndEdit.spec.js.snap b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/DetailsAndEdit.spec.js.snap new file mode 100644 index 00000000000..ae1ce232e64 --- /dev/null +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/DetailsAndEdit.spec.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DetailsAndEdit component if user can edit renders dropdown and edit button 1`] = ` + +`; + +exports[`DetailsAndEdit component if user can not edit does not render dropdown or edit button 1`] = ` + +`; diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/NameAndCopy.spec.js.snap b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/NameAndCopy.spec.js.snap new file mode 100644 index 00000000000..5f5df96b485 --- /dev/null +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/__snapshots__/NameAndCopy.spec.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NameAndCopy should show link info component 1`] = ` +
+ + +
+`; diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkActions.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkActions.spec.js deleted file mode 100644 index 50725deffb1..00000000000 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkActions.spec.js +++ /dev/null @@ -1,147 +0,0 @@ -import LinkActions from '@files/src/components/SideBar/Shares/PublicLinks/LinkActions.vue' -import { createLocalVue, mount, shallowMount } from '@vue/test-utils' -import GetTextPlugin from 'vue-gettext' -import Vuex from 'vuex' -import DesignSystem from 'owncloud-design-system' - -const localVue = createLocalVue() -localVue.use(DesignSystem) -localVue.use(Vuex) -localVue.use(GetTextPlugin, { - translations: 'does-not-matter.json', - silent: true -}) - -const storeOptions = { - modules: { - Files: { - namespaced: true, - actions: { - removeLink: jest.fn() - }, - mutations: { - TRIGGER_PUBLIC_LINK_EDIT: jest.fn() - } - } - }, - actions: { - showMessage: jest.fn(), - createModal: jest.fn(), - hideModal: jest.fn() - } -} - -const selectors = { - editActionButton: '.oc-files-file-link-edit', - deleteActionButton: '.oc-files-file-link-delete' -} - -describe('LinkActions', () => { - describe('removal in progress', () => { - it('should render spinner if true', async () => { - const wrapper = getShallowMountedWrapper() - await wrapper.setData({ removalInProgress: true }) - const spinner = wrapper.find('oc-spinner-stub') - - expect(spinner.exists()).toBeTruthy() - expect(spinner.attributes('aria-label')).toBe('Removing public link') - - const actionLinks = wrapper.findAll('oc-button-stub') - expect(actionLinks.length).toBe(0) - }) - - it('should not render spinner if false', async () => { - const wrapper = getShallowMountedWrapper() - await wrapper.setData({ removalInProgress: false }) - const spinner = wrapper.find('oc-spinner-stub') - expect(spinner.exists()).toBeFalsy() - - const actionLinks = wrapper.findAll('oc-button-stub') - expect(actionLinks.length).toBe(2) - }) - }) - - describe('action buttons label', () => { - it('should set edit link button label', () => { - const wrapper = getShallowMountedWrapper() - const editLinkButton = wrapper.find(selectors.editActionButton) - - expect(editLinkButton.attributes('aria-label')).toBe('Edit public link') - }) - - it('should set remove link button label', () => { - const wrapper = getShallowMountedWrapper() - const editLinkButton = wrapper.find(selectors.deleteActionButton) - - expect(editLinkButton.attributes('aria-label')).toBe('Delete public link') - }) - }) - - describe('link buttons function calls', () => { - const editLinkSpy = jest.spyOn(LinkActions.methods, 'editLink') - const removeLinkSpy = jest.spyOn(LinkActions.methods, '$_removeLink') - const triggerPublicLinkEditSpy = jest.spyOn( - storeOptions.modules.Files.mutations, - 'TRIGGER_PUBLIC_LINK_EDIT' - ) - const wrapper = getMountedWrapper() - - it('should call "editLink" if edit link button is clicked', async () => { - const wrapper = getMountedWrapper() - const editButton = wrapper.find(selectors.editActionButton) - await editButton.trigger('click') - - expect(editLinkSpy).toHaveBeenCalledTimes(1) - expect(triggerPublicLinkEditSpy).toHaveBeenCalledTimes(1) - }) - - it('should call "$_removeLink" if delete link button is clicked', async () => { - const deleteButton = wrapper.find(selectors.deleteActionButton) - await deleteButton.trigger('click') - - expect(removeLinkSpy).toHaveBeenCalledTimes(1) - }) - }) -}) - -function getMountedWrapper() { - return mount(LinkActions, { - localVue, - propsData: { - link: {} - }, - provide: { - changeView: jest.fn() - }, - store: createStore(), - stubs: { - 'oc-button': false, - 'oc-icon': true, - 'oc-spinner': true - } - }) -} - -function getShallowMountedWrapper() { - return shallowMount(LinkActions, { - propsData: { - link: {} - }, - store: createStore(), - provide: { - changeView: jest.fn() - }, - directives: { - 'oc-tooltip': jest.fn() - }, - stubs: { - 'oc-button': true, - 'oc-icon': true, - 'oc-spinner': true - } - }) -} - -function createStore() { - return new Vuex.Store(storeOptions) -} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkEdit.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkEdit.spec.js deleted file mode 100644 index 6fd1fef898c..00000000000 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkEdit.spec.js +++ /dev/null @@ -1,578 +0,0 @@ -import Vuex from 'vuex' -import { DateTime } from 'luxon' -import VueSelect from 'vue-select' -import GetTextPlugin from 'vue-gettext' -import stubs from '@/tests/unit/stubs/index.js' -import DesignSystem from 'owncloud-design-system' -import { createLocalVue, mount, shallowMount } from '@vue/test-utils' -import LinkEdit from '@files/src/components/SideBar/Shares/PublicLinks/LinkEdit.vue' -import { LinkShareRoles } from '../../../../../../src/helpers/share' - -const selectors = { - linkNameInput: '#oc-files-file-link-name', - linkErrorAlert: '.oc-files-file-link-error-alert', - linkExpireDatePicker: '#oc-files-file-link-expire-date', - linkExpireDateDeleteButton: '#oc-files-file-link-expire-date-delete', - linkPasswordField: '#oc-files-file-link-password', - linkPasswordDeleteButton: '#oc-files-file-link-password-delete', - linkCancelButton: '#oc-files-file-link-cancel', - linkCreateButton: '#oc-files-file-link-create', - linkSaveButton: '#oc-files-file-link-save', - linkSavingButton: '#oc-files-file-link-saving' -} - -const ocSpinnerStubSelector = 'oc-spinner-stub' - -const localVue = createLocalVue() -localVue.use(DesignSystem) -localVue.use(VueSelect) -localVue.use(Vuex) -localVue.use(GetTextPlugin, { - translations: 'does-not-matter.json', - silent: true -}) - -const mapActions = { - addLink: jest.fn(), - updateLink: jest.fn() -} - -describe('LinkEdit', () => { - beforeEach(() => { - jest.useFakeTimers('modern') - jest.setSystemTime(new Date(2020, 3, 1)) - }) - - afterEach(() => { - jest.useRealTimers() - }) - - describe('expiration date picker field', () => { - describe('required field label', () => {}) - describe('when the link expiration date is enforced', () => { - it('should have max-datetime attribute with tomorrow datetime value', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - publicLinkCapabilities: getLinkCapabilities({ - enforcedExpireDate: true, - days: 2 - }) - }) - ) - const expirationDatePickerFieldElement = wrapper.find(selectors.linkExpireDatePicker) - const expectedDate = new Date() - expectedDate.setDate(new Date().getDate() + 2) - - expect(expirationDatePickerFieldElement.attributes()['max-date']).toEqual( - expectedDate.toString() - ) - }) - }) - - it('should have min-datetime attribute with the value now', () => { - const wrapper = getShallowMountedWrapper() - const expirationDatePickerElement = wrapper.find('#oc-files-file-link-expire-date') - const expectedDate = new Date() - expect(expirationDatePickerElement.attributes()['min-date']).toEqual(expectedDate.toString()) - }) - - it('should be pre populated if the public link has already an expiration date set', () => { - const expectedDate = new Date() - expectedDate.setDate(expectedDate.getDate() + 1) - - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { - id: 1, - expireDate: expectedDate.toString() - }, - publicLinkCapabilities: getLinkCapabilities({ enabledExpireDate: true }) - }) - ) - const expirationDatePickerFieldElement = wrapper.find(selectors.linkExpireDatePicker) - - expect(expirationDatePickerFieldElement.attributes().value).toEqual(expectedDate.toString()) - }) - }) - - describe('expiration date delete button', () => { - it('should be present if the expiration date is not enforced and the link has expiration date set', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { - expireDate: DateTime.now().plus({ days: 1 }).toString() - } - }) - ) - const expirationDateDeleteButtonElement = wrapper.find(selectors.linkExpireDateDeleteButton) - - expect(expirationDateDeleteButtonElement.exists()).toBeTruthy() - }) - - it('should set expire date to empty string if clicked', async () => { - const expireDate = new Date() - expireDate.setDate(new Date().getDate() + 1) - - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: { - expireDate - } - }) - ) - const expirationDateDeleteButtonElement = wrapper.find(selectors.linkExpireDateDeleteButton) - await expirationDateDeleteButtonElement.trigger('click') - - expect(wrapper.vm.expireDate).toEqual(null) - }) - - it('should not be present if expiration date is not enforced and the link in edit does not have an expiration date set', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { expireDate: null }, - publicLinkCapabilities: getLinkCapabilities({ - days: null - }) - }) - ) - const expirationDateDeleteButtonElement = wrapper.find(selectors.linkExpireDateDeleteButton) - - expect(expirationDateDeleteButtonElement.exists()).toBeFalsy() - }) - - it('should not be present if expiration date is enforced', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - publicLinkCapabilities: getLinkCapabilities({ enforcedExpireDate: true }) - }) - ) - const expirationDateDeleteButtonElement = wrapper.find(selectors.linkExpireDateDeleteButton) - - expect(expirationDateDeleteButtonElement.exists()).toBeFalsy() - }) - }) - - describe('role select field', () => { - it.each(['folder', 'file'])( - 'should list all available link sharing roles according to resource type %s', - (type) => { - const wrapper = getMountedWrapper(createStore({ type })) - const roleSelectElement = wrapper.findComponent(VueSelect) - const actualOptions = roleSelectElement.props().options - - const linkShareRoles = LinkShareRoles.list(type === 'folder') - expect(actualOptions.length).toBe(linkShareRoles.length) - for (let i = 0; i < linkShareRoles.length; i++) { - expect(actualOptions[i].name).toBe(linkShareRoles[i].name) - } - } - ) - - it('should not be clearable', () => { - const wrapper = getMountedWrapper() - const roleSelectElement = wrapper.findComponent(VueSelect) - - expect(roleSelectElement.props().clearable).toBeFalsy() - }) - - it('should set selected role when input is triggered', () => { - const wrapper = getMountedWrapper(createStore({ type: 'folder' })) - const roleSelectElement = wrapper.findComponent(VueSelect) - - const option = roleSelectElement.vm.options[2] - roleSelectElement.vm.select(option) - expect(wrapper.vm.selectedRole.name).toBe(option.name) - }) - }) - - describe('password field', () => { - it('should have required label if password is enforced', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - publicLinkCapabilities: getLinkCapabilities({ - passwordEnforcedFor: { - read_only: '1' - } - }) - }) - ) - const passwordFieldElement = wrapper.find(selectors.linkPasswordField) - - expect(passwordFieldElement).toMatchSnapshot() - }) - - it('should not have required label if password is not enforced', () => { - const wrapper = getShallowMountedWrapper() - const passwordFieldElement = wrapper.find(selectors.linkPasswordField) - - expect(passwordFieldElement).toMatchSnapshot() - }) - }) - - describe('link password delete button', () => { - it('should be present if password is not enforced and the link has a password set', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { - hasPassword: true - } - }) - ) - const passwordDeleteButtonElement = wrapper.find(selectors.linkPasswordDeleteButton) - - expect(passwordDeleteButtonElement.exists()).toBeTruthy() - }) - - it('should not be present if password is not enforced', () => { - const wrapper = getShallowMountedWrapper() - const passwordDeleteButtonElement = wrapper.find(selectors.linkPasswordDeleteButton) - - expect(passwordDeleteButtonElement.exists()).toBeFalsy() - }) - it('should not be present if the link does not has a password set', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { - hasPassword: false - } - }) - ) - const passwordDeleteButtonElement = wrapper.find(selectors.linkPasswordDeleteButton) - - expect(passwordDeleteButtonElement.exists()).toBeFalsy() - }) - - it('should be present if password is not enforced and password value is set', async () => { - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: {} - }) - ) - const linkPasswordInputElement = wrapper.find(selectors.linkPasswordField) - await linkPasswordInputElement.setValue('VeryStrongPassword') - - const passwordDeleteButtonElement = wrapper.find(selectors.linkPasswordDeleteButton) - - expect(passwordDeleteButtonElement.exists()).toBeTruthy() - }) - - it('should remove password if clicked', async () => { - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: {} - }) - ) - const linkPasswordInputElement = wrapper.find(selectors.linkPasswordField) - await linkPasswordInputElement.setValue('VeryStrongPassword') - - const passwordDeleteButtonElement = wrapper.find(selectors.linkPasswordDeleteButton) - await passwordDeleteButtonElement.trigger('click') - - expect(wrapper.vm.password).toBe('') - expect(wrapper.vm.hasPassword).toBeFalsy() - }) - }) - - describe('link edit form validation', () => { - it('should disable the link save button if the expiration date value is not valid', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { - id: 1224, - name: 'Public Link', - hasPassword: true, - // for an enforced expireDate, empty expireDate is an invalid value - expireDate: '' - }, - publicLinkCapabilities: getLinkCapabilities({ enforcedExpireDate: true }) - }), - { - saving: false - } - ) - - expect(wrapper.find(selectors.linkSaveButton).attributes('disabled')).toBe('true') - }) - - it('should disable the link save button if the password field is invalid', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { id: 1224, name: 'Public Link' }, - publicLinkCapabilities: getLinkCapabilities({ - passwordEnforcedFor: { - read_only: '1' - } - }) - }), - { saving: false } - ) - - expect(wrapper.find(selectors.linkSaveButton).attributes('disabled')).toBe('true') - }) - - it('should enable link save button if the expiration and password fields are valid', () => { - const expireDate = new Date() - expireDate.setDate(new Date().getDate() + 1) - - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: { - id: 1224, - name: 'Public Link', - hasPassword: true, - expireDate - }, - publicLinkCapabilities: getLinkCapabilities({ enforcedExpireDate: true }) - }), - { - saving: false - } - ) - - expect(wrapper.find(selectors.linkSaveButton).attributes('disabled')).toBeUndefined() - }) - }) - - describe('grid', () => { - it('should disable the cancel link button if saving is set to true', () => { - const wrapper = getShallowMountedWrapper(createStore(), { saving: true }) - - const cancelLinkButtonElement = wrapper.find(selectors.linkCancelButton) - - expect(cancelLinkButtonElement.attributes('disabled')).toBe('true') - }) - describe('saving button', () => { - describe('when saving is set to true', () => { - it('should show the text "Creating" if a new link is being created', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { name: 'Public Link' } - }), - { saving: true } - ) - const savingButtonElement = wrapper.find(selectors.linkSavingButton) - - expect(savingButtonElement.exists()).toBeTruthy() - expect(savingButtonElement.text()).toBe('Creating') - expect(savingButtonElement.find('oc-spinner-stub').attributes('arialabel')).toBe( - 'Creating Public Link' - ) - }) - it('should show the text "Saving" during the update process', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { id: 1223, name: 'Public Link' } - }), - { saving: true } - ) - const savingButtonElement = wrapper.find(selectors.linkSavingButton) - - expect(savingButtonElement.exists()).toBeTruthy() - expect(savingButtonElement.text()).toBe('Saving') - expect(savingButtonElement.find(ocSpinnerStubSelector).attributes('arialabel')).toBe( - 'Saving Public Link' - ) - }) - }) - describe('when saving is set to false', () => { - describe('when new link is being created', () => { - it('should show the link create button', () => { - const wrapper = getShallowMountedWrapper(createStore(), { saving: false }) - const linkCreateButton = wrapper.find(selectors.linkCreateButton) - expect(linkCreateButton.exists()).toBeTruthy() - expect(linkCreateButton.attributes('disabled')).toBeFalsy() - }) - it('should trigger "addLink" function if clicked', async () => { - const addLinkSpy = jest.spyOn(mapActions, 'addLink') - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: { name: 'Public Link', hasPassword: true }, - publicLinkCapabilities: getLinkCapabilities({ enforcedExpireDate: true }) - }), - { saving: false } - ) - const linkCreateButton = wrapper.find(selectors.linkCreateButton) - expect(linkCreateButton.attributes('disabled')).toBeFalsy() - - await linkCreateButton.trigger('click') - expect(wrapper.vm.saving).toBeTruthy() - expect(addLinkSpy).toHaveBeenCalledTimes(1) - }) - }) - describe('when existing link is being updated', () => { - it('should show the link save button', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { id: 1224, name: 'Public Link' } - }), - { saving: false } - ) - - const linkSaveButton = wrapper.find(selectors.linkSaveButton) - expect(linkSaveButton.exists()).toBeTruthy() - }) - it('should set the link save button as disabled if form is not valid', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { id: 1224, name: 'Public Link' }, - // invalid password field - // by default viewer role is selected with permission 1 (viewer) - // combined with enforced for password is enforce for link form - // since link password is enforced and password is not set, form will be invalid - publicLinkCapabilities: getLinkCapabilities({ - passwordEnforcedFor: { - read_only: '1' - } - }) - }), - { saving: false } - ) - const linkSaveButton = wrapper.find(selectors.linkSaveButton) - - expect(linkSaveButton.attributes('disabled')).toBeTruthy() - }) - it('should set the link save button as disabled if the form does not have any changes', () => { - const wrapper = getShallowMountedWrapper( - createStore({ - linkInEdit: { id: 1224, name: 'Public Link', hasPassword: false, permissions: 1 }, - publicLinkCapabilities: getLinkCapabilities({ enforcedExpireDate: true }) - }), - { saving: false } - ) - const linkSaveButton = wrapper.find(selectors.linkSaveButton) - - expect(linkSaveButton.attributes('disabled')).toBe('true') - }) - - describe('when the form is valid and has some changes', () => { - const updateLinkSpy = jest.spyOn(mapActions, 'updateLink') - - it('should trigger "updateLink" method if clicked', async () => { - const wrapper = getMountedWrapper( - createStore({ - linkInEdit: { id: 1224, name: 'Public Link', hasPassword: true } - }), - { - saving: false - } - ) - - // make some changes in the form - const nameInput = wrapper.find(selectors.linkNameInput) - await nameInput.setValue('Link changed') - const linkSaveButton = wrapper.find(selectors.linkSaveButton) - - expect(linkSaveButton.attributes('disabled')).toBeUndefined() - expect(wrapper.vm.saving).toBeFalsy() - expect(updateLinkSpy).not.toHaveBeenCalled() - - await linkSaveButton.trigger('click') - - expect(wrapper.vm.saving).toBeTruthy() - expect(linkSaveButton.attributes('disabled')).toBe('disabled') - expect(updateLinkSpy).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - }) - }) -}) - -function createStore({ - linkInEdit = {}, - publicLinkCapabilities = getLinkCapabilities(), - type = 'files' -} = {}) { - return new Vuex.Store({ - modules: { - Files: { - namespaced: true, - state: { - publicLinkInEdit: linkInEdit - }, - getters: { - highlightedFile: function () { - return { type: type, isFolder: type === 'folder' } - } - }, - actions: mapActions - } - }, - getters: { - getToken: jest.fn(), - capabilities: function () { - return { - files_sharing: { - public: publicLinkCapabilities - } - } - } - } - }) -} - -function getLinkCapabilities({ - enforcedExpireDate = false, - days = 1, - enabledExpireDate = false, - passwordEnforcedFor = false -} = {}) { - return { - expire_date: { - enabled: enabledExpireDate, - days: days, - enforced: enforcedExpireDate - }, - password: { - enforced_for: passwordEnforcedFor - } - } -} - -function getShallowMountedWrapper(store = createStore(), data = {}) { - const wrapper = shallowMount(LinkEdit, { - ...mountOptions(data, store), - stubs: { - ...stubs, - 'oc-text-input': true, - 'oc-select': true, - 'oc-datepicker': true, - 'role-item': true, - 'oc-icon': true, - 'oc-progress': true - } - }) - wrapper.vm.$refs.nameInput.focus = jest.fn() - return wrapper -} - -function getMountedWrapper(store = createStore(), data = {}) { - return mount(LinkEdit, { - ...mountOptions(data, store), - stubs: { - 'vue-select': VueSelect - }, - mocks: { - $route: { - params: { - storageId: 1 - } - } - } - }) -} - -const mountOptions = (data, store) => ({ - localVue, - store, - provide: { - changeView: jest.fn() - }, - directives: { - translate: jest.fn() - }, - data() { - return data - } -}) diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkInfo.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkInfo.spec.js deleted file mode 100644 index 98296b241f5..00000000000 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/LinkInfo.spec.js +++ /dev/null @@ -1,235 +0,0 @@ -import { DateTime } from 'luxon' -import stubs from '@/tests/unit/stubs' -import GetTextPlugin from 'vue-gettext' -import DesignSystem from 'owncloud-design-system' -import { mount, shallowMount, createLocalVue } from '@vue/test-utils' -import LinkInfo from '@files/src/components/SideBar/Shares/PublicLinks/LinkInfo.vue' - -const selectors = { - linkName: '.oc-files-file-link-name', - linkExpirationInfo: '.oc-files-public-link-expires', - linkPassword: '.oc-files-file-link-password', - linkIndirect: '.oc-files-file-link-via', - linkViaLabel: '.files-file-links-link-via-label', - linkRole: '.oc-files-file-link-role', - linkCopyUrl: '.oc-files-public-link-copy-url', - linkUrl: '.oc-files-file-link-url' -} -const localVue = createLocalVue() -localVue.use(GetTextPlugin, { - translations: 'does-not-matter.json', - silent: true -}) - -describe('LinkInfo', () => { - describe('link name', () => { - it('should show token as link name if link does not have name', () => { - const wrapper = getShallowWrapper() - - expect(wrapper.vm.linkName).toEqual('122235445488') - expect(wrapper.find(selectors.linkName).text()).toEqual('122235445488') - }) - - it('should show link name if provided link has name attribute', () => { - const wrapper = getShallowWrapper({ - name: 'some-name', - url: 'some-link' - }) - - expect(wrapper.vm.linkName).toEqual('some-name') - expect(wrapper.find(selectors.linkName).text()).toEqual('some-name') - }) - - it('should show link name if provided link has both name and token attribute', () => { - const wrapper = getShallowWrapper({ - name: 'some-name', - token: 'some-token', - url: 'some-link' - }) - - expect(wrapper.vm.linkName).toEqual('some-name') - expect(wrapper.find(selectors.linkName).text()).toEqual('some-name') - }) - }) - - describe('link url', () => { - it('should set provided link url as href attribute of link tag and as value of copy to clipboard button', () => { - const wrapper = getShallowWrapper({ - url: 'some-link' - }) - const linkCopyUrlHyperlink = wrapper.find(selectors.linkUrl) - - expect(wrapper.vm.link.url).toEqual('some-link') - expect(linkCopyUrlHyperlink.attributes().href).toEqual('some-link') - expect(linkCopyUrlHyperlink.text()).toEqual('some-link') - }) - }) - - describe('link role', () => { - it.each([ - { role: 'Viewer', icon: 'eye' }, - { role: 'Editor', icon: 'pencil' }, - { role: 'Contributor', icon: 'pencil' }, - { role: 'Uploader', icon: 'upload' }, - { role: '*', icon: 'key' } - ])('should set different role tag icon for different role types', (dataSet) => { - const wrapper = getShallowWrapper({ - url: 'some-link', - description: dataSet.role - }) - - expect(wrapper.find(`${selectors.linkRole} oc-icon-stub`).attributes().name).toEqual( - dataSet.icon - ) - expect(wrapper.find(selectors.linkRole).text()).toEqual(dataSet.role) - }) - }) - describe('copy to clipboard button', () => { - const wrapper = getShallowWrapper({ - url: 'some-link' - }) - const linkCopyUrlButton = wrapper.find(selectors.linkCopyUrl) - - it('should set link url as button value', () => { - expect(linkCopyUrlButton.attributes().value).toEqual('some-link') - }) - - it('should have label set', () => { - expect(linkCopyUrlButton.props().label).toBe('Copy link to clipboard') - }) - - it('should have success message title set', () => { - expect(linkCopyUrlButton.props().successMsgTitle).toBe('Public link copied') - }) - - it('should have success message text set', () => { - const wrapper = getShallowWrapper({ - url: 'some-link', - name: 'some name' - }) - const linkCopyUrlButton = wrapper.find(selectors.linkCopyUrl) - - expect(linkCopyUrlButton.props().successMsgText).toBe( - 'The public link "some name" has been copied to your clipboard.' - ) - }) - }) - - describe('link expiration', () => { - it('should exist if link has expiration', () => { - const tomorrow = DateTime.now().plus({ days: 1 }) - const wrapper = getShallowWrapper({ - url: 'some-link', - expiration: tomorrow - }) - - const linkExpiration = wrapper.find(selectors.linkExpirationInfo) - - expect(linkExpiration.exists()).toBeTruthy() - expect(linkExpiration.find('translate-stub').props().translateParams).toMatchObject({ - expires: 'in 1 day' - }) - }) - it('should not be present if link does not have expiration', () => { - const wrapper = getShallowWrapper({ - url: 'some-link' - }) - - expect(wrapper.find(selectors.linkExpirationInfo).exists()).toBeFalsy() - }) - }) - describe('link password', () => { - it('should exist if link has password', () => { - const wrapper = getShallowWrapper({ - url: 'some-link', - password: 'some-password' - }) - - expect(wrapper.find(selectors.linkPassword).exists()).toBeTruthy() - }) - it('should not be present if link does not have password', () => { - const wrapper = getShallowWrapper({ - url: 'some-link' - }) - - expect(wrapper.find(selectors.linkPassword).exists()).toBeFalsy() - }) - }) - describe('indirect link', () => { - it('should exist if link is indirect', () => { - const wrapper = getMountedWrapper({ - url: 'some-link', - indirect: true, - path: '/some-path' - }) - const linkDirectTag = wrapper.find(selectors.linkIndirect) - - expect(linkDirectTag.exists()).toBeTruthy() - expect(linkDirectTag.props().to).toMatchObject({ - name: 'files-spaces-personal-home', - params: { item: '/some-path' }, - query: { scrollTo: 'some-path' } - }) - expect(linkDirectTag.find(selectors.linkViaLabel).text()).toBe('Via some-path') - }) - it('should not exist if link is not indirect', () => { - const wrapper = getShallowWrapper({ - url: 'some-link' - }) - - expect(wrapper.find(selectors.linkIndirect).exists()).toBeFalsy() - }) - }) -}) - -function getShallowWrapper( - link = { - token: '122235445488', - url: 'some-link' - } -) { - return shallowMount(LinkInfo, { - localVue, - stubs: { ...stubs, 'oc-tag': true }, - propsData: { - link: link - }, - directives: { - 'oc-tooltip': jest.fn() - } - }) -} - -function getMountedWrapper( - link = { - token: '122235445488', - url: 'some-link' - } -) { - localVue.use(DesignSystem) - return mount(LinkInfo, { - localVue, - propsData: { - link: link - }, - stubs, - directives: { - 'oc-tooltip': jest.fn() - }, - mocks: { - $router: { - currentRoute: { name: 'some-route' }, - resolve: (r) => { - return { - href: r.name - } - } - }, - $route: { - params: { - storageId: 1 - } - } - } - }) -} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/ListItem.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/ListItem.spec.js deleted file mode 100644 index 12b3636f3bb..00000000000 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/ListItem.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import ListItem from '@files/src/components/SideBar/Shares/PublicLinks/ListItem.vue' -import { createLocalVue, shallowMount } from '@vue/test-utils' - -const localVue = createLocalVue() - -describe('ListItem', () => { - it('should show link info component', () => { - const wrapper = getShallowWrapper(getLinkObject()) - expect(wrapper.find('link-info-stub').props('link')).toMatchObject({ - name: 'public link', - url: 'some-url', - indirect: false - }) - }) - - it('should show link actions component if link is not indirect', () => { - const wrapper = getShallowWrapper(getLinkObject()) - const linkActions = wrapper.find('link-actions-stub') - expect(linkActions.exists()).toBeTruthy() - expect(linkActions.props('link')).toMatchObject({ - name: 'public link', - url: 'some-url', - indirect: false - }) - }) - it('should not show link actions component if link is indirect', () => { - const wrapper = getShallowWrapper(getLinkObject(true)) - const linkActions = wrapper.find('link-actions-stub') - expect(linkActions.exists()).toBeFalsy() - }) -}) - -function getLinkObject(indirect = false) { - return { - link: { - name: 'public link', - url: 'some-url', - indirect: indirect - } - } -} - -function getShallowWrapper(props) { - return shallowMount(ListItem, { - localVue, - propsData: props, - stubs: { - 'oc-grid': true, - 'link-info': true, - 'link-actions': true - } - }) -} diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/__snapshots__/LinkEdit.spec.js.snap b/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/__snapshots__/LinkEdit.spec.js.snap deleted file mode 100644 index 552659bf3a5..00000000000 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/PublicLinks/__snapshots__/LinkEdit.spec.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LinkEdit password field should have required label if password is enforced 1`] = ``; - -exports[`LinkEdit password field should not have required label if password is not enforced 1`] = ``; diff --git a/tests/acceptance/features/webUISharingPublicBasic/publicLinkCreate.feature b/tests/acceptance/features/webUISharingPublicBasic/publicLinkCreate.feature index ff84d69322d..9e00619845f 100644 --- a/tests/acceptance/features/webUISharingPublicBasic/publicLinkCreate.feature +++ b/tests/acceptance/features/webUISharingPublicBasic/publicLinkCreate.feature @@ -14,12 +14,12 @@ Feature: Create public link shares And user "Alice" has logged in using the webUI When the user creates a new public link for resource "simple-folder" using the webUI Then user "Alice" should have a share with these details in the server: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | read | - | path | /simple-folder | - | name | Public link | + | field | value | + | share_type | public_link | + | uid_owner | Alice | + | permissions | read | + | path | /simple-folder | + | name | Public link | And a link named "Public link" should be listed with role "Viewer" in the public link list of resource "simple-folder" on the webUI When the public uses the webUI to access the last public link created by user "Alice" in a new session Then file "lorem.txt" should be listed on the webUI @@ -30,12 +30,12 @@ Feature: Create public link shares And user "Alice" has logged in using the webUI When the user creates a new public link for resource "lorem.txt" using the webUI Then user "Alice" should have a share with these details in the server: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | read | - | path | /lorem.txt | - | name | Public link | + | field | value | + | share_type | public_link | + | uid_owner | Alice | + | permissions | read | + | path | /lorem.txt | + | name | Public link | And a link named "Public link" should be listed with role "Viewer" in the public link list of resource "lorem.txt" on the webUI When the public uses the webUI to access the last public link created by user "Alice" in a new session Then file "lorem.txt" should be listed on the webUI @@ -48,11 +48,11 @@ Feature: Create public link shares And user "Alice" has logged in using the webUI When the user creates a new public link for resource "simple-folder" using the webUI Then user "Alice" should have a share with these details in the server: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | read | - | path | /simple-folder | + | field | value | + | share_type | public_link | + | uid_owner | Alice | + | permissions | read | + | path | /simple-folder | And a public link with the last created link share token as name should be listed for resource "simple-folder" on the webUI When the public uses the webUI to access the last public link created by user "Alice" in a new session Then file "lorem.txt" should be listed on the webUI @@ -64,11 +64,11 @@ Feature: Create public link shares And user "Alice" has logged in using the webUI When the user creates a new public link for resource "lorem.txt" using the webUI Then user "Alice" should have a share with these details in the server: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | read | - | path | /lorem.txt | + | field | value | + | share_type | public_link | + | uid_owner | Alice | + | permissions | read | + | path | /lorem.txt | And a public link with the last created link share token as name should be listed for resource "lorem.txt" on the webUI When the public uses the webUI to access the last public link created by user "Alice" in a new session Then file "lorem.txt" should be listed on the webUI @@ -93,14 +93,14 @@ Feature: Create public link shares When the user creates a new public link for file "lorem.txt" using the webUI And the user opens folder "simple-folder" using the webUI And the user creates a new public link for file "lorem.txt" using the webUI - And the user browses to the shared-via-link page using the webUI + # using the webui to navigate creates a problem because "successfully created link" notifications block the nav + And the user has browsed to the shared-via-link page Then file with path "lorem.txt" should be listed on the webUI And file with path "simple-folder/lorem.txt" should be listed on the webUI - Scenario: user creates a multiple public link of a file and delete the first link Given user "Alice" has created file "lorem.txt" in the server And user "Alice" has created a public link with following settings in the server diff --git a/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature b/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature index c29af9ca34a..eb3c85ec9be 100644 --- a/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature +++ b/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature @@ -7,6 +7,7 @@ Feature: Edit public link shares Background: Given user "Alice" has been created with default attributes and without skeleton files in the server + @issue-ocis-1328 Scenario Outline: user tries to change the role of an existing public link role without entering share password while enforce password for that role is enforced Given the setting "" of app "core" has been set to "yes" in the server @@ -16,8 +17,7 @@ Feature: Edit public link shares | name | Public-link | | permissions | | And user "Alice" has logged in using the webUI - When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing following - | role | | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the role to "" Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" And user "Alice" should have a share with these details in the server: | field | value | @@ -32,6 +32,8 @@ Feature: Edit public link shares | read | Editor | shareapi_enforce_links_password_read_write_delete | | read, create | Uploader | shareapi_enforce_links_password_write_only | + + @issue-ocis-1328 Scenario Outline: user tries to delete the password of an existing public link role while enforce password for that role is enforced Given the setting "" of app "core" has been set to "yes" in the server @@ -42,9 +44,8 @@ Feature: Edit public link shares | permissions | | | password | 123 | And user "Alice" has logged in using the webUI - When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing following - | password | | - Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the password to "" + Then the user should see an error message on the public link edit modal dialog saying "Password can't be empty" And user "Alice" should have a share with these details in the server: | field | value | | share_type | public_link | @@ -58,45 +59,6 @@ Feature: Edit public link shares | read, update, create, delete | shareapi_enforce_links_password_read_write_delete | | create | shareapi_enforce_links_password_write_only | - @issue-ocis-1328 - Scenario Outline: user changes the role of an existing public link role without entering share password while enforce password for the original role is enforced - Given the setting "" of app "core" has been set to "yes" in the server - And user "Alice" has created folder "simple-folder" in the server - And user "Alice" has created a public link with following settings in the server - | path | simple-folder | - | name | Public-link | - | permissions | | - | password | 123 | - And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of folder "simple-folder" changing following - | role | | - | password | | - Then user "Alice" should have a share with these details in the server: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | | - | path | /simple-folder | - Examples: - | initial-permissions | role | setting-name | expected-permissions | - | read | Contributor | shareapi_enforce_links_password_read_only | read, create | - | read, create | Viewer | shareapi_enforce_links_password_read_write | read | - | read, update, create, delete | Uploader | shareapi_enforce_links_password_read_write_delete | create | - | create | Editor | shareapi_enforce_links_password_write_only | read, update, create, delete | - - @issue-ocis-1328 - Scenario: user edits a public link and does not save the changes - Given the setting "shareapi_allow_public_notification" of app "core" has been set to "yes" in the server - And user "Alice" has created folder "simple-folder" in the server - And user "Alice" has created a public link with following settings in the server - | path | simple-folder | - | name | test_public_link | - | password | pass123 | - And user "Alice" has logged in using the webUI - When the user edits the public link named "test_public_link" of folder "simple-folder" changing following but not saving - | password | qwertyui | - And the public uses the webUI to access the last public link created by user "Alice" with password "qwertyui" in a new session - Then the public should not get access to the publicly shared file Scenario: user edits a name of an already existing public link Given user "Alice" has created folder "simple-folder" in the server @@ -121,8 +83,7 @@ Feature: Edit public link shares | permissions | read, update, create, delete | | password | pass123 | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of folder "simple-folder" changing following - | password | qwertyui | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the password to "qwertyui" And the public uses the webUI to access the last public link created by user "Alice" with password "qwertyui" in a new session Then file "lorem.txt" should be listed on the webUI @@ -136,8 +97,7 @@ Feature: Edit public link shares | permissions | read, update, create, delete | | password | pass123 | And user "Alice" has logged in using the webUI - When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing following - | password | qwertyui | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the password to "qwertyui" And the public uses the webUI to access the last public link created by user "Alice" with password "pass123" in a new session Then the public should not get access to the publicly shared file @@ -150,8 +110,7 @@ Feature: Edit public link shares | name | Public-link | | permissions | read, update, create, delete | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of folder "simple-folder" changing following - | role | Viewer | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the role to "Viewer" And the public uses the webUI to access the last public link created by user "Alice" in a new session Then file "lorem.txt" should be listed on the webUI And it should not be possible to delete file "lorem.txt" using the webUI @@ -166,8 +125,7 @@ Feature: Edit public link shares | name | Public-link | | permissions | read | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of folder "simple-folder" changing following - | role | Editor | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the role to "Editor" And the public uses the webUI to access the last public link created by user "Alice" in a new session And the user deletes the following elements using the webUI | name | @@ -196,8 +154,7 @@ Feature: Edit public link shares | name | Public-link | | permissions | read, update, create, delete | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of folder "simple-folder" changing following - | role | Contributor | + When the user tries to edit the public link named "Public-link" of folder "simple-folder" changing the role to "Contributor" And the public uses the webUI to access the last public link created by user "Alice" in a new session And the user uploads file "lorem.txt" using the webUI Then file "simple.txt" should be listed on the webUI @@ -207,10 +164,9 @@ Feature: Edit public link shares Scenario: assign password to already created public share Given user "Alice" has created file "lorem.txt" in the server And user "Alice" has created a public link with following settings in the server - | path | lorem.txt | - | name | Public-link | + | path | lorem.txt | + | name | Public-link | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public-link" of file "lorem.txt" changing following - | password | pass123 | + When the user tries to edit the public link named "Public-link" of folder "lorem.txt" adding a password "pass123" And the public uses the webUI to access the last public link created by user "Alice" with password "pass123" in a new session Then file "lorem.txt" should be listed on the webUI diff --git a/tests/acceptance/features/webUISharingPublicDifferentRoles/shareByPublicLinkDifferentRoles.feature b/tests/acceptance/features/webUISharingPublicDifferentRoles/shareByPublicLinkDifferentRoles.feature index 94db04ff24d..acf0005e7a2 100644 --- a/tests/acceptance/features/webUISharingPublicDifferentRoles/shareByPublicLinkDifferentRoles.feature +++ b/tests/acceptance/features/webUISharingPublicDifferentRoles/shareByPublicLinkDifferentRoles.feature @@ -279,36 +279,36 @@ Feature: Share by public link with different roles Given the setting "shareapi_enforce_links_password_read_only" of app "core" has been set to "yes" in the server And user "Alice" has logged in using the webUI When the user creates a new public link for folder "simple-folder" using the webUI - Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" And user "Alice" should not have created any shares in the server + @issue-ocis-1328 Scenario: user tries to create a public link with Contributor role without entering share password while enforce password on read-write public share is enforced Given the setting "shareapi_enforce_links_password_read_write" of app "core" has been set to "yes" in the server And user "Alice" has logged in using the webUI When the user creates a new public link for folder "simple-folder" using the webUI with | role | Contributor | - Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" And user "Alice" should not have created any shares in the server + @issue-ocis-1328 Scenario: user tries to create a public link with Editor Role without entering share password while enforce password on read-write public share is enforced Given the setting "shareapi_enforce_links_password_read_write_delete" of app "core" has been set to "yes" in the server And user "Alice" has logged in using the webUI When the user creates a new public link for folder "simple-folder" using the webUI with | role | Editor | - Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" And user "Alice" should not have created any shares in the server + @issue-ocis-1328 Scenario: user tries to create a public link with Uploader role without entering share password while enforce password on write only public share is enforced Given the setting "shareapi_enforce_links_password_write_only" of app "core" has been set to "yes" in the server And user "Alice" has logged in using the webUI When the user creates a new public link for folder "simple-folder" using the webUI with | role | Uploader | - Then the user should see an error message on the public link share dialog saying "Passwords are enforced for link shares" And user "Alice" should not have created any shares in the server + @issue-ocis-1328 Scenario: user creates a public link with Contributor Role without entering share password while enforce password on read only public share is enforced Given the setting "shareapi_enforce_links_password_read_only" of app "core" has been set to "yes" in the server diff --git a/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature b/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature index de1fc116bc2..c8c6afa39b4 100644 --- a/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature +++ b/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature @@ -15,8 +15,7 @@ Feature: Share by public link | name | Public link | | expireDate | 2038-10-14 | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public link" of file "lorem.txt" changing following - | expireDate | 2038 July 21 | + When the user edits the public link named "Public link" of file "lorem.txt" changing expireDate to "2038 July 21" Then the last public link share response of user "Alice" should include the following fields in the server | expireDate | 2038-07-21 | @@ -32,6 +31,7 @@ Feature: Share by public link + @issue-ocis-1328 Scenario Outline: auto set expiration date on public link (with default amount of expiry days) Given the setting "shareapi_default_expire_date" of app "core" has been set to "yes" in the server @@ -114,9 +114,7 @@ Feature: Share by public link | expireDate | +16 | And user "Alice" has logged in using the webUI And the setting "shareapi_expire_after_n_days" of app "core" has been set to "7" in the server - When the user edits the public link named "Public link" of file "lorem.txt" changing following - | expireDate | +15 | - Then the user should see an error message on the public link share dialog saying "Cannot set expiration date more than 7 days in the future" + When the user edits the public link named "Public link" of file "lorem.txt" changing expireDate to "+15" And user "Alice" should have a share with these details in the server: | field | value | | share_type | public_link | @@ -126,6 +124,8 @@ Feature: Share by public link | name | Public link | | expiration | +16 | + + @issue-ocis-1328 Scenario: user can set an expiry date when creating a public link to a date that is before the enforced max expiry date Given the setting "shareapi_default_expire_date" of app "core" has been set to "yes" in the server @@ -153,8 +153,7 @@ Feature: Share by public link | name | Public link | | expireDate | +5 | And user "Alice" has logged in using the webUI - When the user edits the public link named "Public link" of file "lorem.txt" changing following - | expireDate | +7 | + When the user edits the public link named "Public link" of file "lorem.txt" changing expireDate to "+7" Then user "Alice" should have a share with these details in the server: | field | value | | share_type | public_link | diff --git a/tests/acceptance/pageObjects/FilesPageElement/publicLinksDialog.js b/tests/acceptance/pageObjects/FilesPageElement/publicLinksDialog.js index e62881e3d6d..f850bf439e7 100644 --- a/tests/acceptance/pageObjects/FilesPageElement/publicLinksDialog.js +++ b/tests/acceptance/pageObjects/FilesPageElement/publicLinksDialog.js @@ -35,13 +35,70 @@ module.exports = { .click(linkRowEditButton) .waitForOutstandingAjaxCalls() }, + + clickLinkAddPasswordBtn: function (linkName) { + const linkRowAddPasswordSelector = + this.elements.publicLinkContainer.selector + + util.format(this.elements.publicLinkAddPasswordButton.selector, linkName) + const publicLinkAddPasswordButton = { + locateStrategy: this.elements.publicLinkAddPasswordButton.locateStrategy, + selector: linkRowAddPasswordSelector + } + return this.waitForElementVisible(publicLinkAddPasswordButton) + .initAjaxCounters() + .click(publicLinkAddPasswordButton) + .waitForOutstandingAjaxCalls() + }, + + clickLinkEditPasswordBtn: function (linkName) { + const linkRowEditPasswordSelector = + this.elements.publicLinkContainer.selector + + util.format(this.elements.publicLinkRenamePasswordButton.selector, linkName) + const publicLinkRenamePasswordButton = { + locateStrategy: this.elements.publicLinkRenamePasswordButton.locateStrategy, + selector: linkRowEditPasswordSelector + } + return this.waitForElementVisible(publicLinkRenamePasswordButton) + .initAjaxCounters() + .click(publicLinkRenamePasswordButton) + .waitForOutstandingAjaxCalls() + }, + + clickLinkEditNameBtn: function (linkName) { + const linkRowEditNameSelector = + this.elements.publicLinkContainer.selector + + util.format(this.elements.publicLinkRenameButton.selector, linkName) + const publicLinkRenameButton = { + locateStrategy: this.elements.publicLinkRenameButton.locateStrategy, + selector: linkRowEditNameSelector + } + return this.waitForElementVisible(publicLinkRenameButton) + .initAjaxCounters() + .click(publicLinkRenameButton) + .waitForOutstandingAjaxCalls() + }, + + clickLinkEditExpirationBtn: function (linkName) { + const linkRowEditExpirationDateSelector = + this.elements.publicLinkContainer.selector + + util.format(this.elements.publicLinkExpirationDateEditButton.selector, linkName) + const publicLinkExpirationDateEditButton = { + locateStrategy: this.elements.publicLinkExpirationDateEditButton.locateStrategy, + selector: linkRowEditExpirationDateSelector + } + return this.waitForElementVisible(publicLinkExpirationDateEditButton) + .initAjaxCounters() + .click(publicLinkExpirationDateEditButton) + .waitForOutstandingAjaxCalls() + }, + /** * sets role or permissions for public link on webUI * * @param {string} role - e.g. Viewer, Contributor, Editor, Uploader * @returns {Promise} */ - setPublicLinkRole: function (role) { + setPublicLinkInitialRole: function (role) { role = _(role).chain().toLower().startCase().replace(/\s/g, '').value() const selectedRoleDropdown = util.format( this.elements.publicLinkRoleSelectionDropdown.selector, @@ -89,7 +146,7 @@ module.exports = { */ setPublicLinkForm: async function (key, value) { if (key === 'role') { - return this.setPublicLinkRole(value) + return this.setPublicLinkInitialRole(value) } else if (key === 'name') { return this.setPublicLinkName(value) } else if (key === 'password') { @@ -115,11 +172,48 @@ module.exports = { * @param {string} editData.expireDate - Expire date for a public link share * @returns {exports} */ - editPublicLink: async function (linkName, editData) { + editPublicLink: async function (linkName) { await this.clickLinkEditBtn(linkName) - for (const [key, value] of Object.entries(editData)) { - await this.setPublicLinkForm(key, value) + return this + }, + + editPublicLinkExpiration: async function (linkName) { + await this.clickLinkEditExpirationBtn(linkName) + return this + }, + + changeExpirationDate: async function (linkName, expiry) { + const value = sharingHelper.calculateDate(expiry) + await this.editPublicLink(linkName) + await this.editPublicLinkExpiration(linkName) + return this.api.page.FilesPageElement.expirationDatePicker().setExpirationDate(value, 'link') + }, + + openRolesDrop: function (linkName) { + const linkRowEditRoleButtonSelector = + this.elements.publicLinkContainer.selector + + util.format(this.elements.publicLinkEditRoleButton.selector, linkName) + const linkRowEditRoleButton = { + locateStrategy: this.elements.publicLinkEditRoleButton.locateStrategy, + selector: linkRowEditRoleButtonSelector } + return this.waitForElementVisible(linkRowEditRoleButton) + .initAjaxCounters() + .click(linkRowEditRoleButton) + .waitForOutstandingAjaxCalls() + }, + + setPublicLinkRole: function (role) { + role = _(role).chain().toLower().startCase().replace(/\s/g, '').value() + return this.waitForElementVisible(`@role${role}`) + .initAjaxCounters() + .click(`@role${role}`) + .waitForOutstandingAjaxCalls() + }, + + changeRole: async function (linkName, role) { + await this.openRolesDrop(linkName) + await this.setPublicLinkRole(role) return this }, /** @@ -146,7 +240,7 @@ module.exports = { * @param {string} linkName Name of the public link share of a resource to be deleted * @returns {exports} */ - removePublicLink: function (linkName) { + removePublicLink: async function (linkName) { const linkRowDeleteButtonSelector = this.elements.publicLinkContainer.selector + util.format(this.elements.publicLinkDeleteButton.selector, linkName) @@ -154,6 +248,7 @@ module.exports = { locateStrategy: this.elements.publicLinkDeleteButton.locateStrategy, selector: linkRowDeleteButtonSelector } + await this.editPublicLink(linkName) return this.waitForElementVisible(linkRowDeleteButton) .initAjaxCounters() .click(linkRowDeleteButton) @@ -168,7 +263,7 @@ module.exports = { * @param {string} linkName Name of the public link share of a resource to be deleted * @returns {exports} */ - cancelRemovePublicLink: function (linkName) { + cancelRemovePublicLink: async function (linkName) { const linkRowDeleteButtonSelector = this.elements.publicLinkContainer.selector + util.format(this.elements.publicLinkDeleteButton.selector, linkName) @@ -176,6 +271,7 @@ module.exports = { locateStrategy: this.elements.publicLinkDeleteButton.locateStrategy, selector: linkRowDeleteButtonSelector } + await this.editPublicLink(linkName) return this.waitForElementVisible(linkRowDeleteButton) .initAjaxCounters() .click(linkRowDeleteButton) @@ -225,7 +321,6 @@ module.exports = { return this.waitForElementVisible('@publicLinkCreateButton') .initAjaxCounters() .click('@publicLinkCreateButton') - .waitForElementNotPresent('@publicLinkCreateButton') .waitForOutstandingAjaxCalls() }, /** @@ -296,7 +391,11 @@ module.exports = { } ) - if (attrElementId) { + // hack to check for presence of via-button + // since the redesign removed the visual via-text + if (attrElementId && attrName === 'viaLabel') { + linkResult.viaLabel = true + } else if (attrElementId) { await this.api.elementIdText(attrElementId, (text) => { linkResult[attrName] = text.value }) @@ -353,6 +452,72 @@ module.exports = { }) return message }, + + getErrorMessageFromModal: async function () { + let message + await this.getText('.oc-modal-body-input .oc-text-input-message', function (result) { + message = result.value + }) + return message + }, + + editPublicLinkName: async function (linkName) { + await this.clickLinkEditNameBtn(linkName) + return this + }, + + changeName: async function (linkName, newName) { + await this.editPublicLink(linkName) + await this.editPublicLinkName(linkName) + + await this.useXpath() + .waitForElementVisible('@dialog') + .waitForAnimationToFinish() + .clearValue('@dialogInput') + .setValue('@dialogInput', newName) + .useCss() + + await this.click('@dialogConfirmBtnEnabled') + }, + + editPublicLinkPassword: async function (linkName) { + await this.clickLinkEditPasswordBtn(linkName) + return this + }, + + addPublicLinkPassword: async function (linkName) { + await this.clickLinkAddPasswordBtn(linkName) + return this + }, + + addPassword: async function (linkName, password) { + await this.editPublicLink(linkName) + await this.addPublicLinkPassword(linkName) + + await this.useXpath() + .waitForElementVisible('@dialog') + .waitForAnimationToFinish() + .clearValue('@dialogInput') + .setValue('@dialogInput', password) + .useCss() + + await this.click('@dialogConfirmBtnEnabled') + }, + + changePassword: async function (linkName, password) { + await this.editPublicLink(linkName) + await this.editPublicLinkPassword(linkName) + + await this.useXpath() + .waitForElementVisible('@dialog') + .waitForAnimationToFinish() + .clearValue('@dialogInput') + .setValue('@dialogInput', password) + .useCss() + + await this.click('@dialogConfirmBtnEnabled') + }, + /** * clicks the 'copy-public-link-uri' button of a public link * @@ -416,7 +581,7 @@ module.exports = { selector: '.oc-files-file-link-name' }, publicLinkSubRole: { - selector: '.oc-files-file-link-role' + selector: '.link-details .oc-invisible-sr' }, publicLinkSubVia: { selector: '.oc-files-file-link-via' @@ -450,14 +615,39 @@ module.exports = { selector: '//input[@id="oc-files-file-link-name"]', locateStrategy: 'xpath' }, + publicLinkEditRoleButton: { + selector: + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "link-details")]/div/button[contains(@class, "edit-public-link-role-dropdown-toggl")]', + locateStrategy: 'xpath' + }, publicLinkEditButton: { selector: - '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]/../../..//button[contains(@class, "oc-files-file-link-edit")]', + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[contains(@class, "edit-drop-trigger")]', + locateStrategy: 'xpath' + }, + publicLinkRenameButton: { + selector: + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[text()="Rename"]', + locateStrategy: 'xpath' + }, + publicLinkAddPasswordButton: { + selector: + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[text()="Add password"]', + locateStrategy: 'xpath' + }, + publicLinkRenamePasswordButton: { + selector: + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[text()="Edit password"]', + locateStrategy: 'xpath' + }, + publicLinkExpirationDateEditButton: { + selector: + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[text()="Edit expiration date"]', locateStrategy: 'xpath' }, publicLinkDeleteButton: { selector: - '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]/../../..//button[contains(@class, "oc-files-file-link-delete")]', + '//h5[contains(@class, "oc-files-file-link-name") and text()="%s"]//ancestor::li//div[contains(@class, "details-buttons")]//button[text()="Delete public link"]', locateStrategy: 'xpath' }, publicLinkURLCopyButton: { @@ -485,8 +675,11 @@ module.exports = { dialog: { selector: '.oc-modal' }, + dialogInput: { + selector: '.oc-modal-body-input .oc-text-input' + }, dialogConfirmBtnEnabled: { - selector: '.oc-modal-body-actions-confirm:enabled' + selector: '.oc-modal-body-actions-confirm' }, dialogCancelBtn: { selector: '.oc-modal-body-actions-cancel' diff --git a/tests/acceptance/stepDefinitions/publicLinkContext.js b/tests/acceptance/stepDefinitions/publicLinkContext.js index b8ed1ce3be1..989b9628ec5 100644 --- a/tests/acceptance/stepDefinitions/publicLinkContext.js +++ b/tests/acceptance/stepDefinitions/publicLinkContext.js @@ -99,11 +99,9 @@ const loadPublicLinkWithPassword = async function (linkCreator, password, newSes return client.page.publicLinkPasswordPage().submitPublicLinkPassword(password) } -const editPublicLink = async function (linkName, resource, dataTable) { - const editData = dataTable.rowsHash() +const editPublicLink = async function (linkName, resource) { await client.page.FilesPageElement.filesList().openPublicLinkDialog(resource) - await client.page.FilesPageElement.publicLinksDialog().editPublicLink(linkName, editData) - return client.page.FilesPageElement.publicLinksDialog().savePublicLink() + await client.page.FilesPageElement.publicLinksDialog().editPublicLink(linkName) } Then('the public should not get access to the publicly shared file', async function () { @@ -135,11 +133,43 @@ When( } ) +When( + 'the user edits the public link named {string} of file/folder/resource {string} changing expireDate to {string}', + async function (linkName, resource, expiry) { + await client.page.FilesPageElement.filesList().openPublicLinkDialog(resource) + await client.page.FilesPageElement.publicLinksDialog().changeExpirationDate(linkName, expiry) + } +) + +When( + 'the user tries to edit the public link named {string} of file/folder/resource {string} changing the role to {string}', + async function (linkName, resource, role) { + await client.page.FilesPageElement.filesList().openPublicLinkDialog(resource) + await client.page.FilesPageElement.publicLinksDialog().changeRole(linkName, role) + } +) + +When( + 'the user tries to edit the public link named {string} of file/folder/resource {string} changing the password to {string}', + async function (linkName, resource, password) { + await client.page.FilesPageElement.filesList().openPublicLinkDialog(resource) + await client.page.FilesPageElement.publicLinksDialog().changePassword(linkName, password) + } +) + +When( + 'the user tries to edit the public link named {string} of file/folder/resource {string} adding a password {string}', + async function (linkName, resource, password) { + await client.page.FilesPageElement.filesList().openPublicLinkDialog(resource) + await client.page.FilesPageElement.publicLinksDialog().addPassword(linkName, password) + } +) + When( 'the user tries to edit the public link named {string} of file/folder/resource {string} changing following', - function (linkName, resource, dataTable) { + function (linkName, resource) { return ( - editPublicLink(linkName, resource, dataTable) + editPublicLink(linkName, resource) // while editing public link, after clicking the "Save" button, the button should disappear but if it doesn't // we throw "ElementPresentError" error. So, all the error except "ElementPresentError" is caught and thrown back // Also, when no error is thrown, the button seems to disappear, so an error should be thrown in such case as well. @@ -163,7 +193,7 @@ When( await api.publicLinksDialog().clickLinkEditBtn(linkName) const value = sharingHelper.calculateDate(pastDate) const dateToSet = new Date(Date.parse(value)) - await api.publicLinksDialog().openExpirationDatePicker() + await api.publicLinksDialog().clickLinkEditExpirationBtn(linkName) const isDisabled = await api.expirationDatePicker().isExpiryDateDisabled(dateToSet) return assert.ok(isDisabled, 'Expected expiration date to be disabled but found not disabled') } @@ -214,7 +244,7 @@ async function findMatchingPublicLinkByName(name, role, resource, via = null) { assert.strictEqual(role, share.role) if (via !== null) { - assert.strictEqual('Via ' + via, share.viaLabel) + assert.ok(share.viaLabel, 'Expected "shared via" icon to be displayed but was not visible') } } @@ -240,6 +270,15 @@ Then( } ) +Then( + 'the user should see an error message on the public link edit modal dialog saying {string}', + async function (expectedMessage) { + const actualMessage = + await client.page.FilesPageElement.publicLinksDialog().getErrorMessageFromModal() + return client.assert.strictEqual(actualMessage, expectedMessage) + } +) + When( 'the user copies the url of public link named {string} of file/folder/resource {string} using the webUI', async function (linkName, resource) { diff --git a/tests/e2e/support/objects/app-files/link/actions.ts b/tests/e2e/support/objects/app-files/link/actions.ts index c54bf77b1c2..713254d224c 100644 --- a/tests/e2e/support/objects/app-files/link/actions.ts +++ b/tests/e2e/support/objects/app-files/link/actions.ts @@ -70,6 +70,6 @@ export const createLink = async (args: createLinkArgs): Promise => { await page.locator('#oc-files-file-link-create').click() return await page - .locator(`//ul/li//h5[contains(text(),'${name}')]/following-sibling::div/a`) + .locator(`//ul/li/div/h4[contains(text(),'${name}')]/following-sibling::div//p`) .textContent() }