diff --git a/.travis.yml b/.travis.yml index 11e39bebb..f97f4facd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,24 @@ dist: trusty sudo: required +services: + - docker + +addons: + chrome: stable + language: node_js node_js: - "8" +before_script: + # Disable services enabled by default + - sudo /etc/init.d/postgresql stop + install: - npm install -g npm@latest - npm ci script: - - npm run test:ci + # - docker-compose stop + - npm run build && npm run e2e:docker diff --git a/e2e/components/component.ts b/e2e/components/component.ts new file mode 100755 index 000000000..134287c0c --- /dev/null +++ b/e2e/components/component.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, element, by, ExpectedConditions as EC, browser } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../configs'; + +export abstract class Component { + component: ElementFinder; + + constructor(selector: string, ancestor?: ElementFinder) { + const locator = by.css(selector); + + this.component = ancestor + ? ancestor.element(locator) + : element(locator); + } + + wait() { + return browser.wait(EC.presenceOf(this.component), BROWSER_WAIT_TIMEOUT); + } +} diff --git a/e2e/components/components.ts b/e2e/components/components.ts new file mode 100755 index 000000000..604be941d --- /dev/null +++ b/e2e/components/components.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './login/login'; +export * from './header/header'; +export * from './header/user-info'; +export * from './data-table/data-table'; +export * from './pagination/pagination'; +export * from './sidenav/sidenav'; +export * from './toolbar/toolbar'; diff --git a/e2e/components/data-table/data-table.ts b/e2e/components/data-table/data-table.ts new file mode 100755 index 000000000..1bf8de816 --- /dev/null +++ b/e2e/components/data-table/data-table.ts @@ -0,0 +1,243 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, promise, by, browser, ExpectedConditions as EC, protractor } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; +import { Utils } from '../../utilities/utils'; + +export class DataTable extends Component { + private static selectors = { + root: 'adf-datatable', + + head: '.adf-datatable-header', + columnHeader: '.adf-datatable-row .adf-datatable-table-cell-header', + sortedColumnHeader: ` + .adf-data-table__header--sorted-asc, + .adf-data-table__header--sorted-desc + `, + + body: '.adf-datatable-body', + row: '.adf-datatable-row[role]', + selectedRow: '.adf-datatable-row.is-selected', + cell: '.adf-data-table-cell', + locationLink: 'app-location-link', + + selectedIcon: '.mat-icon', + + emptyListContainer: 'div.adf-no-content-container', + emptyFolderDragAndDrop: '.adf-empty-list_template .adf-empty-folder', + + emptyListTitle: '.app-empty-folder__title', + emptyListSubtitle: '.app-empty-folder__subtitle', + emptyListText: '.app-empty-folder__text' + }; + + head: ElementFinder = this.component.element(by.css(DataTable.selectors.head)); + body: ElementFinder = this.component.element(by.css(DataTable.selectors.body)); + cell = by.css(DataTable.selectors.cell); + locationLink = by.css(DataTable.selectors.locationLink); + emptyList: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListContainer)); + emptyFolderDragAndDrop: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyFolderDragAndDrop)); + emptyListTitle: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListTitle)); + emptyListSubtitle: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListSubtitle)); + emptyListText: ElementArrayFinder = this.component.all(by.css(DataTable.selectors.emptyListText)); + + constructor(ancestor?: ElementFinder) { + super(DataTable.selectors.root, ancestor); + } + + // Wait methods (waits for elements) + waitForHeader() { + return browser.wait(EC.presenceOf(this.head), BROWSER_WAIT_TIMEOUT); + } + + waitForEmptyState() { + return browser.wait(EC.presenceOf(this.emptyList), BROWSER_WAIT_TIMEOUT); + } + + // Header/Column methods + getColumnHeaders(): ElementArrayFinder { + const locator = by.css(DataTable.selectors.columnHeader); + return this.head.all(locator); + } + + getNthColumnHeader(nth: number): ElementFinder { + return this.getColumnHeaders().get(nth - 1); + } + + getColumnHeaderByLabel(label: string): ElementFinder { + const locator = by.cssContainingText(DataTable.selectors.columnHeader, label); + return this.head.element(locator); + } + + getSortedColumnHeader(): ElementFinder { + const locator = by.css(DataTable.selectors.sortedColumnHeader); + return this.head.element(locator); + } + + getSortingOrder() { + return this.getSortedColumnHeader().getAttribute('class') + .then(str => { + if (str.includes('asc')) { + return 'asc'; + } else { + if (str.includes('desc')) { + return 'desc'; + } + } + }); + } + + sortByColumn(columnName: string): promise.Promise { + const column = this.getColumnHeaderByLabel(columnName); + const click = browser.actions().mouseMove(column).click(); + + return click.perform(); + } + + // Rows methods + getRows(): ElementArrayFinder { + return this.body.all(by.css(DataTable.selectors.row)); + } + + getSelectedRows(): ElementArrayFinder { + return this.body.all(by.css(DataTable.selectors.selectedRow)); + } + + countSelectedRows(): promise.Promise { + return this.getSelectedRows().count(); + } + + getNthRow(nth: number): ElementFinder { + return this.getRows().get(nth - 1); + } + + getRowName(name: string): ElementFinder { + return this.body.element(by.cssContainingText(`.adf-data-table-cell span`, name)); + } + + getItemNameTooltip(name: string): promise.Promise { + return this.getRowName(name).getAttribute('title'); + } + + countRows(): promise.Promise { + return this.getRows().count(); + } + + hasCheckMarkIcon(itemName: string) { + return this.getRowName(itemName).element(by.xpath(`./ancestor::div[contains(@class, 'adf-datatable-row')]`)) + .element(by.css(DataTable.selectors.selectedIcon)).isPresent(); + } + + // Navigation/selection methods + doubleClickOnItemName(name: string): promise.Promise { + const dblClick = browser.actions() + .mouseMove(this.getRowName(name)) + .click() + .click(); + + return dblClick.perform(); + } + + clickOnItemName(name: string): promise.Promise { + const item = this.getRowName(name); + return Utils.waitUntilElementClickable(item) + .then(() => this.getRowName(name).click()); + } + + selectMultipleItems(names: string[]): promise.Promise { + return this.clearSelection() + .then(() => browser.actions().sendKeys(protractor.Key.COMMAND).perform()) + .then(() => { + names.forEach(name => { + this.clickOnItemName(name); + }); + }) + .then(() => browser.actions().sendKeys(protractor.Key.NULL).perform()); + } + + clearSelection(): promise.Promise { + return this.getSelectedRows().count() + .then(count => { + if (count !== 0) { browser.refresh().then(() => this.waitForHeader()); } + }); + } + + getItemLocation(name: string) { + return this.getRowName(name).element(by.xpath(`./ancestor::div[contains(@class, 'adf-datatable-row')]`)) + .element(this.locationLink); + } + + getItemLocationTooltip(name: string): promise.Promise { + return this.getItemLocation(name).$('a').getAttribute('title'); + } + + clickItemLocation(name: string) { + return this.getItemLocation(name).click(); + } + + // empty state methods + isEmptyList(): promise.Promise { + return this.emptyList.isPresent(); + } + + isEmptyWithDragAndDrop(): promise.Promise { + return this.emptyFolderDragAndDrop.isDisplayed(); + } + + getEmptyDragAndDropText(): promise.Promise { + return this.isEmptyWithDragAndDrop() + .then(() => { + return this.emptyFolderDragAndDrop.getText(); + }); + } + + getEmptyStateTitle(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListTitle.getText(); + }); + } + + getEmptyStateSubtitle(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListSubtitle.getText(); + }); + } + + getEmptyStateText(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListText.getText(); + }); + } + + getCellsContainingName(name: string) { + return this.getRows().all(by.cssContainingText(DataTable.selectors.cell, name)) + .map(cell => cell.getText()); + } +} diff --git a/e2e/components/dialog/create-edit-folder-dialog.ts b/e2e/components/dialog/create-edit-folder-dialog.ts new file mode 100755 index 000000000..696746e63 --- /dev/null +++ b/e2e/components/dialog/create-edit-folder-dialog.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by, browser, protractor, ExpectedConditions as EC, promise } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; +import { Utils } from '../../utilities/utils'; + +export class CreateOrEditFolderDialog extends Component { + private static selectors = { + root: 'adf-folder-dialog', + + title: '.mat-dialog-title', + nameInput: 'input[placeholder="Name" i]', + descriptionTextArea: 'textarea[placeholder="Description" i]', + button: '.mat-dialog-actions button', + validationMessage: '.mat-hint span' + }; + + title: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.title)); + nameInput: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.nameInput)); + descriptionTextArea: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.descriptionTextArea)); + createButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Create')); + cancelButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Cancel')); + updateButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Update')); + validationMessage: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.validationMessage)); + + constructor(ancestor?: ElementFinder) { + super(CreateOrEditFolderDialog.selectors.root, ancestor); + } + + waitForDialogToOpen() { + return browser.wait(EC.presenceOf(this.title), BROWSER_WAIT_TIMEOUT) + .then(() => browser.wait(EC.presenceOf(browser.element(by.css('.cdk-overlay-backdrop'))), BROWSER_WAIT_TIMEOUT)); + + } + + waitForDialogToClose() { + return browser.wait(EC.stalenessOf(this.title), BROWSER_WAIT_TIMEOUT); + } + + getTitle(): promise.Promise { + return this.title.getText(); + } + + isValidationMessageDisplayed(): promise.Promise { + return this.validationMessage.isDisplayed(); + } + + getValidationMessage(): promise.Promise { + return this.isValidationMessageDisplayed() + .then(() => this.validationMessage.getText()); + } + + enterName(name: string) { + return this.nameInput.clear() + // .then(() => this.nameInput.sendKeys(name)); + .then(() => Utils.typeInField(this.nameInput, name)); + } + + enterDescription(description: string) { + return this.descriptionTextArea.clear() + // .then(() => this.descriptionTextArea.sendKeys(description)); + .then(() => Utils.typeInField(this.descriptionTextArea, description)); + } + + deleteNameWithBackspace(): promise.Promise { + return this.nameInput.clear() + .then(() => { + return this.nameInput.sendKeys(' ', protractor.Key.CONTROL, 'a', protractor.Key.NULL, protractor.Key.BACK_SPACE); + }); + } + + clickCreate() { + return this.createButton.click(); + } + + clickCancel() { + return this.cancelButton.click() + .then(() => this.waitForDialogToClose()); + } + + clickUpdate() { + return this.updateButton.click(); + } +} diff --git a/e2e/components/header/header.ts b/e2e/components/header/header.ts new file mode 100755 index 000000000..ef151d54d --- /dev/null +++ b/e2e/components/header/header.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by } from 'protractor'; +import { Component } from '../component'; +import { UserInfo } from './user-info'; + +export class Header extends Component { + private locators = { + logoLink: by.css('.app-menu__title'), + userInfo: by.css('app-current-user') + }; + + logoLink: ElementFinder = this.component.element(this.locators.logoLink); + userInfo: UserInfo = new UserInfo(this.component); + + constructor(ancestor?: ElementFinder) { + super('app-header', ancestor); + } +} diff --git a/e2e/components/header/user-info.ts b/e2e/components/header/user-info.ts new file mode 100755 index 000000000..b5e840080 --- /dev/null +++ b/e2e/components/header/user-info.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class UserInfo extends Component { + private locators = { + avatar: by.css('.current-user__avatar'), + fullName: by.css('.current-user__full-name'), + menuItems: by.css('[mat-menu-item]') + }; + + fullName: ElementFinder = this.component.element(this.locators.fullName); + avatar: ElementFinder = this.component.element(this.locators.avatar); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super('app-current-user', ancestor); + } + + openMenu(): promise.Promise { + const { menu, avatar } = this; + + return avatar.click() + .then(() => menu.wait()) + .then(() => menu); + } + + get name(): promise.Promise { + return this.fullName.getText(); + } + + signOut(): promise.Promise { + return this.openMenu() + .then(menu => { + menu.clickMenuItem('Sign out'); + }); + } +} diff --git a/e2e/components/login/login.ts b/e2e/components/login/login.ts new file mode 100755 index 000000000..5b6a306a8 --- /dev/null +++ b/e2e/components/login/login.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { by, ElementFinder, promise } from 'protractor'; +import { Component } from '../component'; + +export class LoginComponent extends Component { + static selector = 'adf-login'; + + private locators = { + usernameInput: by.css('input#username'), + passwordInput: by.css('input#password'), + passwordVisibility: by.css('.adf-login-password-icon'), + submitButton: by.css('button#login-button'), + errorMessage: by.css('.login-error-message'), + copyright: by.css('.copyright') + }; + + usernameInput: ElementFinder = this.component.element(this.locators.usernameInput); + passwordInput: ElementFinder = this.component.element(this.locators.passwordInput); + submitButton: ElementFinder = this.component.element(this.locators.submitButton); + errorMessage: ElementFinder = this.component.element(this.locators.errorMessage); + copyright: ElementFinder = this.component.element(this.locators.copyright); + passwordVisibility: ElementFinder = this.component.element(this.locators.passwordVisibility); + + constructor(ancestor?: ElementFinder) { + super(LoginComponent.selector, ancestor); + } + + enterUsername(username: string): LoginComponent { + const { usernameInput } = this; + + usernameInput.clear(); + usernameInput.sendKeys(username); + + return this; + } + + enterPassword(password: string): LoginComponent { + const { passwordInput } = this; + + passwordInput.clear(); + passwordInput.sendKeys(password); + + return this; + } + + enterCredentials(username: string, password: string) { + this.enterUsername(username).enterPassword(password); + + return this; + } + + submit(): promise.Promise { + return this.submitButton.click(); + } + + getPasswordVisibility() { + return this.passwordVisibility.getText() + .then(text => { + if (text.endsWith('visibility_off')) { + return false; + } else { + if (text.endsWith('visibility')) { + return true; + } + } + }); + } + + isPasswordShown() { + return this.passwordInput.getAttribute('type') + .then(type => { + if (type === 'text') { + return true; + } else { + if (type === 'password') { + return false; + } + } + }); + } +} diff --git a/e2e/components/menu/menu.ts b/e2e/components/menu/menu.ts new file mode 100755 index 000000000..ba6796a8c --- /dev/null +++ b/e2e/components/menu/menu.ts @@ -0,0 +1,106 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, browser, ExpectedConditions as EC, promise } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; + +export class Menu extends Component { + private static selectors = { + root: '.mat-menu-panel', + item: '.mat-menu-item', + icon: '.mat-icon', + uploadFiles: 'input[id="upload-multiple-files"]' + }; + + items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item)); + backdrop: ElementFinder = browser.element(by.css('.cdk-overlay-backdrop')); + uploadFiles: ElementFinder = this.component.element(by.css(Menu.selectors.uploadFiles)); + + constructor(ancestor?: ElementFinder) { + super(Menu.selectors.root, ancestor); + } + + waitForMenuToOpen() { + return browser.wait(EC.presenceOf(browser.element(by.css('.mat-menu-panel'))), BROWSER_WAIT_TIMEOUT) + .then(() => browser.wait(EC.presenceOf(browser.element(by.css('.cdk-overlay-backdrop'))), BROWSER_WAIT_TIMEOUT)) + .then(() => browser.wait(EC.visibilityOf(this.items.get(0)), BROWSER_WAIT_TIMEOUT)); + } + + waitForMenuToClose() { + return browser.wait(EC.not(EC.presenceOf(browser.element(by.css('.mat-menu-panel')))), BROWSER_WAIT_TIMEOUT); + } + + closeMenu() { + if (this.backdrop.isPresent()) { + return this.backdrop.click(); + } else { + return browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform(); + } + } + + getNthItem(nth: number): ElementFinder { + return this.items.get(nth - 1); + } + + getItemByLabel(menuItem: string): ElementFinder { + return this.component.element(by.cssContainingText(Menu.selectors.item, menuItem)); + } + + getItemTooltip(menuItem: string): promise.Promise { + return this.getItemByLabel(menuItem).getAttribute('title'); + } + + getItemIconText(menuItem: string) { + return this.getItemByLabel(menuItem).element(by.css(Menu.selectors.icon)).getText(); + + } + + getItemsCount(): promise.Promise { + return this.items.count(); + } + + clickNthItem(nth: number): promise.Promise { + const elem = this.getNthItem(nth); + return browser.wait(EC.elementToBeClickable(elem), BROWSER_WAIT_TIMEOUT) + .then(() => browser.actions().mouseMove(elem).click().perform()) + .then(() => this.waitForMenuToClose()); + } + + clickMenuItem(menuItem: string): promise.Promise { + const elem = this.getItemByLabel(menuItem); + return browser.wait(EC.elementToBeClickable(elem), BROWSER_WAIT_TIMEOUT) + .then(() => elem.click()) + .then(() => this.waitForMenuToClose()); + } + + isMenuItemPresent(title: string): promise.Promise { + return this.component.element(by.cssContainingText(Menu.selectors.item, title)).isPresent(); + } + + uploadFile() { + return this.uploadFiles; + } +} diff --git a/e2e/components/pagination/pagination.ts b/e2e/components/pagination/pagination.ts new file mode 100755 index 000000000..47a07dd85 --- /dev/null +++ b/e2e/components/pagination/pagination.ts @@ -0,0 +1,98 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, promise, by } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class Pagination extends Component { + private static selectors = { + root: 'adf-pagination', + range: '.adf-pagination__range', + maxItems: '.adf-pagination__max-items', + currentPage: '.adf-pagination__current-page', + totalPages: '.adf-pagination__total-pages', + + previousButton: '.adf-pagination__previous-button', + nextButton: '.adf-pagination__next-button', + maxItemsButton: '.adf-pagination__max-items + button[mat-icon-button]', + pagesButton: '.adf-pagination__current-page + button[mat-icon-button]' + }; + + range: ElementFinder = this.component.element(by.css(Pagination.selectors.range)); + maxItems: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItems)); + currentPage: ElementFinder = this.component.element(by.css(Pagination.selectors.currentPage)); + totalPages: ElementFinder = this.component.element(by.css(Pagination.selectors.totalPages)); + previousButton: ElementFinder = this.component.element(by.css(Pagination.selectors.previousButton)); + nextButton: ElementFinder = this.component.element(by.css(Pagination.selectors.nextButton)); + maxItemsButton: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItemsButton)); + pagesButton: ElementFinder = this.component.element(by.css(Pagination.selectors.pagesButton)); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super(Pagination.selectors.root, ancestor); + } + + openMaxItemsMenu(): promise.Promise { + const { menu, maxItemsButton } = this; + + return maxItemsButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + openCurrentPageMenu(): promise.Promise { + const { menu, pagesButton } = this; + + return pagesButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + getText(elem: ElementFinder) { + return elem.getText(); + } + + resetToDefaultPageSize(): promise.Promise { + return this.openMaxItemsMenu() + .then(menu => menu.clickMenuItem('25')) + .then(() => this.menu.waitForMenuToClose()); + } + + resetToDefaultPageNumber(): promise.Promise { + return this.openCurrentPageMenu() + .then(menu => menu.clickMenuItem('1')) + .then(() => this.menu.waitForMenuToClose()); + } + + clickNext(): promise.Promise { + return this.nextButton.click(); + } + + clickPrevious(): promise.Promise { + return this.previousButton.click(); + } +} diff --git a/e2e/components/sidenav/sidenav.ts b/e2e/components/sidenav/sidenav.ts new file mode 100755 index 000000000..53d4baf84 --- /dev/null +++ b/e2e/components/sidenav/sidenav.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class Sidenav extends Component { + private static selectors = { + root: 'app-sidenav', + link: '.sidenav-menu__item', + label: '.menu__item--label', + activeLink: '.menu__item--active', + newButton: '.sidenav__section--new__button' + }; + + links: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.link)); + activeLink: ElementFinder = this.component.element(by.css(Sidenav.selectors.activeLink)); + newButton: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.newButton)); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super(Sidenav.selectors.root, ancestor); + } + + openNewMenu(): promise.Promise { + const { menu, newButton } = this; + + return newButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + openCreateDialog(): any { + return this.openNewMenu() + .then(() => this.menu.clickMenuItem('Create folder')); + } + + isActiveByLabel(label: string): promise.Promise { + return this.getLinkByLabel(label).getAttribute('class') + .then(className => className.includes(Sidenav.selectors.activeLink.replace('.', ''))); + } + + getLink(label: string): ElementFinder { + return this.component.element(by.cssContainingText(Sidenav.selectors.link, label)); + } + + getLinkByLabel(label: string): ElementFinder { + return this.component.element(by.cssContainingText(Sidenav.selectors.label, label)); + } + + getLinkTooltip(label: string): promise.Promise { + return this.getLink(label).getAttribute('title'); + } + + navigateToLinkByLabel(label: string): promise.Promise { + return this.getLinkByLabel(label).click(); + } +} diff --git a/e2e/components/toolbar/toolbar-actions.ts b/e2e/components/toolbar/toolbar-actions.ts new file mode 100755 index 000000000..6847a306d --- /dev/null +++ b/e2e/components/toolbar/toolbar-actions.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class ToolbarActions extends Component { + private static selectors = { + root: 'adf-toolbar', + button: '.mat-icon-button' + }; + + menu: Menu = new Menu(); + buttons: ElementArrayFinder = this.component.all(by.css(ToolbarActions.selectors.button)); + + constructor(ancestor?: ElementFinder) { + super(ToolbarActions.selectors.root, ancestor); + } + + isEmpty(): promise.Promise { + return this.buttons.count().then(count => (count === 0)); + } + + isButtonPresent(title: string): promise.Promise { + return this.component.element(by.css(`${ToolbarActions.selectors.button}[title="${title}"]`)).isPresent(); + } + + getButtonByLabel(label: string): ElementFinder { + return this.component.element(by.cssContainingText(ToolbarActions.selectors.button, label)); + } + + getButtonByTitleAttribute(title: string): ElementFinder { + return this.component.element(by.css(`${ToolbarActions.selectors.button}[title="${title}"]`)); + } + + openMoreMenu() { + return this.getButtonByTitleAttribute('More actions').click() + .then(() => this.menu.waitForMenuToOpen()) + .then(() => this.menu); + } +} diff --git a/e2e/components/toolbar/toolbar-breadcrumb.ts b/e2e/components/toolbar/toolbar-breadcrumb.ts new file mode 100755 index 000000000..7b939d120 --- /dev/null +++ b/e2e/components/toolbar/toolbar-breadcrumb.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Component } from '../component'; + +export class ToolbarBreadcrumb extends Component { + private static selectors = { + root: 'adf-breadcrumb', + item: '.adf-breadcrumb-item', + currentItem: '.adf-breadcrumb-item-current' + }; + + items: ElementArrayFinder = this.component.all(by.css(ToolbarBreadcrumb.selectors.item)); + currentItem: ElementFinder = this.component.element(by.css(ToolbarBreadcrumb.selectors.currentItem)); + + constructor(ancestor?: ElementFinder) { + super(ToolbarBreadcrumb.selectors.root, ancestor); + } + + getNthItem(nth: number): ElementFinder { + return this.items.get(nth - 1); + } + + getNthItemName(nth: number) { + return this.getNthItem(nth).getText(); + } + + getItemsCount(): promise.Promise { + return this.items.count(); + } + + getAllItems() { + return this.items.map(elem => elem.getText().then(str => str.split('\nchevron_right')[0])); + } + + getFirstItemName(): promise.Promise { + return this.items.get(0).getText(); + } + + getCurrentItem() { + return this.currentItem; + } + + getCurrentItemName(): promise.Promise { + return this.currentItem.getText(); + } + + clickItem(name: string) { + return this.component.element(by.css(`${ToolbarBreadcrumb.selectors.item}[title=${name}]`)).click(); + } + + clickNthItem(nth: number) { + return this.getNthItem(nth).click(); + } + + getNthItemTooltip(nth: number) { + return this.getNthItem(nth).getAttribute('title'); + } +} diff --git a/e2e/components/toolbar/toolbar.ts b/e2e/components/toolbar/toolbar.ts new file mode 100755 index 000000000..7bfbcfba0 --- /dev/null +++ b/e2e/components/toolbar/toolbar.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder } from 'protractor'; +import { Component } from '../component'; +import { ToolbarActions } from './toolbar-actions'; +import { ToolbarBreadcrumb } from './toolbar-breadcrumb'; + +export class Toolbar extends Component { + private static selectors = { + root: '.inner-layout__header' + }; + + actions: ToolbarActions = new ToolbarActions(this.component); + breadcrumb: ToolbarBreadcrumb = new ToolbarBreadcrumb(this.component); + + constructor(ancestor?: ElementFinder) { + super(Toolbar.selectors.root, ancestor); + } +} diff --git a/e2e/configs.ts b/e2e/configs.ts new file mode 100755 index 000000000..3ac50a40d --- /dev/null +++ b/e2e/configs.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export const BROWSER_RESOLUTION_WIDTH = 1200; +export const BROWSER_RESOLUTION_HEIGHT = 800; + +export const BROWSER_WAIT_TIMEOUT = 30000; + +// Application configs +export const APP_HOST = 'http://localhost:3000'; + +// Repository configs +export const REPO_API_HOST = 'http://localhost:8080'; +export const REPO_API_TENANT = '-default-'; + +// Admin details +export const ADMIN_USERNAME = 'admin'; +export const ADMIN_PASSWORD = 'admin'; +export const ADMIN_FULL_NAME = 'Administrator'; + +// Application Routes +export const APP_ROUTES = { + FAVORITES: '/favorites', + FILE_LIBRARIES: '/libraries', + LOGIN: '/login', + LOGOUT: '/logout', + PERSONAL_FILES: '/personal-files', + RECENT_FILES: '/recent-files', + SHARED_FILES: '/shared', + TRASHCAN: '/trashcan' +}; + +// Sidebar labels +export const SIDEBAR_LABELS = { + PERSONAL_FILES: 'Personal Files', + FILE_LIBRARIES: 'File Libraries', + SHARED_FILES: 'Shared', + RECENT_FILES: 'Recent Files', + FAVORITES: 'Favorites', + TRASH: 'Trash' +}; + +// Site visibility +export const SITE_VISIBILITY = { + PUBLIC: 'PUBLIC', + MODERATED: 'MODERATED', + PRIVATE: 'PRIVATE' +}; + +// Site roles +export const SITE_ROLES = { + SITE_CONSUMER: 'SiteConsumer', + SITE_COLLABORATOR: 'SiteCollaborator', + SITE_CONTRIBUTOR: 'SiteContributor', + SITE_MANAGER: 'SiteManager' +}; diff --git a/e2e/pages/browsing-page.ts b/e2e/pages/browsing-page.ts new file mode 100755 index 000000000..d5eab3de1 --- /dev/null +++ b/e2e/pages/browsing-page.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { Header, DataTable, Pagination, Toolbar, Sidenav } from '../components/components'; +import { Page } from './page'; + +export class BrowsingPage extends Page { + header = new Header(this.app); + sidenav = new Sidenav(this.app); + toolbar = new Toolbar(this.app); + dataTable = new DataTable(this.app); + pagination = new Pagination(this.app); + + signOut(): promise.Promise { + return this.header.userInfo.signOut(); + } +} diff --git a/e2e/pages/login-page.ts b/e2e/pages/login-page.ts new file mode 100755 index 000000000..87fcf6981 --- /dev/null +++ b/e2e/pages/login-page.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +import { browser, ExpectedConditions as EC, promise } from 'protractor'; +import { LoginComponent } from '../components/components'; +import { Page } from './page'; +import { Utils } from '../utilities/utils'; + +import { + ADMIN_USERNAME, + ADMIN_PASSWORD, + BROWSER_WAIT_TIMEOUT, + APP_ROUTES +} from '../configs'; + +export class LoginPage extends Page { + login: LoginComponent = new LoginComponent(this.app); + + /** @override */ + constructor() { + super(APP_ROUTES.LOGIN); + } + + /** @override */ + load(): promise.Promise { + return super.load().then(() => { + const { submitButton } = this.login; + const hasSubmitButton = EC.presenceOf(submitButton); + + return browser.wait(hasSubmitButton, BROWSER_WAIT_TIMEOUT) + .then(() => Utils.clearLocalStorage()) + .then(() => browser.manage().deleteAllCookies()); + }); + } + + loginWith(username: string, password?: string): promise.Promise { + const pass = password || username; + return this.load() + .then(() => this.login.enterCredentials(username, pass).submit()) + .then(() => super.waitForApp()); + } + + loginWithAdmin(): promise.Promise { + return this.load() + .then(() => this.loginWith(ADMIN_USERNAME, ADMIN_PASSWORD)); + } + + tryLoginWith(username: string, password?: string): promise.Promise { + const pass = password || username; + return this.load() + .then(() => this.login.enterCredentials(username, pass).submit()) + .then(() => browser.wait(EC.presenceOf(this.login.errorMessage), BROWSER_WAIT_TIMEOUT)); + } +} diff --git a/e2e/pages/logout-page.ts b/e2e/pages/logout-page.ts new file mode 100755 index 000000000..b78cb0443 --- /dev/null +++ b/e2e/pages/logout-page.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { Page } from './page'; +import { APP_ROUTES } from '../configs'; +import { Utils } from '../utilities/utils'; + +export class LogoutPage extends Page { + /** @override */ + constructor() { + super(APP_ROUTES.LOGIN); + } + + /** @override */ + load(): promise.Promise { + return Utils.clearLocalStorage() + .then(() => Utils.clearSessionStorage()) + .then(() => super.load()); + } +} diff --git a/e2e/pages/page.ts b/e2e/pages/page.ts new file mode 100755 index 000000000..b2dce1d52 --- /dev/null +++ b/e2e/pages/page.ts @@ -0,0 +1,110 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, element, by, ElementFinder, promise, ExpectedConditions as EC } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from './../configs'; + +export abstract class Page { + private static USE_HASH_STRATEGY = true; + + private locators = { + app: by.css('app-root'), + layout: by.css('app-layout'), + overlay: by.css('.cdk-overlay-container'), + dialogContainer: by.css('.mat-dialog-container'), + snackBarContainer: '.cdk-overlay-pane snack-bar-container.mat-snack-bar-container', + snackBar: 'simple-snack-bar', + snackBarAction: 'button.mat-simple-snackbar-action' + }; + + public app: ElementFinder = element(this.locators.app); + public layout: ElementFinder = element(this.locators.layout); + public overlay: ElementFinder = element(this.locators.overlay); + snackBar: ElementFinder = browser.$(this.locators.snackBar); + dialogContainer: ElementFinder = element(this.locators.dialogContainer); + snackBarContainer: ElementFinder = browser.$(this.locators.snackBarContainer); + snackBarAction: ElementFinder = browser.$(this.locators.snackBarAction); + + constructor(public url: string = '') {} + + get title(): promise.Promise { + return browser.getTitle(); + } + + load(relativeUrl: string = ''): promise.Promise { + const hash = Page.USE_HASH_STRATEGY ? '/#' : ''; + const path = `${hash}${this.url}${relativeUrl}`; + + return browser.get(path); + } + + waitForApp() { + return browser.wait(EC.presenceOf(this.layout), BROWSER_WAIT_TIMEOUT); + } + + waitForSnackBarToAppear() { + return browser.wait(EC.visibilityOf(this.snackBarContainer), BROWSER_WAIT_TIMEOUT); + } + + waitForSnackBarToClose() { + return browser.wait(EC.not(EC.visibilityOf(this.snackBarContainer)), BROWSER_WAIT_TIMEOUT); + } + + waitForDialog() { + return browser.wait(EC.visibilityOf(this.dialogContainer), BROWSER_WAIT_TIMEOUT); + } + + waitForDialogToClose() { + return browser.wait(EC.not(EC.visibilityOf(this.dialogContainer)), BROWSER_WAIT_TIMEOUT); + } + + refresh(): promise.Promise { + return browser.refresh(); + } + + getDialogActionByLabel(label) { + return element(by.cssContainingText('.mat-button-wrapper', label)) + } + + isSnackBarDisplayed(): promise.Promise { + return this.snackBar.isDisplayed(); + } + + getSnackBarMessage(): promise.Promise { + return this.waitForSnackBarToAppear() + .then(() => this.snackBar.getAttribute('innerText')); + } + + getSnackBarAction() { + return this.waitForSnackBarToAppear() + .then(() => this.snackBarAction); + } + + clickSnackBarAction() { + return browser.executeScript(function (elem) { + elem.click(); + }, this.snackBarAction); + } +} diff --git a/e2e/pages/pages.ts b/e2e/pages/pages.ts new file mode 100755 index 000000000..196228230 --- /dev/null +++ b/e2e/pages/pages.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './browsing-page'; +export * from './login-page'; +export * from './logout-page'; diff --git a/e2e/suites/actions/create-folder.test.ts b/e2e/suites/actions/create-folder.test.ts new file mode 100755 index 000000000..d3b4024d8 --- /dev/null +++ b/e2e/suites/actions/create-folder.test.ts @@ -0,0 +1,278 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, SITE_VISIBILITY, SITE_ROLES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Create folder', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; + const folderName1 = `folder-${Utils.random()}`; + const folderName2 = `folder-${Utils.random()}`; + const folderDescription = 'description of my folder'; + const duplicateFolderName = `folder-${Utils.random()}`; + const nameWithSpaces = ` folder-${Utils.random()} `; + + const siteName = `site-private-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const createDialog = new CreateOrEditFolderDialog(); + const { dataTable } = personalFilesPage; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PRIVATE)) + .then(() => apis.admin.nodes.createFolders([ folderName1 ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.user.nodes.createFolders([ duplicateFolderName ], parent)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('option is enabled when having enough permissions', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openNewMenu()) + .then(menu => { + const isEnabled = menu.getItemByLabel('Create folder').isEnabled(); + expect(isEnabled).toBe(true, 'Create folder is not enabled'); + }); + }); + + it('creates new folder with name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(folderName1)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => { + const isPresent = dataTable.getRowName(folderName1).isPresent(); + expect(isPresent).toBe(true, 'Folder not displayed in list view'); + }); + }); + + it('creates new folder with name and description', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(folderName2)) + .then(() => createDialog.enterDescription(folderDescription)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => expect(dataTable.getRowName(folderName2).isPresent()).toBe(true, 'Folder not displayed')) + .then(() => apis.user.nodes.getNodeDescription(folderName2, parent)) + .then(desc => expect(desc).toEqual(folderDescription)); + }); + + it('enabled option tooltip', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openNewMenu()) + .then(menu => browser.actions().mouseMove(menu.getItemByLabel('Create folder')).perform() + .then(() => menu)) + .then(menu => { + expect(menu.getItemTooltip('Create folder')).toContain('Create new folder'); + }); + }); + + it('option is disabled when not enough permissions', () => { + const fileLibrariesPage = new BrowsingPage(); + + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(siteName)) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(folderName1)) + .then(() => fileLibrariesPage.sidenav.openNewMenu()) + .then(menu => { + const isEnabled = menu.getItemByLabel('Create folder').isEnabled(); + expect(isEnabled).toBe(false, 'Create folder is not disabled'); + }); + }); + + it('disabled option tooltip', () => { + const fileLibrariesPage = new BrowsingPage(); + + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(siteName)) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(folderName1)) + .then(() => fileLibrariesPage.sidenav.openNewMenu()) + .then(menu => browser.actions().mouseMove(menu.getItemByLabel('Create folder')).perform() + .then(() => menu)) + .then(menu => { + const tooltip = menu.getItemTooltip('Create folder'); + expect(tooltip).toContain(`You can't create a folder here`); + }); + }); + + it('dialog UI elements', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => { + const dialogTitle = createDialog.getTitle(); + const isFolderNameDisplayed = createDialog.nameInput.isDisplayed(); + const isDescriptionDisplayed = createDialog.descriptionTextArea.isDisplayed(); + const isCreateEnabled = createDialog.createButton.isEnabled(); + const isCancelEnabled = createDialog.cancelButton.isEnabled(); + + expect(dialogTitle).toMatch('Create new folder'); + expect(isFolderNameDisplayed).toBe(true, 'Name input is not displayed'); + expect(isDescriptionDisplayed).toBe(true, 'Description field is not displayed'); + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(isCancelEnabled).toBe(true, 'Cancel button is not enabled'); + }); + }); + + it('with empty folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.deleteNameWithBackspace()) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is enabled'); + expect(validationMessage).toMatch('Folder name is required'); + }); + }); + + it('with folder name ending with a dot "."', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName('folder-name.')) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toMatch(`Folder name can't end with a period .`); + }); + }); + + it('with folder name containing special characters', () => { + const namesWithSpecialChars = [ 'a*a', 'a"a', 'aa', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ]; + + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => namesWithSpecialChars.forEach(name => { + createDialog.enterName(name); + + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toContain(`Folder name can't contain these characters`); + })); + }); + + it('with folder name containing only spaces', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(' ')) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toMatch(`Folder name can't contain only spaces`); + }); + }); + + it('cancel folder creation', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName('test')) + .then(() => createDialog.enterDescription('test description')) + .then(() => createDialog.clickCancel()) + .then(() => { + expect(createDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'); + }); + }); + + it('duplicate folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(duplicateFolderName)) + .then(() => createDialog.clickCreate()) + .then(() => personalFilesPage.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`There's already a folder with this name. Try a different name.`); + expect(createDialog.component.isPresent()).toBe(true, 'dialog is not present'); + }); + }); + + it('trim ending spaces from folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(nameWithSpaces)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => { + const isPresent = dataTable.getRowName(nameWithSpaces.trim()).isPresent(); + expect(isPresent).toBe(true, 'Folder not displayed in list view'); + }); + }); +}); diff --git a/e2e/suites/actions/delete.test.ts b/e2e/suites/actions/delete.test.ts new file mode 100755 index 000000000..c401680ae --- /dev/null +++ b/e2e/suites/actions/delete.test.ts @@ -0,0 +1,503 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Delete content', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + afterAll(done => { + apis.admin.trashcan.emptyTrash().then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + const file1 = `file1-${Utils.random()}.txt`; let file1Id; + const file2 = `file2-${Utils.random()}.txt`; let file2Id; + const file3 = `file3-${Utils.random()}.txt`; + const file4 = `file4-${Utils.random()}.txt`; let file4Id; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder2 = `folder2-${Utils.random()}`; let folder2Id; + const fileLocked1 = `fileLocked-${Utils.random()}.txt`; let fileLocked1Id; + + beforeAll(done => { + apis.user.nodes.createFile(file1).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3, folder1Id)) + .then(() => apis.user.nodes.createFile(file4, folder2Id).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(file4Id)) + + .then(() => apis.user.nodes.createFile(fileLocked1).then(resp => fileLocked1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(fileLocked1Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(file4Id) + .then(() => apis.user.nodes.unlockFile(fileLocked1Id)) + .then(() => apis.user.nodes.deleteNodesById([file1Id, file2Id, folder1Id, folder2Id, fileLocked1Id])) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${file1} deleted`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(file1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + it('delete multiple files and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([file1, file2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, `${file1} was not removed from list`); + expect(dataTable.getRowName(file2).isPresent()).toBe(false, `${file2} was not removed from list`); + items = items - 2; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(file1).isPresent()).toBe(true, `${file1} is not in trash`); + expect(dataTable.getRowName(file2).isPresent()).toBe(true, `${file2} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(file1Id)) + .then(() => apis.user.trashcan.restore(file2Id)); + }); + + it('delete a folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(true, 'Item is not in trash'); + expect(dataTable.getRowName(file3).isPresent()).toBe(false, 'Item is in trash'); + }) + + .then(() => apis.user.trashcan.restore(folder1Id)); + }); + + it('delete a folder containing locked files', () => { + dataTable.clickOnItemName(folder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${folder2} couldn't be deleted`); + expect(dataTable.getRowName(folder2).isPresent()).toBe(true, 'Item was removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(folder2).isPresent()).toBe(false, 'Item is in trash'); + expect(dataTable.getRowName(file4).isPresent()).toBe(false, 'Item is in trash'); + }); + }); + + it('notification on multiple items deletion - some items fail to delete', () => { + dataTable.selectMultipleItems([file1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Deleted 1 item, 1 couldn't be deleted`)) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + it('Notification on multiple items deletion - all items fail to delete', () => { + dataTable.selectMultipleItems([fileLocked1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toEqual(`2 items couldn't be deleted`)); + }); + }); + + describe('on Shared Files', () => { + const sharedFile1 = `sharedFile1-${Utils.random()}.txt`; let sharedFile1Id; + const sharedFile2 = `sharedFile2-${Utils.random()}.txt`; let sharedFile2Id; + const sharedFile3 = `sharedFile3-${Utils.random()}.txt`; let sharedFile3Id; + + beforeAll(done => { + apis.user.nodes.createFile(sharedFile1).then(resp => sharedFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(sharedFile2).then(resp => sharedFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile3).then(resp => sharedFile3Id = resp.data.entry.id)) + .then(() => apis.user.shared.shareFilesByIds([sharedFile1Id, sharedFile2Id, sharedFile3Id])) + .then(() => apis.user.shared.waitForApi({ expect: 3 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([sharedFile1Id, sharedFile2Id, sharedFile3Id]) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + dataTable.clickOnItemName(sharedFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${sharedFile1} deleted`); + expect(dataTable.getRowName(sharedFile1).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(sharedFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(sharedFile1Id)); + }); + + it('delete multiple files and check notification', () => { + dataTable.selectMultipleItems([sharedFile2, sharedFile3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(false, `${sharedFile2} was not removed from list`); + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(false, `${sharedFile3} was not removed from list`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(true, `${sharedFile2} is not in trash`); + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(true, `${sharedFile3} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(sharedFile2Id)) + .then(() => apis.user.trashcan.restore(sharedFile3Id)); + }); + }); + + describe('on Favorites', () => { + const favoriteFile1 = `favFile1-${Utils.random()}.txt`; let favoriteFile1Id; + const favoriteFile2 = `favFile2-${Utils.random()}.txt`; let favoriteFile2Id; + const favoriteFile3 = `favFile3-${Utils.random()}.txt`; + const favoriteFile4 = `favFile4-${Utils.random()}.txt`; let favoriteFile4Id; + const favoriteFolder1 = `favFolder1-${Utils.random()}`; let favoriteFolder1Id; + const favoriteFolder2 = `favFolder2-${Utils.random()}`; let favoriteFolder2Id; + const favoriteFileLocked1 = `favFileLocked-${Utils.random()}.txt`; let favoriteFileLocked1Id; + + beforeAll(done => { + apis.user.nodes.createFile(favoriteFile1).then(resp => favoriteFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(favoriteFile2).then(resp => favoriteFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder1).then(resp => favoriteFolder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder2).then(resp => favoriteFolder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFile3, favoriteFolder1Id)) + .then(() => apis.user.nodes.createFile(favoriteFile4, favoriteFolder2Id).then(resp => favoriteFile4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFile4Id)) + + .then(() => apis.user.nodes.createFile(favoriteFileLocked1).then(resp => favoriteFileLocked1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFileLocked1Id)) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [favoriteFile1Id, favoriteFile2Id, favoriteFileLocked1Id])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [favoriteFolder1Id, favoriteFolder2Id])) + .then(() => apis.user.favorites.waitForApi({ expect: 5 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(favoriteFile4Id) + .then(() => apis.user.nodes.unlockFile(favoriteFileLocked1Id)) + .then(() => apis.user.nodes.deleteNodesById([ + favoriteFile1Id, favoriteFile2Id, favoriteFolder1Id, favoriteFolder2Id, favoriteFileLocked1Id + ])) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${favoriteFile1} deleted`); + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('delete multiple files and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([favoriteFile1, favoriteFile2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(false, `${favoriteFile1} was not removed from list`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(false, `${favoriteFile2} was not removed from list`); + items = items - 2; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, `${favoriteFile1} is not in trash`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(true, `${favoriteFile2} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)) + .then(() => apis.user.trashcan.restore(favoriteFile2Id)); + }); + + it('delete a folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + dataTable.clickOnItemName(favoriteFolder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(true, 'Item is not in trash'); + expect(dataTable.getRowName(favoriteFile3).isPresent()).toBe(false, 'Item is in trash'); + }) + + .then(() => apis.user.trashcan.restore(favoriteFolder1Id)); + }); + + it('delete a folder containing locked files', () => { + dataTable.clickOnItemName(favoriteFolder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${favoriteFolder2} couldn't be deleted`); + expect(dataTable.getRowName(favoriteFolder2).isPresent()).toBe(true, 'Item was removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFolder2).isPresent()).toBe(false, 'Item is in trash'); + expect(dataTable.getRowName(favoriteFile4).isPresent()).toBe(false, 'Item is in trash'); + }); + }); + + it('notification on multiple items deletion - some items fail to delete', () => { + dataTable.selectMultipleItems([favoriteFile1, favoriteFolder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 1 item, 1 couldn't be deleted`); + }) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('Notification on multiple items deletion - all items fail to delete', () => { + dataTable.selectMultipleItems([favoriteFileLocked1, favoriteFolder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`2 items couldn't be deleted`); + }); + }); + }); + + describe('on Recent Files', () => { + const recentFile1 = `recentFile1-${Utils.random()}.txt`; let recentFile1Id; + const recentFile2 = `recentFile2-${Utils.random()}.txt`; let recentFile2Id; + const recentFile3 = `recentFile3-${Utils.random()}.txt`; let recentFile3Id; + + beforeAll(done => { + apis.user.nodes.createFile(recentFile1).then(resp => recentFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(recentFile2).then(resp => recentFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile3).then(resp => recentFile3Id = resp.data.entry.id)) + .then(() => apis.user.search.waitForApi(username, { expect: 3 })) + + .then(() => loginPage.loginWith(username)) + + .then((): any => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.isEmptyList()) + .then(empty => { + if (empty) { + browser.sleep(6000).then(() => page.refresh()); + } + }) + ) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([recentFile1Id, recentFile2Id, recentFile3Id]) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + dataTable.clickOnItemName(recentFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${recentFile1} deleted`); + expect(dataTable.getRowName(recentFile1).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(recentFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(recentFile1Id)); + }); + + it('delete multiple files and check notification', () => { + dataTable.selectMultipleItems([recentFile2, recentFile3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(recentFile2).isPresent()).toBe(false, `${recentFile2} was not removed from list`); + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(false, `${recentFile3} was not removed from list`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(recentFile2).isPresent()).toBe(true, `${recentFile2} is not in trash`); + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(true, `${recentFile3} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(recentFile2Id)) + .then(() => apis.user.trashcan.restore(recentFile3Id)); + }); + }); +}); diff --git a/e2e/suites/actions/edit-folder.test.ts b/e2e/suites/actions/edit-folder.test.ts new file mode 100755 index 000000000..137dcfe3f --- /dev/null +++ b/e2e/suites/actions/edit-folder.test.ts @@ -0,0 +1,187 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { protractor, browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS, SITE_VISIBILITY, SITE_ROLES } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog'; +import { Utils } from '../../utilities/utils'; + +describe('Edit folder', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; + const folderName = `folder-${Utils.random()}`; + const folderDescription = 'my folder description'; + + const folderNameToEdit = `folder-${Utils.random()}`; + const duplicateFolderName = `folder-${Utils.random()}`; + + const folderNameEdited = `folder-${Utils.random()}`; + const folderDescriptionEdited = 'description edited'; + + const siteName = `site-private-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const editDialog = new CreateOrEditFolderDialog(); + const { dataTable } = personalFilesPage; + const editButton = personalFilesPage.toolbar.actions.getButtonByTitleAttribute('Edit'); + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PRIVATE)) + .then(() => apis.admin.nodes.createFolders([ folderName ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + + .then(() => apis.user.nodes.createFolder( parent )) + .then(resp => apis.user.nodes.createFolder( folderName, resp.data.entry.id, '', folderDescription )) + .then(() => apis.user.nodes.createFolders([ folderNameToEdit, duplicateFolderName ], parent)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('dialog UI defaults', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => { + expect(editDialog.getTitle()).toEqual('Edit folder'); + expect(editDialog.nameInput.getAttribute('value')).toBe(folderName); + expect(editDialog.descriptionTextArea.getAttribute('value')).toBe(folderDescription); + expect(editDialog.updateButton.isEnabled()).toBe(true, 'upload button is not enabled'); + expect(editDialog.cancelButton.isEnabled()).toBe(true, 'cancel button is not enabled'); + }); + }); + + it('properties are modified when pressing OK', () => { + dataTable.clickOnItemName(folderNameToEdit) + .then(() => editButton.click()) + .then(() => editDialog.waitForDialogToOpen()) + .then(() => editDialog.enterDescription(folderDescriptionEdited)) + .then(() => editDialog.enterName(folderNameEdited)) + .then(() => editDialog.clickUpdate()) + .then(() => editDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => expect(dataTable.getRowName(folderNameEdited).isPresent()).toBe(true, 'Folder not displayed')) + .then(() => apis.user.nodes.getNodeDescription(folderNameEdited, parent)) + .then(desc => expect(desc).toEqual(folderDescriptionEdited)); + }); + + it('with empty folder name', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.deleteNameWithBackspace()) + .then(() => { + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not enabled'); + expect(editDialog.getValidationMessage()).toMatch('Folder name is required'); + }); + }); + + it('with name with special characters', () => { + const namesWithSpecialChars = [ 'a*a', 'a"a', 'aa', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ]; + + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => namesWithSpecialChars.forEach(name => { + editDialog.enterName(name); + + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not disabled'); + expect(editDialog.getValidationMessage()).toContain(`Folder name can't contain these characters`); + })); + }); + + it('with name ending with a dot', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.nameInput.sendKeys('.')) + .then(() => { + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not enabled'); + expect(editDialog.getValidationMessage()).toMatch(`Folder name can't end with a period .`); + }); + }); + + it('Cancel button', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.clickCancel()) + .then(() => { + expect(editDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'); + }); + }); + + it('with duplicate folder name', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.enterName(duplicateFolderName)) + .then(() => editDialog.clickUpdate()) + .then(() => personalFilesPage.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`There's already a folder with this name. Try a different name.`); + expect(editDialog.component.isPresent()).toBe(true, 'dialog is not present'); + }); + }); + + it('trim ending spaces', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.nameInput.sendKeys(' ')) + .then(() => editDialog.clickUpdate()) + .then(() => editDialog.waitForDialogToClose()) + .then(() => { + expect(personalFilesPage.snackBar.isPresent()).not.toBe(true, 'notification appears'); + expect(dataTable.getRowName(folderName).isPresent()).toBe(true, 'Folder not displayed in list view'); + }); + }); +}); diff --git a/e2e/suites/actions/mark-favorite.test.ts b/e2e/suites/actions/mark-favorite.test.ts new file mode 100644 index 000000000..f6f79cf09 --- /dev/null +++ b/e2e/suites/actions/mark-favorite.test.ts @@ -0,0 +1,419 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; +import { browser } from 'protractor'; + +describe('Mark items as favorites', () => { + const username = `user-${Utils.random()}`; + + const file1NotFav = `file-${Utils.random()}.txt`; + const file2NotFav = `file-${Utils.random()}.txt`; + const file3Fav = `file-${Utils.random()}.txt`; + const file4Fav = `file-${Utils.random()}.txt`; + const folder1 = `folder-${Utils.random()}`; + + let file1Id, file2Id, file3Id, file4Id, folder1Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFile( file1NotFav ).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file2NotFav ).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file3Fav ).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file4Fav ).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder( folder1 ).then(resp => folder1Id = resp.data.entry.id)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ file1Id, file2Id, file3Id, file4Id, folder1Id ]), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('Favorite action has empty star icon for unfavorited item', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star_border')); + }); + + it('Favorite action has empty star icon for multiple selection of items when some are not favorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star_border')); + }); + + it('Favorite action has full star icon for favorited items', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star')); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('favorite a folder', () => { + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(folder1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${folder1} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(folder1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Recent Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Shared Files', () => { + beforeAll(done => { + apis.user.shared.shareFilesByIds([ file1Id, file2Id, file3Id, file4Id ]) + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Favorites', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => { + expect(isFavorite).toBe(false, 'item is marked as favorite'); + expect(dataTable.getRowName(file3Fav).isPresent()).toBe(false, 'item still displayed'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(resp => { + expect(resp).toBe(false, 'file3 marked as favorite'); + expect(dataTable.getRowName(file3Fav).isPresent()).toBe(false, 'file3 still displayed'); + }) + .then(() => apis.user.favorites.isFavorite(file4Id)) + .then(resp => { + expect(resp).toBe(false, 'file4 marked as favorite'); + expect(dataTable.getRowName(file4Fav).isPresent()).toBe(false, 'file4 still displayed'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + +}); diff --git a/e2e/suites/actions/permanently-delete.test.ts b/e2e/suites/actions/permanently-delete.test.ts new file mode 100755 index 000000000..1025feefd --- /dev/null +++ b/e2e/suites/actions/permanently-delete.test.ts @@ -0,0 +1,122 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Permanently delete from Trash', () => { + const username = `user-${Utils.random()}`; + + const file1 = `file-${Utils.random()}.txt`; + const file2 = `file-${Utils.random()}.txt`; + let filesIds; + + const folder1 = `folder-${Utils.random()}`; + const folder2 = `folder-${Utils.random()}`; + let foldersIds; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const trashPage = new BrowsingPage(); + const { dataTable, toolbar } = trashPage; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFiles([ file1, file2 ])) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder1, folder2 ])) + .then(resp => foldersIds = resp.data.list.entries.map(entries => entries.entry.id)) + + .then(() => apis.user.nodes.deleteNodesById(filesIds, false)) + .then(() => apis.user.nodes.deleteNodesById(foldersIds, false)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('delete file [C217094] [C217091] [C217092]', () => { + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`${file1} deleted`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); + + it('delete folder [C217091] [C217092]', () => { + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`${folder1} deleted`); + expect(dataTable.getRowName(folder1).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); + + it('delete multiple items [C217093]', () => { + dataTable.selectMultipleItems([ file2, folder2 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`2 items deleted`); + expect(dataTable.getRowName(file2).isPresent()).toBe(false, 'Item was not deleted'); + expect(dataTable.getRowName(folder2).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); +}); diff --git a/e2e/suites/actions/restore.test.ts b/e2e/suites/actions/restore.test.ts new file mode 100755 index 000000000..c6f379407 --- /dev/null +++ b/e2e/suites/actions/restore.test.ts @@ -0,0 +1,268 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { APP_ROUTES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Restore from Trash', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('successful restore', () => { + const file = `file-${Utils.random()}.txt`; let fileId; + const folder = `folder-${Utils.random()}`; let folderId; + + beforeAll(done => { + apis.user.nodes.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(folder).then(resp => folderId = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodesById([ fileId, folderId ], false)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.user.trashcan.emptyTrash().then(done); + }); + + it('restore file', () => { + dataTable.clickOnItemName(file) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`${file} restored`); + expect(text).toContain(`View`); + expect(dataTable.getRowName(file).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(file).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodeById(fileId, false)); + }); + + it('restore folder', () => { + dataTable.clickOnItemName(folder) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`${folder} restored`); + expect(text).toContain(`View`); + expect(dataTable.getRowName(folder).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(folder).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodeById(folderId, false)); + }); + + it('restore multiple items', () => { + dataTable.selectMultipleItems([ file, folder ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`Restore successful`); + expect(text).not.toContain(`View`); + expect(dataTable.getRowName(file).isPresent()).toBe(false, 'Item was not removed from list'); + expect(dataTable.getRowName(folder).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(file).isPresent()).toBe(true, 'Item not displayed in list'); + expect(page.dataTable.getRowName(folder).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodesById([ fileId, folderId ], false)); + }); + + it('View from notification', () => { + dataTable.clickOnItemName(file) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.clickSnackBarAction()) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.sidenav.isActiveByLabel('Personal Files')).toBe(true, 'Personal Files sidebar link not active'); + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }) + + .then(() => apis.user.nodes.deleteNodeById(fileId, false)); + }); + }); + + describe('failure to restore', () => { + const file1 = `file-${Utils.random()}.txt`; let file1Id1, file1Id2; + const file2 = `file-${Utils.random()}.txt`; let file2Id; + + const folder1 = `folder-${Utils.random()}`; let folder1Id; + const folder2 = `folder-${Utils.random()}`; let folder2Id; + + beforeAll(done => { + apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id1 = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file1Id1, false)) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id2 = resp.data.entry.id)) + + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file2, folder2Id).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file2Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder2Id, false)) + + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(file1Id2), + apis.user.trashcan.emptyTrash() + ]) + .then(done); + }); + + it('Restore a file when another file with same name exists on the restore location', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore, ${file1} already exists`)); + }); + + it('Restore a file when original location no longer exists', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore ${file2}, the original location no longer exists`)); + }); + + }); + + describe('Notification on partial success', () => { + const folder1 = `folder1-${Utils.random()}.txt`; let folder1Id; + const folder2 = `folder2-${Utils.random()}.txt`; let folder2Id; + const file1 = `file-${Utils.random()}.txt`; let file1Id; + const file2 = `file-${Utils.random()}.txt`; let file2Id; + + const folder3 = `folder3-${Utils.random()}.txt`; let folder3Id; + const folder4 = `folder4-${Utils.random()}.txt`; let folder4Id; + const file3 = `file3-${Utils.random()}.txt`; let file3Id; + const file4 = `file4-${Utils.random()}.txt`; let file4Id; + const file5 = `file5-${Utils.random()}.txt`; let file5Id; + + beforeAll(done => { + apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file2, folder2Id).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file1Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder1Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file2Id, false)) + + .then(() => apis.user.nodes.createFolder(folder3).then(resp => folder3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3, folder3Id).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file4, folder3Id).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder4).then(resp => folder4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file5, folder4Id).then(resp => file5Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file3Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file4Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder3Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file5Id, false)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('one failure', () => { + dataTable.selectMultipleItems([ file1, file2 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore ${file1}, the original location no longer exists`)); + }); + + it('multiple failures', () => { + dataTable.selectMultipleItems([ file3, file4, file5 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual('2 items not restored because of issues with the restore location')); + }); + }); +}); diff --git a/e2e/suites/actions/toolbar-multiple-selection.test.ts b/e2e/suites/actions/toolbar-multiple-selection.test.ts new file mode 100755 index 000000000..56c67ac1d --- /dev/null +++ b/e2e/suites/actions/toolbar-multiple-selection.test.ts @@ -0,0 +1,543 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, protractor } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Toolbar actions - multiple selection : ', () => { + const user1 = `user-${Utils.random()}`; + const user2 = `user-${Utils.random()}`; + + const file1 = `file-${Utils.random()}.txt`; + let file1Id; + const file2 = `file-${Utils.random()}.txt`; + let file2Id; + + const folder1 = `folder-${Utils.random()}`; + let folder1Id; + const folder2 = `folder-${Utils.random()}`; + let folder2Id; + + const fileForDelete1 = `file-${Utils.random()}.txt`; let fileForDelete1Id; + const fileForDelete2 = `file-${Utils.random()}.txt`; let fileForDelete2Id; + const folderForDelete1 = `folder-${Utils.random()}`; let folderForDelete1Id; + const folderForDelete2 = `folder-${Utils.random()}`; let folderForDelete2Id; + + const siteName = `site-private-${Utils.random()}`; + const file1Admin = `file-${Utils.random()}.txt`; + const file2Admin = `file-${Utils.random()}.txt`; + const folder1Admin = `folder-${Utils.random()}`; + const folder2Admin = `folder-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(user1, user1) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + const { toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(user1) + .then(() => apis.user.nodes.createFiles([ file1 ]).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ file2 ]).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder1 ]).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder2 ]).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileForDelete1 ]).then(resp => fileForDelete1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileForDelete2 ]).then(resp => fileForDelete2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderForDelete1 ]).then(resp => folderForDelete1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderForDelete2 ]).then(resp => folderForDelete2Id = resp.data.entry.id)) + + .then(() => apis.user.shared.shareFilesByIds([ file1Id, file2Id ])) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [ file1Id, file2Id ])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [ folder1Id, folder2Id ])) + + .then(() => apis.user.nodes.deleteNodesById([ + fileForDelete1Id, fileForDelete2Id, folderForDelete1Id, folderForDelete2Id + ], false)) + + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ file1Id, file2Id, folder1Id, folder2Id ]), + apis.user.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('Personal Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('unselect selected items - single click', () => { + dataTable.selectMultipleItems([ file1, file2, folder1, folder2 ]) + .then(() => expect(dataTable.countSelectedRows()).toEqual(4, 'incorrect selected rows number')) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => expect(dataTable.countSelectedRows()).toEqual(1, 'incorrect selected rows number')) + .then(() => dataTable.clearSelection()); + }); + + it('unselect selected items - CMD+click', () => { + dataTable.selectMultipleItems([ file1, file2, folder1, folder2 ]) + .then(() => expect(dataTable.countSelectedRows()).toEqual(4, 'incorrect selected rows number')) + .then(() => browser.actions().sendKeys(protractor.Key.COMMAND).perform()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => browser.actions().sendKeys(protractor.Key.NULL).perform()) + .then(() => expect(dataTable.countSelectedRows()).toEqual(2, 'incorrect selected rows number')) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1, file2, folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('File Libraries', () => { + beforeAll(done => { + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC) + .then(() => apis.admin.people.createUser(user2)) + .then(() => apis.admin.sites.addSiteMember(siteName, user1, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(siteName, user2, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ file1Admin, file2Admin ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.nodes.createFolders([ folder1Admin, folder2Admin ], `Sites/${siteName}/documentLibrary`)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(siteName)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.admin.sites.deleteSite(siteName).then(done); + }); + + xit(''); + + describe('user is Manager', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('user is Consumer', () => { + beforeAll(done => { + loginPage.loginWith(user2).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + }); + + describe('Shared Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('Recent Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('Favorites', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1, file2, folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + // [C217090] + describe('Trash', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([fileForDelete1, fileForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folderForDelete1, folderForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([fileForDelete1, fileForDelete2, folderForDelete1, folderForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + }); +}); diff --git a/e2e/suites/actions/toolbar-single-selection.test.ts b/e2e/suites/actions/toolbar-single-selection.test.ts new file mode 100755 index 000000000..61e20a22c --- /dev/null +++ b/e2e/suites/actions/toolbar-single-selection.test.ts @@ -0,0 +1,752 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Toolbar actions - single selection : ', () => { + const username = `user-${Utils.random()}`; + const username2 = `user-${Utils.random()}`; + + const fileUser = `file-${Utils.random()}.txt`; let fileUserId; + + const folderUser = `folder-${Utils.random()}`; let folderUserId; + + const fileForDelete = `file-${Utils.random()}.txt`; let fileForDeleteId; + + const folderForDelete = `folder-${Utils.random()}`; let folderForDeleteId; + + const siteName = `site-private-${Utils.random()}`; + const fileAdmin = `file-${Utils.random()}.txt`; + const folderAdmin = `folder-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFiles([ fileUser ])) + .then(resp => fileUserId = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileForDelete ])) + .then(resp => fileForDeleteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolders([ folderForDelete ])) + .then(resp => folderForDeleteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolders([ folderUser ])) + .then(resp => folderUserId = resp.data.entry.id) + .then(() => apis.user.shared.shareFileById(fileUserId)) + .then(() => apis.user.favorites.addFavoriteById('file', fileUserId)) + .then(() => apis.user.favorites.addFavoriteById('folder', folderUserId)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(fileUserId), + apis.user.nodes.deleteNodeById(folderUserId), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('General tests', () => { + const userSite = `site-${Utils.random()}`; + + beforeAll(done => { + apis.user.sites.createSite(userSite, SITE_VISIBILITY.PUBLIC) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.sites.deleteSite(userSite), + logoutPage.load() + ]) + .then(done); + }); + + it('actions not displayed for top level of File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(userSite)) + .then(() => expect(toolbar.actions.isEmpty()).toBe(true, 'toolbar not empty')); + }); + + it('selected row is marked with a check circle icon', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(fileUser)) + .then(() => expect(dataTable.hasCheckMarkIcon(fileUser)).toBe(true, 'check mark missing')); + }); + + describe('granular permissions', () => { + const site = `site-${Utils.random()}`; + const file1 = `file-${Utils.random()}`; let file1Id; + const file2 = `file-${Utils.random()}`; let file2Id; + + beforeAll(done => { + apis.admin.sites.createSite(site, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.nodes.createFiles([ file1 ], `Sites/${site}/documentLibrary`) + .then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.admin.nodes.createFiles([ file2 ], `Sites/${site}/documentLibrary`) + .then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.admin.sites.addSiteMember(site, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.setGranularPermission(file1Id, false, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.setGranularPermission(file2Id, false, username, SITE_ROLES.SITE_MANAGER)) + + .then(() => apis.user.shared.shareFileById(file1Id)) + .then(() => apis.admin.shared.shareFileById(file2Id)) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [file1Id, file2Id])) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(site), + logoutPage.load() + ]) + .then(done); + }); + + describe('actions update accordingly for files with different granular permissions', () => { + it('on File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(site)) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('on Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + // disabled until ACA-1184 is done + xit('on Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('correct actions are displayed when selecting multiple files with different granular permissions', () => { + it('on File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(site)) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('on Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + // disabled until ACA-1184 is done + xit('on Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + xit(''); + }); + }); + + describe('Personal Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('File Libraries', () => { + beforeAll(done => { + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC) + .then(() => apis.admin.people.createUser(username2)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(siteName, username2, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.nodes.createFolders([ folderAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(siteName)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.admin.sites.deleteSite(siteName).then(done); + }); + + xit(''); + + describe('user is Manager', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileAdmin}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderAdmin}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('user is Consumer', () => { + beforeAll(done => { + loginPage.loginWith(username2).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileAdmin}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderAdmin}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${folderAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + }); + + describe('Shared Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('Recent Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('Favorites', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + // [C217090] + describe('Trash', () => { + beforeAll(done => { + apis.user.nodes.deleteNodeById(fileForDeleteId, false) + .then(() => apis.user.nodes.deleteNodeById(folderForDeleteId, false)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.trashcan.permanentlyDelete(fileForDeleteId), + apis.user.trashcan.permanentlyDelete(folderForDeleteId), + logoutPage.load() + ]) + .then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileForDelete) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileForDelete}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderForDelete) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderForDelete}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileForDelete) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, `Permanently delete is not displayed for ${fileForDelete}`); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, `Restore is not displayed for ${fileForDelete}`); + }); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderForDelete) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, `Permanently delete is displayed for ${folderForDelete}`); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, `Restore is not enabled for ${folderForDelete}`); + }); + }); + }); +}); diff --git a/e2e/suites/actions/undo-delete.test.ts b/e2e/suites/actions/undo-delete.test.ts new file mode 100755 index 000000000..2409444d4 --- /dev/null +++ b/e2e/suites/actions/undo-delete.test.ts @@ -0,0 +1,423 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Undo delete content', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + afterAll(done => { + apis.admin.trashcan.emptyTrash().then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + const file1 = `file1-${Utils.random()}.txt`; let file1Id; + const file2 = `file2-${Utils.random()}.txt`; let file2Id; + const file3 = `file3-${Utils.random()}.txt`; let file3Id; + const file4 = `file4-${Utils.random()}.txt`; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder2 = `folder2-${Utils.random()}`; let folder2Id; + const fileLocked2 = `fileLocked2-${Utils.random()}.txt`; let fileLocked2Id; + + beforeAll(done => { + apis.user.nodes.createFile(file1).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file4, folder1Id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(fileLocked2, folder2Id).then(resp => fileLocked2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(fileLocked2Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(fileLocked2Id) + .then(() => apis.user.nodes.deleteNodesById([file1Id, file2Id, file3Id, folder1Id, folder2Id])) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Undo`); + }) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + it('Unsuccessful delete notification does not show Undo action', () => { + dataTable.clickOnItemName(folder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).not.toContain(`Undo`); + }); + }); + + it('Undo delete of file', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(file1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + + it('Undo delete of folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => dataTable.doubleClickOnItemName(folder1)) + .then(() => { + expect(dataTable.getRowName(file4).isPresent()).toBe(true, 'file from folder not restored'); + }); + }); + + it('undo delete of multiple files', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([file2, file3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(file2).isPresent()).toBe(true, `${file2} was not removed from list`); + expect(dataTable.getRowName(file3).isPresent()).toBe(true, `${file3} was not removed from list`); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + }); + + describe('on Shared Files', () => { + const sharedFile1 = `sharedFile1-${Utils.random()}`; let sharedFile1Id; + const sharedFile2 = `sharedFile2-${Utils.random()}`; let sharedFile2Id; + const sharedFile3 = `sharedFile3-${Utils.random()}`; let sharedFile3Id; + const sharedFile4 = `sharedFile4-${Utils.random()}`; let sharedFile4Id; + + beforeAll(done => { + apis.user.nodes.createFile(sharedFile1).then(resp => sharedFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(sharedFile2).then(resp => sharedFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile3).then(resp => sharedFile3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile4).then(resp => sharedFile4Id = resp.data.entry.id)) + .then(() => apis.user.shared.shareFilesByIds([sharedFile1Id, sharedFile2Id, sharedFile3Id, sharedFile4Id])) + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([sharedFile2Id, sharedFile3Id, sharedFile4Id]) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(sharedFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)); + }); + + it('Undo delete of file', () => { + dataTable.clickOnItemName(sharedFile2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(false, 'Item was not restored')); + }); + + it('undo delete of multiple files', () => { + dataTable.selectMultipleItems([sharedFile3, sharedFile4]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(false, `${sharedFile3} was not restored`); + expect(dataTable.getRowName(sharedFile4).isPresent()).toBe(false, `${sharedFile4} was not restored`); + }); + }); + }); + + describe('on Favorites', () => { + const favoriteFile1 = `favFile1-${Utils.random()}.txt`; let favoriteFile1Id; + const favoriteFile2 = `favFile2-${Utils.random()}.txt`; let favoriteFile2Id; + const favoriteFile4 = `favFile4-${Utils.random()}.txt`; + const favoriteFileLocked2 = `favFileLocked2-${Utils.random()}.txt`; let favoriteFileLocked2Id; + const favoriteFolder1 = `favFolder1-${Utils.random()}`; let favoriteFolder1Id; + const favoriteFolder2 = `favFolder2-${Utils.random()}`; let favoriteFolder2Id; + + beforeAll(done => { + apis.user.nodes.createFile(favoriteFile1).then(resp => favoriteFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(favoriteFile2).then(resp => favoriteFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder1).then(resp => favoriteFolder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFile4, favoriteFolder1Id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder2).then(resp => favoriteFolder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFileLocked2, favoriteFolder2Id) + .then(resp => favoriteFileLocked2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFileLocked2Id)) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [favoriteFile1Id, favoriteFile2Id])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [favoriteFolder1Id, favoriteFolder2Id])) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(favoriteFileLocked2Id) + .then(() => apis.user.nodes.deleteNodesById([favoriteFile1Id, favoriteFile2Id, favoriteFolder1Id, favoriteFolder2Id])) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('Unsuccessful delete notification does not show Undo action', () => { + dataTable.clickOnItemName(favoriteFolder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).not.toContain(`Undo`)); + }); + + it('Undo delete of file', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + + it('Undo delete of folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFolder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => dataTable.doubleClickOnItemName(favoriteFolder1)) + .then(() => expect(dataTable.getRowName(favoriteFile4).isPresent()).toBe(true, 'file from folder not restored')); + }); + + it('undo delete of multiple files', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([favoriteFile1, favoriteFile2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, `${favoriteFile1} was not removed from list`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(true, `${favoriteFile2} was not removed from list`); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + }); + + describe('on Recent Files', () => { + const recentFile1 = `recentFile1-${Utils.random()}.txt`; let recentFile1Id; + const recentFile2 = `recentFile2-${Utils.random()}.txt`; let recentFile2Id; + const recentFile3 = `recentFile3-${Utils.random()}.txt`; let recentFile3Id; + const recentFile4 = `recentFile4-${Utils.random()}.txt`; let recentFile4Id; + + beforeAll(done => { + apis.user.nodes.createFile(recentFile1).then(resp => recentFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(recentFile2).then(resp => recentFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile3).then(resp => recentFile3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile4).then(resp => recentFile4Id = resp.data.entry.id)) + .then(() => apis.user.search.waitForApi(username, { expect: 4 })) + + .then(() => loginPage.loginWith(username)) + + .then((): any => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.isEmptyList()) + .then(empty => { + if (empty) { + browser.sleep(6000).then(() => page.refresh()); + } + }) + ) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([recentFile2Id, recentFile3Id, recentFile4Id]) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(recentFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)); + }); + + // due to the fact that the search api is slow to update, + // we cannot test that the restored file is displayed in the Recent Files list + // without adding a very big browser.sleep followed by a page.refresh + // so for the moment we're testing that the restored file is not displayed in the Trash + it('Undo delete of file', () => { + dataTable.clickOnItemName(recentFile2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(recentFile2).isPresent()).toBe(false, 'Item is in Trash')); + }); + + // due to the fact that the search api is slow to update, + // we cannot test that the restored file is displayed in the Recent Files list + // without adding a very big browser.sleep followed by a page.refresh + // so for the moment we're testing that the restored file is not displayed in the Trash + it('undo delete of multiple files', () => { + dataTable.selectMultipleItems([recentFile3, recentFile4]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(false, `${recentFile3} is in Trash`); + expect(dataTable.getRowName(recentFile4).isPresent()).toBe(false, `${recentFile4} is in Trash`); + }); + }); + }); +}); diff --git a/e2e/suites/actions/upload-file.test.ts b/e2e/suites/actions/upload-file.test.ts new file mode 100755 index 000000000..7789d4366 --- /dev/null +++ b/e2e/suites/actions/upload-file.test.ts @@ -0,0 +1,74 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +// import { browser, protractor, promise } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Upload files', () => { + const username = `user-${Utils.random()}`; + + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + // apis.user.nodes.deleteNodeById(folder1Id), + logoutPage.load() + ]) + .then(done); + }); + + it('Upload a file', () => { + dataTable.doubleClickOnItemName(folder1) + .then(() => page.sidenav.openNewMenu()) + .then(() => page.sidenav.menu.uploadFile().sendKeys(`${__dirname}/create-folder.test.ts`)); + }); +}); diff --git a/e2e/suites/application/page-titles.test.ts b/e2e/suites/application/page-titles.test.ts new file mode 100755 index 000000000..07197ca50 --- /dev/null +++ b/e2e/suites/application/page-titles.test.ts @@ -0,0 +1,128 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; + +describe('Page titles', () => { + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + + xit(''); + + describe('on Login / Logout pages', () => { + it('on Login page', () => { + loginPage.load() + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + + it('after logout', () => { + loginPage.loginWithAdmin() + .then(() => page.signOut()) + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + + it('when pressing Back after Logout', () => { + loginPage.loginWithAdmin() + .then(() => page.signOut()) + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + }); + + describe('on list views', () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + afterAll(done => { + logoutPage.load() + .then(done); + }); + + it('Personal Files page', () => { + const label = SIDEBAR_LABELS.PERSONAL_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('File Libraries page', () => { + const label = SIDEBAR_LABELS.FILE_LIBRARIES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Shared Files page', () => { + const label = SIDEBAR_LABELS.SHARED_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Recent Files page', () => { + const label = SIDEBAR_LABELS.RECENT_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Favorites page', () => { + const label = SIDEBAR_LABELS.FAVORITES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Trash page', () => { + const label = SIDEBAR_LABELS.TRASH; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + }); +}); diff --git a/e2e/suites/authentication/login.test.ts b/e2e/suites/authentication/login.test.ts new file mode 100755 index 000000000..3ab8cdfab --- /dev/null +++ b/e2e/suites/authentication/login.test.ts @@ -0,0 +1,237 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { APP_ROUTES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Login', () => { + const peopleApi = new RepoClient().people; + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + + const testUser = `user-${Utils.random()}@alfness`; + + const russianUser = { + username: `пользвате${Utils.random()}`, + password: '密碼中國' + }; + + const johnDoe = { + username: `user-${Utils.random()}`, + get password() { return this.username; }, + firstName: 'John', + lastName: 'Doe' + }; + + const disabledUser = `user-${Utils.random()}`; + const testUser2 = { + username: `user-${Utils.random()}`, + password: 'user2 password' + }; + const newPassword = 'new password'; + + beforeAll(done => { + Promise + .all([ + peopleApi.createUser(testUser), + peopleApi.createUser(russianUser.username, russianUser.password), + peopleApi.createUser(johnDoe.username, johnDoe.password, { + firstName: johnDoe.firstName, + lastName: johnDoe.lastName + }), + peopleApi.createUser(disabledUser).then(() => peopleApi.disableUser(disabledUser)), + peopleApi.createUser(testUser2.username, testUser2.password) + ]) + .then(done); + }); + + afterEach(done => { + logoutPage.load() + .then(() => Utils.clearLocalStorage()) + .then(done); + }); + + xit(''); + + describe('general tests', () => { + beforeEach(done => { + loginPage.load().then(done); + }); + + it('login page default values', () => { + expect(loginPage.login.usernameInput.isEnabled()).toBe(true, 'username input is not enabled'); + expect(loginPage.login.passwordInput.isEnabled()).toBe(true, 'password input is not enabled'); + expect(loginPage.login.submitButton.isEnabled()).toBe(false, 'SIGN IN button is enabled'); + expect(loginPage.login.getPasswordVisibility()).toBe(false, 'Password is not hidden by default'); + }); + + it('change password visibility', () => { + loginPage.login.enterPassword('some password'); + expect(loginPage.login.isPasswordShown()).toBe(false, 'password is visible'); + loginPage.login.passwordVisibility.click() + .then(() => { + expect(loginPage.login.getPasswordVisibility()).toBe(true, 'Password visibility not changed'); + expect(loginPage.login.isPasswordShown()).toBe(true, 'password is not visible'); + }); + }); + }); + + describe('with valid credentials', () => { + it('navigate to "Personal Files"', () => { + const { username } = johnDoe; + + loginPage.loginWith(username) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it(`displays user's name in header`, () => { + const { userInfo } = new BrowsingPage(APP_ROUTES.PERSONAL_FILES).header; + const { username, firstName, lastName } = johnDoe; + + loginPage.loginWith(username) + .then(() => { + expect(userInfo.name).toEqual(`${firstName} ${lastName}`); + }); + }); + + it(`logs in with user having username containing "@"`, () => { + loginPage + .loginWith(testUser) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('logs in with user with non-latin characters', () => { + const { username, password } = russianUser; + + loginPage + .loginWith(username, password) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('redirects to Home Page when navigating to the Login page while already logged in', () => { + const { username } = johnDoe; + + loginPage + .loginWith(username) + .then(() => browser.get(APP_ROUTES.LOGIN) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }) + ); + }); + + it('redirects to Personal Files when pressing browser Back while already logged in ', () => { + const { username } = johnDoe; + + loginPage + .loginWith(username) + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('user is able to login after changing his password', () => { + loginPage.loginWith(testUser2.username, testUser2.password) + .then(() => logoutPage.load()) + .then(() => peopleApi.changePassword(testUser2.username, newPassword)) + .then(() => loginPage.loginWith(testUser2.username, newPassword)) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + }); + + describe('with invalid credentials', () => { + const { login: loginComponent } = loginPage; + const { submitButton, errorMessage } = loginComponent; + + beforeEach(done => { + loginPage.load().then(done); + }); + + it('disabled submit button when no credentials are entered', () => { + expect(submitButton.isEnabled()).toBe(false); + }); + + it('disabled submit button when password is empty', () => { + loginComponent.enterUsername('any-username'); + expect(submitButton.isEnabled()).toBe(false); + }); + + it('disabled submit button when username is empty', () => { + loginPage.login.enterPassword('any-password'); + expect(submitButton.isEnabled()).toBe(false); + }); + + it('shows error when entering nonexistent user', () => { + loginPage + .tryLoginWith('nonexistent-user', 'any-password') + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + + it('shows error when entering invalid password', () => { + const { username } = johnDoe; + + loginPage + .tryLoginWith(username, 'incorrect-password') + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + + it('unauthenticated user is redirected to Login page', () => { + browser.get(APP_ROUTES.PERSONAL_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('disabled user is not logged in', () => { + loginPage.tryLoginWith(disabledUser) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + }); +}); diff --git a/e2e/suites/authentication/logout.test.ts b/e2e/suites/authentication/logout.test.ts new file mode 100755 index 000000000..dfb79669a --- /dev/null +++ b/e2e/suites/authentication/logout.test.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { APP_ROUTES } from '../../configs'; + +describe('Logout', () => { + const page = new BrowsingPage(); + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + + const peopleApi = new RepoClient().people; + + const johnDoe = `user-${Utils.random()}`; + + beforeAll((done) => { + peopleApi + .createUser(johnDoe) + .then(done); + }); + + beforeEach((done) => { + loginPage.loginWith(johnDoe).then(done); + }); + + afterEach((done) => { + logoutPage.load().then(done); + }); + + it('Sign out option is available [C213143]', () => { + page.header.userInfo.openMenu() + .then(() => expect(page.header.userInfo.menu.isMenuItemPresent('Sign out')).toBe(true, 'Sign out option not displayed')); + }); + + it('redirects to Login page on sign out [C213144]', () => { + page.signOut() + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('redirects to Login page when pressing browser Back after logout [C213145]', () => { + page.signOut() + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('redirects to Login page when trying to access a part of the app after logout [C213146]', () => { + page.signOut() + .then(() => page.load('/favorites')) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); +}); diff --git a/e2e/suites/list-views/empty-list.test.ts b/e2e/suites/list-views/empty-list.test.ts new file mode 100755 index 000000000..dd9ea40cd --- /dev/null +++ b/e2e/suites/list-views/empty-list.test.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Empty list views', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('empty Personal Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyDragAndDropText()).toContain('Drag and drop'); + }); + }); + + it('empty File Libraries [C217099]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain(`You aren't a member of any File Libraries yet`); + expect(dataTable.getEmptyStateSubtitle()).toContain('Join sites to upload, view, and share files.'); + }); + }); + + it('empty Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No shared files or folders'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Items you share using the Share option are shown here.'); + }); + }); + + it('empty Recent Files [C213169]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No recent files'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Items you upload or edit in the last 30 days are shown here.'); + }); + }); + + it('empty Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No favorite files or folders'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Favorite items that you want to easily find later.'); + }); + }); + + it('empty Trash', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('Trash is empty'); + expect(dataTable.getEmptyStateText()).toContain('Items you delete are moved to the Trash.'); + expect(dataTable.getEmptyStateText()).toContain('Empty Trash to permanently delete items.'); + }); + }); +}); diff --git a/e2e/suites/list-views/favorites.test.ts b/e2e/suites/list-views/favorites.test.ts new file mode 100755 index 000000000..3cd8ae546 --- /dev/null +++ b/e2e/suites/list-views/favorites.test.ts @@ -0,0 +1,156 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Favorites', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const siteName = `site-${Utils.random()}`; + const folderName = `folder-${Utils.random()}`; + const fileName1 = `file1-${Utils.random()}.txt`; + const fileName2 = `file2-${Utils.random()}.txt`; + const fileName3 = `file3-${Utils.random()}.txt`; let file3Id; + const fileName4 = `file4-${Utils.random()}.txt`; let file4Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const favoritesPage = new BrowsingPage(); + const { dataTable } = favoritesPage; + const { breadcrumb } = favoritesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.nodes.createFiles([ fileName1 ], `Sites/${siteName}/documentLibrary`) + .then(resp => apis.user.favorites.addFavoriteById('file', resp.data.entry.id))) + .then(() => apis.user.nodes.createFolders([ folderName ]) + .then(resp => apis.user.favorites.addFavoriteById('folder', resp.data.entry.id))) + .then(() => apis.user.nodes.createFiles([ fileName2 ], folderName) + .then(resp => apis.user.favorites.addFavoriteById('file', resp.data.entry.id))) + .then(() => apis.user.nodes.createFiles([ fileName3 ], folderName) + .then(resp => file3Id = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.nodes.deleteNodeById(file3Id, false))) + .then(() => apis.user.nodes.createFiles([ fileName4 ], folderName) + .then(resp => file4Id = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)) + .then(() => apis.user.nodes.deleteNodeById(file4Id, false)) + .then(() => apis.user.trashcan.restore(file4Id))) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ folderName ]), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified', 'Modified by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(5 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the favorite files and folders [C213226]', () => { + expect(dataTable.countRows()).toEqual(4, 'Incorrect number of items displayed'); + expect(dataTable.getRowName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); + expect(dataTable.getRowName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); + expect(dataTable.getRowName(folderName).isPresent()).toBe(true, `${folderName} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213228]`, () => { + expect(dataTable.getRowName(fileName3).isPresent()).not.toBe(true, `${fileName3} is displayed`); + }); + + it(`file is displayed after it is restored from Trashcan [C213229]`, () => { + expect(dataTable.getRowName(fileName4).isPresent()).toBe(true, `${fileName4} not displayed`); + }); + + it('Location column displays the parent folder of the files [C213231]', () => { + expect(dataTable.getItemLocation(fileName1).getText()).toEqual(siteName); + expect(dataTable.getItemLocation(fileName2).getText()).toEqual(folderName); + expect(dataTable.getItemLocation(folderName).getText()).toEqual('Personal Files'); + }); + + it('Location column displays a tooltip with the entire path of the file [C213671]', () => { + expect(dataTable.getItemLocationTooltip(fileName1)).toEqual(`File Libraries/${siteName}`); + expect(dataTable.getItemLocationTooltip(fileName2)).toEqual(`Personal Files/${folderName}`); + expect(dataTable.getItemLocationTooltip(folderName)).toEqual('Personal Files'); + }); + + it('Location column redirect - item in user Home [C213650] [C260968]', () => { + dataTable.clickItemLocation(folderName) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in folder [C213650] [C260968]', () => { + dataTable.clickItemLocation(fileName2) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderName ])); + }); + + it('Location column redirect - file in site [C213650] [C260969]', () => { + dataTable.clickItemLocation(fileName1) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + + it('Navigate into folder from Favorites [C213230]', () => { + dataTable.doubleClickOnItemName(folderName) + .then(() => dataTable.waitForHeader()) + .then(() => breadcrumb.getCurrentItemName()) + .then(name => { + expect(name).toBe(folderName); + }); + }); + +}); diff --git a/e2e/suites/list-views/file-libraries.test.ts b/e2e/suites/list-views/file-libraries.test.ts new file mode 100755 index 000000000..3f6567ddd --- /dev/null +++ b/e2e/suites/list-views/file-libraries.test.ts @@ -0,0 +1,161 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { by } from 'protractor'; + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('File Libraries', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const sitePrivate = `private-${Utils.random()}`; + const siteModerated = `moderated-${Utils.random()}`; + const sitePublic = `public-${Utils.random()}`; + const siteName = `siteName-${Utils.random()}`; + const siteId1 = Utils.random(); + const siteId2 = Utils.random(); + const adminSite = `admin-${Utils.random()}`; + + const siteDescription = 'my site description'; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const fileLibrariesPage = new BrowsingPage(); + const { dataTable } = fileLibrariesPage; + + beforeAll(done => { + Promise + .all([ + apis.admin.people.createUser(username), + apis.admin.sites.createSite(sitePublic, SITE_VISIBILITY.PUBLIC), + apis.admin.sites.createSite(siteModerated, SITE_VISIBILITY.MODERATED, { description: siteDescription }), + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE, { description: '' }), + apis.admin.sites.createSite(adminSite, SITE_VISIBILITY.PUBLIC), + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC, { id: siteId1 }), + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC, { id: siteId2 }) + ]) + .then(() => apis.admin.sites.addSiteMember(sitePublic, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.sites.addSiteMember(siteModerated, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_CONTRIBUTOR)) + .then(() => apis.admin.sites.addSiteMember(siteId1, username, SITE_ROLES.SITE_CONTRIBUTOR)) + .then(() => apis.admin.sites.addSiteMember(siteId2, username, SITE_ROLES.SITE_CONTRIBUTOR)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSites([ + sitePublic, + siteModerated, + sitePrivate, + adminSite, + siteId1, + siteId2 + ]), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Title', 'Status' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(2 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('User can see only the sites he is a member of [C217095]', () => { + const sitesCount = dataTable.countRows(); + + const expectedSites = { + [sitePrivate]: SITE_VISIBILITY.PRIVATE, + [siteModerated]: SITE_VISIBILITY.MODERATED, + [sitePublic]: SITE_VISIBILITY.PUBLIC + }; + + expect(sitesCount).toEqual(5, 'Incorrect number of sites displayed'); + expect(dataTable.getRowName(adminSite).isPresent()).toBe(false, 'Incorrect site appears in list'); + + dataTable.getRows() + .map((row) => { + return row.all(dataTable.cell).map(cell => cell.getText()); + }) + .then((rowCells) => { + return rowCells.reduce((acc, cell) => { + acc[cell[1]] = cell[2].toUpperCase(); + return acc; + }, {}); + }) + .then((sitesList) => { + Object.keys(expectedSites).forEach((site) => { + expect(sitesList[site]).toEqual(expectedSites[site]); + }); + }); + }); + + it('Site ID is displayed when two sites have the same name [C217098]', () => { + const expectedSites = [ + `${siteName} (${siteId1})`, + `${siteName} (${siteId2})` + ]; + dataTable.getCellsContainingName(siteName) + .then(resp => { + const expectedJSON = JSON.stringify(expectedSites.sort()); + const actualJSON = JSON.stringify(resp.sort()); + expect(actualJSON).toEqual(expectedJSON); + }); + }); + + it('Tooltip for sites without description [C217096]', () => { + const tooltip = dataTable.getItemNameTooltip(sitePrivate); + expect(tooltip).toBe(`${sitePrivate}`); + }); + + it('Tooltip for sites with description [C217097]', () => { + const tooltip = dataTable.getItemNameTooltip(siteModerated); + expect(tooltip).toBe(`${siteDescription}`); + }); +}); diff --git a/e2e/suites/list-views/permissions.test.ts b/e2e/suites/list-views/permissions.test.ts new file mode 100755 index 000000000..9b637babb --- /dev/null +++ b/e2e/suites/list-views/permissions.test.ts @@ -0,0 +1,181 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Special permissions', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const recentFilesPage = new BrowsingPage(); + const favoritesPage = new BrowsingPage(); + const sharedPage = new BrowsingPage(); + const { dataTable } = recentFilesPage; + + xit(''); + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + describe('file not displayed if user no longer has permissions on it', () => { + const sitePrivate = `private-${Utils.random()}`; + const fileName = `file-${Utils.random()}.txt`; + let fileId; + + beforeAll(done => { + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR)) + .then(() => apis.admin.nodes.createFiles([ fileName ], `Sites/${sitePrivate}/documentLibrary`) + .then(resp => fileId = resp.data.entry.id)) + .then(() => apis.user.favorites.addFavoriteById('file', fileId)) + .then(() => apis.admin.shared.shareFileById(fileId)) + .then(() => apis.user.nodes.editNodeContent(fileId, 'edited by user')) + + .then(() => apis.user.search.waitForApi(username, { expect: 1 })) + .then(() => apis.user.shared.waitForApi({ expect: 1 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterEach(done => { + apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR).then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(sitePrivate), + logoutPage.load() + ]) + .then(done); + }); + + it('on Recent Files [C213173]', () => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => recentFilesPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + + it('on Favorites [C213227]', () => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => favoritesPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + + it('on Shared Files [C213116]', () => { + sharedPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => sharedPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + }); + + describe(`Location column is empty if user doesn't have permissions on the file's parent folder`, () => { + const sitePrivate = `private-${Utils.random()}`; + const fileName = `file-${Utils.random()}.txt`; + let fileId; + + beforeAll(done => { + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR)) + .then(() => apis.admin.sites.getDocLibId(sitePrivate)) + .then(resp => apis.user.nodes.createFile(fileName, resp)) + .then(resp => fileId = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', fileId)) + .then(() => apis.user.shared.shareFileById(fileId)) + .then(() => apis.user.shared.waitForApi({ expect: 1 })) + .then(() => apis.user.search.waitForApi(username, { expect: 1 })) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(sitePrivate), + logoutPage.load() + ]) + .then(done); + }); + + it(`on Recent Files [C213178]`, () => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + + it(`on Favorites [C213672]`, () => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + + it(`on Shared Files [C213668]`, () => { + sharedPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + }); +}); diff --git a/e2e/suites/list-views/personal-files.test.ts b/e2e/suites/list-views/personal-files.test.ts new file mode 100755 index 000000000..88bdd95de --- /dev/null +++ b/e2e/suites/list-views/personal-files.test.ts @@ -0,0 +1,176 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, APP_ROUTES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Personal Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const { dataTable } = personalFilesPage; + + const adminFolder = `admin-folder-${Utils.random()}`; + + const userFolder = `user-folder-${Utils.random()}`; + const userFile = `file-${Utils.random()}.txt`; + + beforeAll(done => { + Promise + .all([ + apis.admin.people.createUser(username), + apis.admin.nodes.createFolders([ adminFolder ]) + ]) + .then(() => apis.user.nodes.createFolders([ userFolder ])) + .then(() => apis.user.nodes.createFiles([ userFile ], userFolder)) + .then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.nodes.deleteNodes([ adminFolder ]), + apis.user.nodes.deleteNodes([ userFolder ]) + ]) + .then(done); + }); + + xit(''); + + describe(`Admin user's personal files`, () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has "Data Dictionary" folder [C213241]', () => { + expect(dataTable.getRowName('Data Dictionary').isPresent()).toBe(true); + }); + + it('has created content', () => { + expect(dataTable.getRowName(adminFolder).isPresent()).toBe(true); + }); + }); + + describe(`Regular user's personal files`, () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns [C217142]', () => { + const labels = [ 'Name', 'Size', 'Modified', 'Modified by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(4 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('has default sorted column [C217143]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + }); + + it('has user created content [C213242]', () => { + expect(dataTable.getRowName(userFolder).isPresent()) + .toBe(true); + }); + + it('navigates to folder [C213244]', () => { + const getNodeIdPromise = apis.user.nodes + .getNodeByPath(`/${userFolder}`) + .then(response => response.data.entry.id); + + const navigatePromise = dataTable + .doubleClickOnItemName(userFolder) + .then(() => dataTable.waitForHeader()); + + Promise + .all([ + getNodeIdPromise, + navigatePromise + ]) + .then(([ nodeId ]) => { + expect(browser.getCurrentUrl()) + .toContain(nodeId, 'Node ID is not in the URL'); + + expect(dataTable.getRowName(userFile).isPresent()) + .toBe(true, 'user file is missing'); + }); + }); + + it('redirects to Personal Files on clicking the link from sidebar [C213245]', () => { + personalFilesPage.dataTable.doubleClickOnItemName(userFolder) + .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => browser.getCurrentUrl()) + .then(url => expect(url.endsWith(APP_ROUTES.PERSONAL_FILES)).toBe(true, 'incorrect url')); + }); + + it('page loads correctly after browser refresh [C213246]', () => { + personalFilesPage.refresh() + .then(() => expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES)); + }); + + it('page load by URL [C213247]', () => { + let url; + browser.getCurrentUrl() + .then(resp => url = resp) + .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => browser.get(url)) + .then(() => expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES)); + }); + }); +}); diff --git a/e2e/suites/list-views/recent-files.test.ts b/e2e/suites/list-views/recent-files.test.ts new file mode 100755 index 000000000..0902c7c70 --- /dev/null +++ b/e2e/suites/list-views/recent-files.test.ts @@ -0,0 +1,141 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Recent Files', () => { + const username = `user-${Utils.random()}`; + + const folderName = `folder-${Utils.random()}`; let folderId; + const fileName1 = `file-${Utils.random()}.txt`; + const fileName2 = `file-${Utils.random()}.txt`; let file2Id; + const fileName3 = `file-${Utils.random()}.txt`; + + const siteName = `site-${Utils.random()}`; + const folderSite = `folder2-${Utils.random()}`; let folderSiteId; + const fileSite = `file-${Utils.random()}.txt`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const recentFilesPage = new BrowsingPage(); + const { dataTable } = recentFilesPage; + const { breadcrumb } = recentFilesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolders([ folderName ])).then(resp => folderId = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileName1 ], folderName)) + .then(() => apis.user.nodes.createFiles([ fileName2 ])).then(resp => file2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileName3 ]).then(resp => apis.user.nodes.deleteNodeById(resp.data.entry.id, false))) + + .then(() => apis.user.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.user.sites.getDocLibId(siteName)) + .then(resp => apis.user.nodes.createFolder(folderSite, resp)).then(resp => folderSiteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileSite, folderSiteId)) + + .then(() => apis.user.search.waitForApi(username, { expect: 3 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ folderId, file2Id ]), + apis.user.sites.deleteSite(siteName), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns [C213168]', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(4 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('default sorting column [C213171]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('displays the files added by the current user in the last 30 days [C213170]', () => { + expect(dataTable.countRows()).toEqual(3, 'Incorrect number of files displayed'); + expect(dataTable.getRowName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); + expect(dataTable.getRowName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213174]`, () => { + expect(dataTable.getRowName(fileName3).isPresent()).not.toBe(true, `${fileName3} is displayed`); + }); + + it('Location column displays the parent folder of the file [C213175]', () => { + expect(dataTable.getItemLocation(fileName1).getText()).toEqual(folderName); + expect(dataTable.getItemLocation(fileName2).getText()).toEqual('Personal Files'); + expect(dataTable.getItemLocation(fileSite).getText()).toEqual(folderSite); + }); + + it('Location column displays a tooltip with the entire path of the file [C213177]', () => { + expect(dataTable.getItemLocationTooltip(fileName1)).toEqual(`Personal Files/${folderName}`); + expect(dataTable.getItemLocationTooltip(fileName2)).toEqual('Personal Files'); + expect(dataTable.getItemLocationTooltip(fileSite)).toEqual(`File Libraries/${siteName}/${folderSite}`); + }); + + it('Location column redirect - file in user Home [C213176] [C260968]', () => { + dataTable.clickItemLocation(fileName2) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in folder [C213176] [C260968]', () => { + dataTable.clickItemLocation(fileName1) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderName ])); + }); + + it('Location column redirect - file in site [C213176] [C260969]', () => { + dataTable.clickItemLocation(fileSite) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName, folderSite ])); + }); +}); diff --git a/e2e/suites/list-views/shared-files.test.ts b/e2e/suites/list-views/shared-files.test.ts new file mode 100755 index 000000000..b8168d3c0 --- /dev/null +++ b/e2e/suites/list-views/shared-files.test.ts @@ -0,0 +1,142 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Shared Files', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const siteName = `site-${Utils.random()}`; + const fileAdmin = `file-${Utils.random()}.txt`; + + const folderUser = `folder-${Utils.random()}`; + const file1User = `file1-${Utils.random()}.txt`; let file1Id; + const file2User = `file2-${Utils.random()}.txt`; let file2Id; + const file3User = `file3-${Utils.random()}.txt`; let file3Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const sharedFilesPage = new BrowsingPage(); + const { dataTable } = sharedFilesPage; + const { breadcrumb } = sharedFilesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(resp => apis.admin.shared.shareFileById(resp.data.entry.id)) + + .then(() => apis.user.nodes.createFolders([ folderUser ])) + .then(() => apis.user.nodes.createFiles([ file1User ], folderUser)).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2User)).then(resp => file2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file3User)).then(resp => file3Id = resp.data.entry.id) + .then(() => apis.user.shared.shareFilesByIds([file1Id, file2Id, file3Id])) + + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + .then(() => apis.user.nodes.deleteNodeById(file2Id)) + .then(() => apis.user.shared.unshareFile(file3User)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + sharedFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + sharedFilesPage.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ folderUser ]), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns [C213113]', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified', 'Modified by', 'Shared by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(6 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('default sorting column [C213115]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('displays the files shared by everyone [C213114]', () => { + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); + expect(dataTable.getRowName(file1User).isPresent()).toBe(true, `${file1User} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213117]`, () => { + expect(dataTable.getRowName(file2User).isPresent()).toBe(false, `${file2User} is displayed`); + }); + + it('unshared file is not displayed [C213118]', () => { + expect(dataTable.getRowName(file3User).isPresent()).toBe(false, `${file3User} is displayed`); + }); + + it('Location column displays the parent folder of the file [C213665]', () => { + expect(dataTable.getItemLocation(fileAdmin).getText()).toEqual(siteName); + expect(dataTable.getItemLocation(file1User).getText()).toEqual(folderUser); + }); + + it('Location column redirect - file in user Home [C213666] [C260968]', () => { + dataTable.clickItemLocation(file1User) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderUser ])); + }); + + it('Location column redirect - file in site [C213666] [C260969]', () => { + dataTable.clickItemLocation(fileAdmin) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + + it('Location column displays a tooltip with the entire path of the file [C213667]', () => { + expect(dataTable.getItemLocationTooltip(fileAdmin)).toEqual(`File Libraries/${siteName}`); + expect(dataTable.getItemLocationTooltip(file1User)).toEqual(`Personal Files/${folderUser}`); + }); +}); diff --git a/e2e/suites/list-views/tooltips.test.ts b/e2e/suites/list-views/tooltips.test.ts new file mode 100755 index 000000000..43e12985b --- /dev/null +++ b/e2e/suites/list-views/tooltips.test.ts @@ -0,0 +1,330 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('File / folder tooltips', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const parent = `parent-${Utils.random()}`; + + const file = `file1-${Utils.random()}`; + const fileWithDesc = `file2-${Utils.random()}`; + const fileWithTitle = `file3-${Utils.random()}`; + const fileWithTitleAndDesc = `file4-${Utils.random()}`; + const fileNameEqTitleEqDesc = `file5-${Utils.random()}`; + const fileNameEqTitleDiffDesc = `file6-${Utils.random()}`; + const fileNameEqDescDiffTitle = `file7-${Utils.random()}`; + const fileTitleEqDesc = `file8-${Utils.random()}`; + let parentId, file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id; + + const fileTitle = 'file title'; + const fileDescription = 'file description'; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder( parent )) + .then(resp => parentId = resp.data.entry.id) + + .then(() => Promise.all([ + apis.user.nodes.createFile(file, parentId).then(resp => file1Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithDesc, parentId, '', fileDescription).then(resp => file2Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitle, parentId, fileTitle).then(resp => file3Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitleAndDesc, parentId, fileTitle, fileDescription) + .then(resp => file4Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleEqDesc, parentId, fileNameEqTitleEqDesc, fileNameEqTitleEqDesc) + .then(resp => file5Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleDiffDesc, parentId, fileNameEqTitleDiffDesc, fileDescription) + .then(resp => file6Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqDescDiffTitle, parentId, fileTitle, fileNameEqDescDiffTitle) + .then(resp => file7Id = resp.data.entry.id), + apis.user.nodes.createFile(fileTitleEqDesc, parentId, fileTitle, fileTitle).then(resp => file8Id = resp.data.entry.id) + ])) + + .then(() => apis.user.shared.shareFilesByIds([ file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id ])) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [ + file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id + ])) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodes([ parent ]), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Recent Files', () => { + beforeAll(done => { + apis.user.search.waitForApi(username, { expect: 8 }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + // disabled until ACA-518 is done + xdescribe('on Shared Files', () => { + beforeAll(done => { + apis.user.shared.waitForApi({ expect: 8 }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Favorites', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES).then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Trash', () => { + const parentForTrash = `parent-${Utils.random()}`; + let parentForTrashId, file1TrashId, file2TrashId, file3TrashId, file4TrashId; + let file5TrashId, file6TrashId, file7TrashId, file8TrashId; + + beforeAll(done => { + apis.user.nodes.createFolder( parentForTrash ) + .then(resp => parentForTrashId = resp.data.entry.id) + .then(() => Promise.all([ + apis.user.nodes.createFile(file, parentForTrashId) + .then(resp => file1TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithDesc, parentForTrashId, '', fileDescription) + .then(resp => file2TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitle, parentForTrashId, fileTitle) + .then(resp => file3TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitleAndDesc, parentForTrashId, fileTitle, fileDescription) + .then(resp => file4TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleEqDesc, parentForTrashId, fileNameEqTitleEqDesc, fileNameEqTitleEqDesc) + .then(resp => file5TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleDiffDesc, parentForTrashId, fileNameEqTitleDiffDesc, fileDescription) + .then(resp => file6TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqDescDiffTitle, parentForTrashId, fileTitle, fileNameEqDescDiffTitle) + .then(resp => file7TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileTitleEqDesc, parentForTrashId, fileTitle, fileTitle) + .then(resp => file8TrashId = resp.data.entry.id) + ])) + + .then(() => apis.user.nodes.deleteNodesById([ + file1TrashId, file2TrashId, file3TrashId, file4TrashId, file5TrashId, file6TrashId, file7TrashId, file8TrashId + ], false)) + + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(done); + }); + + afterAll(done => { + apis.user.nodes.deleteNodes([ parentForTrash ]).then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); +}); diff --git a/e2e/suites/list-views/trash.test.ts b/e2e/suites/list-views/trash.test.ts new file mode 100755 index 000000000..8af8827bf --- /dev/null +++ b/e2e/suites/list-views/trash.test.ts @@ -0,0 +1,169 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Trash', () => { + const username = `user-${Utils.random()}`; + + const siteName = `site-${Utils.random()}`; + const fileSite = `file-${Utils.random()}.txt`; let fileSiteId; + + const folderAdmin = `folder-${Utils.random()}`; let folderAdminId; + const fileAdmin = `file-${Utils.random()}.txt`; let fileAdminId; + + const folderUser = `folder-${Utils.random()}`; let folderUserId; + const fileUser = `file-${Utils.random()}.txt`; let fileUserId; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const trashPage = new BrowsingPage(); + const { dataTable } = trashPage; + const { breadcrumb } = trashPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ]).then(resp => fileAdminId = resp.data.entry.id)) + .then(() => apis.admin.nodes.createFolders([ folderAdmin ]).then(resp => folderAdminId = resp.data.entry.id)) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.nodes.createFiles([ fileSite ], `Sites/${siteName}/documentLibrary`) + .then(resp => fileSiteId = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileUser ]).then(resp => fileUserId = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderUser ]).then(resp => folderUserId = resp.data.entry.id)) + + .then(() => apis.admin.nodes.deleteNodesById([ fileAdminId, folderAdminId ], false)) + .then(() => apis.user.nodes.deleteNodesById([ fileSiteId, fileUserId, folderUserId ], false)) + + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.admin.trashcan.emptyTrash() + ]) + .then(done); + }); + + xit(''); + + describe('as admin', () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Deleted', 'Deleted by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(5 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the files and folders deleted by everyone [C213217]', () => { + expect(dataTable.countRows()).toEqual(5, 'Incorrect number of deleted items displayed'); + + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); + expect(dataTable.getRowName(folderAdmin).isPresent()).toBe(true, `${folderAdmin} not displayed`); + expect(dataTable.getRowName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); + expect(dataTable.getRowName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + }); + }); + + describe('as user', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Deleted', 'Deleted by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(5 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the files and folders deleted by the user [C213218]', () => { + expect(dataTable.countRows()).toEqual(3, 'Incorrect number of deleted items displayed'); + + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + expect(dataTable.getRowName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); + expect(dataTable.getRowName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(false, `${fileAdmin} is displayed`); + }); + + it('default sorting column [C213219]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Deleted'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('Location column redirect - file in user Home [C217144] [C260968]', () => { + dataTable.clickItemLocation(fileUser) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in site [C217144] [C260969]', () => { + dataTable.clickItemLocation(fileSite) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + }); +}); diff --git a/e2e/suites/navigation/breadcrumb.test.ts b/e2e/suites/navigation/breadcrumb.test.ts new file mode 100755 index 000000000..419950ed3 --- /dev/null +++ b/e2e/suites/navigation/breadcrumb.test.ts @@ -0,0 +1,244 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Breadcrumb', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; let parentId; + const subfolder1 = `subfolder1-${Utils.random()}`; let subfolder1Id; + const subfolder2 = `subfolder2-${Utils.random()}`; let subfolder2Id; + const fileName1 = `file1-${Utils.random()}.txt`; + + const siteName = `site-${Utils.random()}`; + + const parent2 = `parent2-${Utils.random()}`; let parent2Id; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder1Renamed = `renamed-${Utils.random()}`; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { breadcrumb } = page.toolbar; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder(parent)).then(resp => parentId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subfolder1, parentId)).then(resp => subfolder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subfolder2, subfolder1Id)).then(resp => subfolder2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileName1, subfolder2Id)) + + .then(() => apis.user.nodes.createFolder(parent2)).then(resp => parent2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(folder1, parent2Id)).then(resp => folder1Id = resp.data.entry.id) + + .then(() => apis.user.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.user.sites.getDocLibId(siteName)) + .then(resp => apis.user.nodes.createFolder(parent, resp)).then(resp => parentId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subfolder1, parentId)).then(resp => subfolder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subfolder2, subfolder1Id)).then(resp => subfolder2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileName1, subfolder2Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(parentId), + apis.user.nodes.deleteNodeById(parent2Id), + apis.user.sites.deleteSite(siteName), + logoutPage.load() + ]) + .then(done); + }); + + it('Personal Files breadcrumb main node [C260964]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Personal Files'); + }); + }); + + it('File Libraries breadcrumb main node [C260966]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('File Libraries'); + }); + }); + + it('Recent Files breadcrumb main node [C260971]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Recent Files'); + }); + }); + + it('Shared Files breadcrumb main node [C260972]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Shared Files'); + }); + }); + + it('Favorites breadcrumb main node [C260973]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Favorites'); + }); + }); + + it('Trash breadcrumb main node [C260974]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Trash'); + }); + }); + + it('Personal Files breadcrumb for a folder hierarchy [C260965]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder2)) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subfolder1, subfolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + it('File Libraries breadcrumb for a folder hierarchy [C260967]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(siteName)) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder2)) + .then(() => { + const expectedItems = [ 'File Libraries', siteName, parent, subfolder1, subfolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedItems); + }); + }); + + it('User can navigate to any location by clicking on a step from the breadcrumb [C213235]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder2)) + .then(() => breadcrumb.clickItem(subfolder1)) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subfolder1 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + it('Tooltip appears on hover on a step in breadcrumb [C213237]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder2)) + .then(() => { + expect(breadcrumb.getNthItemTooltip(3)).toEqual(subfolder1); + }); + }); + + it('Breadcrumb updates correctly when folder is renamed [C213238]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent2)) + .then(() => page.dataTable.doubleClickOnItemName(folder1)) + .then(() => page.dataTable.wait()) + .then(() => apis.user.nodes.renameNode(folder1Id, folder1Renamed).then(done => done)) + .then(() => page.refresh()) + .then(() => page.dataTable.wait()) + .then(() => { + expect(breadcrumb.getCurrentItemName()).toEqual(folder1Renamed); + }); + }); + + it('Browser back navigates to previous location regardless of breadcrumb steps [C213240]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subfolder2)) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => page.dataTable.waitForEmptyState()) + .then(() => browser.navigate().back()) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subfolder1, subfolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + // disabled cause of ACA-1039 + xdescribe('as admin', () => { + const user2 = 'a_user'; + const userFolder = `userFolder-${Utils.random()}`; let userFolderId; + const user2Api = new RepoClient(user2, user2); + + beforeAll(done => { + logoutPage.load() + .then(() => apis.admin.people.createUser(user2)) + .then(() => user2Api.nodes.createFolder(userFolder).then(resp => userFolderId = resp.data.entry.id)) + .then(() => loginPage.loginWithAdmin()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + user2Api.nodes.deleteNodeById(userFolderId), + logoutPage.load() + ]) + .then(done); + }); + + it(`Breadcrumb on navigation to a user's home [C260970]`, () => { + page.dataTable.doubleClickOnItemName('User Homes') + .then(() => page.dataTable.doubleClickOnItemName(user2)) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', 'User Homes', user2 ])) + .then(() => page.dataTable.doubleClickOnItemName(userFolder)) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', 'User Homes', user2, userFolder ])); + }); + }); +}); diff --git a/e2e/suites/navigation/sidebar.test.ts b/e2e/suites/navigation/sidebar.test.ts new file mode 100755 index 000000000..531fc36d2 --- /dev/null +++ b/e2e/suites/navigation/sidebar.test.ts @@ -0,0 +1,139 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { APP_ROUTES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; + +describe('Sidebar', () => { + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { sidenav } = page; + + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has "Personal Files" as default', () => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + expect(sidenav.isActiveByLabel('Personal Files')).toBe(true, 'Active link'); + }); + + it('navigates to "File Libraries"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.FILE_LIBRARIES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true); + }); + }); + + it('navigates to "Personal Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.PERSONAL_FILES)).toBe(true); + }); + }); + + it('navigates to "Shared Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.SHARED_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.SHARED_FILES)).toBe(true); + }); + }); + + it('navigates to "Recent Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.RECENT_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.RECENT_FILES)).toBe(true); + }); + }); + + it('navigates to "Favorites"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.FAVORITES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FAVORITES)).toBe(true); + }); + }); + + it('navigates to "Trash"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.TRASHCAN); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.TRASH)).toBe(true); + }); + }); + + it('Personal Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.PERSONAL_FILES)).toContain('View your Personal Files'); + }); + }); + + it('File Libraries tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.FILE_LIBRARIES)).toContain('Access File Libraries'); + }); + }); + + it('Shared Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.SHARED_FILES)).toContain('View files that have been shared'); + }); + }); + + it('Recent Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.RECENT_FILES)).toContain('View files you recently edited'); + }); + }); + + it('Favorites tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.FAVORITES)).toContain('View your favorite files and folders'); + }); + }); + + it('Trash tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.TRASH)).toContain('View deleted files in the trash'); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-favorites.test.ts b/e2e/suites/pagination/pag-favorites.test.ts new file mode 100755 index 000000000..2244be6e8 --- /dev/null +++ b/e2e/suites/pagination/pag-favorites.test.ts @@ -0,0 +1,228 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Favorites', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, favorites: favoritesApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => favoritesApi.addFavoriteById('file', fileId)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => favoritesApi.addFavoritesByIds('file', filesIds)) + .then(() => favoritesApi.waitForApi({ expect: 101 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('50'); + expect(pagination.getText(pagination.totalPages)).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => { + expect(pagination.getText(pagination.range)).toContain('51-75 of 101'); + expect(pagination.getText(pagination.currentPage)).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()) + .toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-personal-files.test.ts b/e2e/suites/pagination/pag-personal-files.test.ts new file mode 100755 index 000000000..beb580638 --- /dev/null +++ b/e2e/suites/pagination/pag-personal-files.test.ts @@ -0,0 +1,228 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Personal Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-60.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.clickNext() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-30.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.clickPrevious()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-12.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-recent-files.test.ts b/e2e/suites/pagination/pag-recent-files.test.ts new file mode 100755 index 000000000..bf3baa066 --- /dev/null +++ b/e2e/suites/pagination/pag-recent-files.test.ts @@ -0,0 +1,230 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Recent Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, search: searchApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => searchApi.waitForApi(username, { expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(() => searchApi.waitForApi(username, { expect: 101 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-shared-files.test.ts b/e2e/suites/pagination/pag-shared-files.test.ts new file mode 100755 index 000000000..ae0b47511 --- /dev/null +++ b/e2e/suites/pagination/pag-shared-files.test.ts @@ -0,0 +1,236 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Shared Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, shared: sharedApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => sharedApi.shareFileById(fileId)) + .then(() => sharedApi.waitForApi({ expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + + .then(() => sharedApi.shareFilesByIds(filesIds)) + .then(() => sharedApi.waitForApi({ expect: 101 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()) + .toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-trash.test.ts b/e2e/suites/pagination/pag-trash.test.ts new file mode 100755 index 000000000..35452d2c3 --- /dev/null +++ b/e2e/suites/pagination/pag-trash.test.ts @@ -0,0 +1,233 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Trash', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, trashcan: trashApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const filesForDelete = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesDeletedIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => nodesApi.deleteNodeById(fileId, false)) + .then(() => trashApi.waitForApi({ expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + trashApi.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(filesForDelete) + .then(resp => filesDeletedIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => nodesApi.deleteNodesById(filesDeletedIds, false)) + .then(() => trashApi.waitForApi({expect: 101})) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + trashApi.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json old mode 100644 new mode 100755 diff --git a/e2e/utilities/repo-client/apis/favorites/favorites-api.ts b/e2e/utilities/repo-client/apis/favorites/favorites-api.ts new file mode 100755 index 000000000..3e92d7838 --- /dev/null +++ b/e2e/utilities/repo-client/apis/favorites/favorites-api.ts @@ -0,0 +1,118 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { RepoApi } from '../repo-api'; +import { NodesApi } from '../nodes/nodes-api'; +import { RepoClient } from './../../repo-client'; +import { Utils } from '../../../../utilities/utils'; + +export class FavoritesApi extends RepoApi { + + addFavorite(api: RepoClient, nodeType: string, name: string): Promise { + return api.nodes.getNodeByPath(name) + .then((response) => { + const { id } = response.data.entry; + return ([{ + target: { + [nodeType]: { + guid: id + } + } + }]); + }) + .then((data) => { + return this.post(`/people/-me-/favorites`, { data }); + }) + .catch(this.handleError); + } + + addFavoriteById(nodeType: 'file' | 'folder', id: string): Promise { + const data = [{ + target: { + [nodeType]: { + guid: id + } + } + }]; + return this + .post(`/people/-me-/favorites`, { data }) + .catch(this.handleError); + } + + addFavoritesByIds(nodeType: 'file' | 'folder', ids: string[]): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.addFavoriteById(nodeType, current)) + ), Promise.resolve()); + } + + getFavorites(): Promise { + return this + .get('/people/-me-/favorites') + .catch(this.handleError); + } + + getFavoriteById(nodeId: string): Promise { + return this + .get(`/people/-me-/favorites/${nodeId}`) + .catch(this.handleError); + } + + isFavorite(nodeId: string) { + return this.getFavorites() + .then(resp => JSON.stringify(resp.data.list.entries).includes(nodeId)) + } + + removeFavorite(api: RepoClient, nodeType: string, name: string): Promise { + return api.nodes.getNodeByPath(name) + .then((response) => { + const { id } = response.data.entry; + return this.delete(`/people/-me-/favorites/${id}`); + }) + .catch(this.handleError); + } + + removeFavoriteById(nodeId: string) { + return this + .delete(`/people/-me-/favorites/${nodeId}`) + .catch(this.handleError); + } + + waitForApi(data) { + const favoriteFiles = () => { + return this.getFavorites() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(favoriteFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/nodes/node-body-create.ts b/e2e/utilities/repo-client/apis/nodes/node-body-create.ts new file mode 100755 index 000000000..d82201ab2 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/node-body-create.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export const NODE_TYPE_FILE = 'cm:content'; +export const NODE_TYPE_FOLDER = 'cm:folder'; +export const NODE_TITLE = 'cm:title'; +export const NODE_DESCRIPTION = 'cm:description'; + +export class NodeBodyCreate { + constructor( + public name: string, + public nodeType: string, + public relativePath: string = '/', + public properties?: any[] + ) {} +} diff --git a/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts b/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts new file mode 100755 index 000000000..65915d480 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts @@ -0,0 +1,85 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER, NODE_TITLE, NODE_DESCRIPTION } from './node-body-create'; + +export interface NodeContentTree { + name?: string; + files?: string[]; + folders?: (string|NodeContentTree)[]; + title?: string; + description?: string; +} + +export function flattenNodeContentTree(content: NodeContentTree, relativePath: string = '/'): NodeBodyCreate[] { + const { name, files, folders, title, description } = content; + let data: NodeBodyCreate[] = []; + let properties: any; + + properties = { + [NODE_TITLE]: title, + [NODE_DESCRIPTION]: description + }; + + if (name) { + data = data.concat([{ + nodeType: NODE_TYPE_FOLDER, + name, + relativePath, + properties + }]); + + relativePath = (relativePath === '/') + ? `/${name}` + : `${relativePath}/${name}`; + } + + if (folders) { + const foldersData: NodeBodyCreate[] = folders + .map((folder: (string|NodeContentTree)): NodeBodyCreate[] => { + const folderData: NodeContentTree = (typeof folder === 'string') + ? { name: folder } + : folder; + + return flattenNodeContentTree(folderData, relativePath); + }) + .reduce((nodesData: NodeBodyCreate[], folderData: NodeBodyCreate[]) => nodesData.concat(folderData), []); + + data = data.concat(foldersData); + } + + if (files) { + const filesData: NodeBodyCreate[] = files + .map((filename: string): NodeBodyCreate => ({ + nodeType: NODE_TYPE_FILE, + name: filename, + relativePath + })); + + data = data.concat(filesData); + } + + return data; +} diff --git a/e2e/utilities/repo-client/apis/nodes/nodes-api.ts b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts new file mode 100755 index 000000000..bde0a2a11 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts @@ -0,0 +1,182 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER } from './node-body-create'; +import { NodeContentTree, flattenNodeContentTree } from './node-content-tree'; + +export class NodesApi extends RepoApi { + // nodes + getNodeByPath(relativePath: string = '/'): Promise { + return this + .get(`/nodes/-my-`, { parameters: { relativePath } }) + .catch(this.handleError); + } + + getNodeById(id: string): Promise { + return this + .get(`/nodes/${id}`) + .catch(this.handleError); + } + + getNodeDescription(name: string, relativePath: string = '/') { + relativePath = (relativePath === '/') + ? `${name}` + : `${relativePath}/${name}`; + + return this.getNodeByPath(`${relativePath}`) + .then(response => response.data.entry.properties['cm:description']); + } + + deleteNodeById(id: string, permanent: boolean = true): Promise { + return this + .delete(`/nodes/${id}?permanent=${permanent}`) + .catch(this.handleError); + } + + deleteNodeByPath(path: string, permanent: boolean = true): Promise { + return this + .getNodeByPath(path) + .then((response: any): string => response.data.entry.id) + .then((id: string): any => this.deleteNodeById(id, permanent)) + .catch(this.handleError); + } + + deleteNodes(names: string[], relativePath: string = '', permanent: boolean = true): Promise { + return names.reduce((previous, current) => ( + previous.then(() => this.deleteNodeByPath(`${relativePath}/${current}`, permanent)) + ), Promise.resolve()); + } + + deleteNodesById(ids: string[], permanent: boolean = true): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.deleteNodeById(current, permanent)) + ), Promise.resolve()); + } + + // children + getNodeChildren(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}/children`) + .catch(this.handleError); + } + + createNode(nodeType: string, name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + const data = { + name: name, + nodeType: nodeType, + properties: { + 'cm:title': title, 'cm:description': description + } + }; + + return this + .post(`/nodes/${parentId}/children`, { data }) + .catch(this.handleError); + } + + createFile(name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + return this.createNode('cm:content', name, parentId, title, description); + } + + createFolder(name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + return this.createNode('cm:folder', name, parentId, title, description); + } + + createChildren(data: NodeBodyCreate[]): Promise { + return this + .post(`/nodes/-my-/children`, { data }) + .catch(this.handleError); + } + + createContent(content: NodeContentTree, relativePath: string = '/'): Promise { + return this.createChildren(flattenNodeContentTree(content, relativePath)); + } + + createFolders(names: string[], relativePath: string = '/'): Promise { + return this.createContent({ folders: names }, relativePath); + } + + createFiles(names: string[], relativePath: string = '/'): Promise { + return this.createContent({ files: names }, relativePath); + } + + // node content + getNodeContent(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}/content`) + .catch(this.handleError); + } + + editNodeContent(nodeId: string, content: string): Promise { + return this + .put(`/nodes/${nodeId}/content`, { data: content }) + .catch(this.handleError); + } + + renameNode(nodeId: string, newName: string): Promise { + return this + .put(`/nodes/${nodeId}`, { data: { name: newName } }) + .catch(this.handleError); + } + + // node permissions + setGranularPermission(nodeId: string, inheritPermissions: boolean = false, username: string, role: string): Promise { + const data = { + permissions: { + isInheritanceEnabled: inheritPermissions, + locallySet: [ + { + authorityId: username, + name: role + } + ] + } + }; + + return this + .put(`/nodes/${nodeId}`, { data }) + .catch(this.handleError); + } + + getNodePermissions(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}?include=permissions`) + .catch(this.handleError); + } + + // lock node + lockFile(nodeId: string, lockType: string = 'FULL') { + return this + .post(`/nodes/${nodeId}/lock?include=isLocked`, { data: { 'type': lockType } }) + .catch(this.handleError); + } + + unlockFile(nodeId: string) { + return this + .post(`/nodes/${nodeId}/unlock`) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/people/people-api-models.ts b/e2e/utilities/repo-client/apis/people/people-api-models.ts new file mode 100755 index 000000000..93be7980c --- /dev/null +++ b/e2e/utilities/repo-client/apis/people/people-api-models.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export class Person { + id?: string; + password?: string; + firstName?: string; + lastName?: string; + email?: string; + properties?: any; + + constructor(username: string, password: string, details: Person) { + this.id = username; + this.password = password || username; + this.firstName = username; + this.lastName = username; + this.email = `${username}@alfresco.com`; + + Object.assign(this, details); + } +} diff --git a/e2e/utilities/repo-client/apis/people/people-api.ts b/e2e/utilities/repo-client/apis/people/people-api.ts new file mode 100755 index 000000000..2ede55982 --- /dev/null +++ b/e2e/utilities/repo-client/apis/people/people-api.ts @@ -0,0 +1,70 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Person } from './people-api-models'; + +export class PeopleApi extends RepoApi { + getUser(username: string) { + return this + .get(`/people/${username}`) + .catch(this.handleError); + } + + updateUser(username: string, details?: Person): Promise { + if (details.id) { + delete details.id; + } + + return this + .put(`/people/${username}`, { data: details }) + .catch(this.handleError); + } + + createUser(username: string, password?: string, details?: Person): Promise { + const person: Person = new Person(username, password, details); + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateUser(username, person)) + : Promise.reject(response); + }; + + return this + .post(`/people`, { data: person }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + disableUser(username: string): Promise { + return this.put(`/people/${username}`, { data: { enabled: false } }) + .catch(this.handleError); + } + + changePassword(username: string, newPassword: string) { + return this.put(`/people/${username}`, { data: { password: newPassword } }) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/repo-api.ts b/e2e/utilities/repo-client/apis/repo-api.ts new file mode 100755 index 000000000..ee61ce6df --- /dev/null +++ b/e2e/utilities/repo-client/apis/repo-api.ts @@ -0,0 +1,71 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RestClient, RestClientArgs, RestClientResponse } from '../../rest-client/rest-client'; +import { RepoClientAuth, RepoClientConfig } from '../repo-client-models'; + +export abstract class RepoApi { + private client: RestClient; + private defaults: RepoClientConfig = new RepoClientConfig(); + + constructor( + auth: RepoClientAuth = new RepoClientAuth(), + private config?: RepoClientConfig + ) { + const { username, password } = auth; + + this.client = new RestClient(username, password); + } + + private createEndpointUri(endpoint: string, apiDefinition: string = 'alfresco'): string { + const { defaults, config } = this; + const { host, tenant } = Object.assign(defaults, config); + + return `${host}/alfresco/api/${tenant}/public/${apiDefinition}/versions/1${endpoint}`; + } + + protected handleError(response: RestClientResponse) { + const { request: { method, path, data }, data: error } = response; + + console.log(`ERROR on ${method}\n${path}\n${data}`); + console.log(error); + } + + protected get(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.get(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected post(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.post(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected put(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.put(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected delete(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.delete(this.createEndpointUri(endpoint, apiDefinition), args); + } +} diff --git a/e2e/utilities/repo-client/apis/search/search-api.ts b/e2e/utilities/repo-client/apis/search/search-api.ts new file mode 100755 index 000000000..bb09fabd4 --- /dev/null +++ b/e2e/utilities/repo-client/apis/search/search-api.ts @@ -0,0 +1,71 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Utils } from '../../../../utilities/utils'; + +export class SearchApi extends RepoApi { + apiDefinition = 'search'; + + search(data: any[]): Promise { + return this + .post(`/search`, { data }, this.apiDefinition) + .catch(this.handleError); + } + + queryRecentFiles(username: string): Promise { + const data = { + query: { + query: '*', + language: 'afts' + }, + filterQueries: [ + { query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` }, + { query: `cm:modifier:${username} OR cm:creator:${username}` }, + { query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` } + ] + }; + + return this + .post(`/search`, { data }, this.apiDefinition) + .catch(this.handleError); + } + + waitForApi(username, data) { + const recentFiles = () => { + return this.queryRecentFiles(username) + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(recentFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts b/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts new file mode 100755 index 000000000..6140dcd74 --- /dev/null +++ b/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts @@ -0,0 +1,79 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { NodesApi } from '../nodes/nodes-api'; +import { RepoClient } from './../../repo-client'; +import { Utils } from '../../../../utilities/utils'; + +export class SharedLinksApi extends RepoApi { + + shareFileById(id: string): Promise { + const data = [{ nodeId: id }]; + + return this.post(`/shared-links`, { data }) + .catch(this.handleError); + } + + shareFilesByIds(ids: string[]): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.shareFileById(current)) + ), Promise.resolve()); + } + + getSharedIdOfNode(name: string) { + return this.getSharedLinks() + .then(resp => resp.data.list.entries.find(entries => entries.entry.name === name)) + .then(resp => resp.entry.id) + .catch(this.handleError); + } + + unshareFile(name: string) { + return this.getSharedIdOfNode(name) + .then(id => this.delete(`/shared-links/${id}`)) + .catch(this.handleError); + } + + getSharedLinks(): Promise { + return this.get(`/shared-links`) + .catch(this.handleError); + } + + waitForApi(data) { + const sharedFiles = () => { + return this.getSharedLinks() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(sharedFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/sites/sites-api-models.ts b/e2e/utilities/repo-client/apis/sites/sites-api-models.ts new file mode 100755 index 000000000..598aad900 --- /dev/null +++ b/e2e/utilities/repo-client/apis/sites/sites-api-models.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY } from '../../../../configs'; + +export class Site { + title?: string; + visibility?: string = SITE_VISIBILITY.PUBLIC; + id?: string; + description?: string; + + constructor(title: string, visibility: string, details: Site) { + this.title = title; + this.visibility = visibility; + this.id = title; + this.description = `${title} description`; + + Object.assign(this, details); + } +} diff --git a/e2e/utilities/repo-client/apis/sites/sites-api.ts b/e2e/utilities/repo-client/apis/sites/sites-api.ts new file mode 100755 index 000000000..a824893ba --- /dev/null +++ b/e2e/utilities/repo-client/apis/sites/sites-api.ts @@ -0,0 +1,124 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Site } from './sites-api-models'; + +export class SitesApi extends RepoApi { + getSite(id: string): Promise { + return this + .get(`/sites/${id}`) + .catch(this.handleError); + } + + getSiteContainers(siteId: string): Promise { + return this + .get(`/sites/${siteId}/containers`) + .then(resp => resp.data.list.entries) + .catch(this.handleError); + } + + getDocLibId(siteId: string) { + return this.getSiteContainers(siteId) + .then(resp => resp[0].entry.id) + .catch(this.handleError); + } + + updateSite(id: string, details?: Site): Promise { + if (details.id) { + delete details.id; + } + + return this + .put(`/sites/${id}`, { data: details }) + .catch(this.handleError); + } + + createOrUpdateSite(title: string, visibility: string, details?: Site): Promise { + const site: Site = new Site(title, visibility, details); + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateSite(site.id, site)) + : Promise.reject(response); + }; + + return this + .post(`/sites`, { data: site }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + createSite(title: string, visibility: string, details?: Site): Promise { + const site: Site = new Site(title, visibility, details); + return this + .post(`/sites`, { data: site }) + .catch(this.handleError); + } + + createSites(titles: string[], visibility: string): Promise { + return titles.reduce((previous, current) => ( + previous.then(() => this.createSite(current, visibility)) + ), Promise.resolve()); + } + + deleteSite(id: string, permanent: boolean = true): Promise { + return this + .delete(`/sites/${id}?permanent=${permanent}`) + .catch(this.handleError); + } + + deleteSites(ids: string[], permanent: boolean = true): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.deleteSite(current)) + ), Promise.resolve()); + } + + updateSiteMember(siteId: string, userId: string, role: string): Promise { + return this + .put(`/sites/${siteId}/members/${userId}`, { data: { role } }) + .catch(this.handleError); + } + + addSiteMember(siteId: string, userId: string, role: string): Promise { + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateSiteMember(siteId, userId, role)) + : Promise.reject(response); + }; + + return this + .post(`/sites/${siteId}/members`, { data: { role, id: userId } }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + deleteSiteMember(siteId: string, userId: string): Promise { + return this + .delete(`/sites/${siteId}/members/${userId}`) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts b/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts new file mode 100755 index 000000000..0f94d5ee6 --- /dev/null +++ b/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts @@ -0,0 +1,76 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Utils } from '../../../../utilities/utils'; + +export class TrashcanApi extends RepoApi { + permanentlyDelete(id: string): Promise { + return this + .delete(`/deleted-nodes/${id}`) + .catch(this.handleError); + } + + restore(id: string) { + return this + .post(`/deleted-nodes/${id}/restore`) + .catch(this.handleError); + } + + getDeletedNodes(): Promise { + return this + .get(`/deleted-nodes?maxItems=1000`) + .catch(this.handleError); + } + + emptyTrash(): Promise { + return this.getDeletedNodes() + .then(resp => { + return resp.data.list.entries.map(entries => entries.entry.id); + }) + .then(ids => { + return ids.reduce((previous, current) => ( + previous.then(() => this.permanentlyDelete(current)) + ), Promise.resolve()); + }) + .catch(this.handleError); + } + + waitForApi(data) { + const deletedFiles = () => { + return this.getDeletedNodes() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(deletedFiles); + } +} diff --git a/e2e/utilities/repo-client/repo-client-models.ts b/e2e/utilities/repo-client/repo-client-models.ts new file mode 100755 index 000000000..a352eb882 --- /dev/null +++ b/e2e/utilities/repo-client/repo-client-models.ts @@ -0,0 +1,46 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { + ADMIN_USERNAME, + ADMIN_PASSWORD, + REPO_API_HOST, + REPO_API_TENANT +} from '../../configs'; + +export class RepoClientAuth { + static DEFAULT_USERNAME: string = ADMIN_USERNAME; + static DEFAULT_PASSWORD: string = ADMIN_PASSWORD; + + constructor( + public username: string = RepoClientAuth.DEFAULT_USERNAME, + public password: string = RepoClientAuth.DEFAULT_PASSWORD + ) {} +} + +export class RepoClientConfig { + host?: string = REPO_API_HOST; + tenant?: string = REPO_API_TENANT; +} diff --git a/e2e/utilities/repo-client/repo-client.ts b/e2e/utilities/repo-client/repo-client.ts new file mode 100755 index 000000000..e3ffc0107 --- /dev/null +++ b/e2e/utilities/repo-client/repo-client.ts @@ -0,0 +1,59 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoClientAuth, RepoClientConfig } from './repo-client-models'; + +import { PeopleApi } from './apis/people/people-api'; +import { NodesApi } from './apis/nodes/nodes-api'; +import { SitesApi } from './apis/sites/sites-api'; +import { FavoritesApi } from './apis/favorites/favorites-api'; +import { SharedLinksApi } from './apis/shared-links/shared-links-api'; +import { TrashcanApi } from './apis/trashcan/trashcan-api'; +import { SearchApi } from './apis/search/search-api'; + +export class RepoClient { + public people: PeopleApi = new PeopleApi(this.auth, this.config); + public nodes: NodesApi = new NodesApi(this.auth, this.config); + public sites: SitesApi = new SitesApi(this.auth, this.config); + public favorites: FavoritesApi = new FavoritesApi(this.auth, this.config); + public shared: SharedLinksApi = new SharedLinksApi(this.auth, this.config); + public trashcan: TrashcanApi = new TrashcanApi(this.auth, this.config); + public search: SearchApi = new SearchApi(this.auth, this.config); + + constructor( + private username: string = RepoClientAuth.DEFAULT_USERNAME, + private password: string = RepoClientAuth.DEFAULT_PASSWORD, + private config?: RepoClientConfig + ) {} + + private get auth(): RepoClientAuth { + const { username, password } = this; + return { username, password }; + } +} + +export * from './apis/nodes/node-body-create'; +export * from './apis/nodes/node-content-tree'; +export * from './apis/nodes/nodes-api'; diff --git a/e2e/utilities/reporters/console/console-logger.ts b/e2e/utilities/reporters/console/console-logger.ts new file mode 100755 index 000000000..756bfb4a8 --- /dev/null +++ b/e2e/utilities/reporters/console/console-logger.ts @@ -0,0 +1,79 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +/* tslint:disable */ +const chalk = require('chalk'); +/* tslint:enable */ + +export const log = { + i: 0, + + get indentation(): string { + return new Array(this.i).fill(' ').join(''); + }, + + indent() { + this.i++; + return this; + }, + + dedent() { + this.i--; + return this; + }, + + log(message: string = '', options: any = { ignoreIndentation: false }) { + const indentation = (!options.ignoreIndentation) + ? this.indentation + : ''; + + console.log(`${indentation}${message}`); + + return this; + }, + + blank() { + return this.log(); + }, + + info(message: string = '', options: any = { bold: false, title: false }) { + const { bold } = options; + const style = (bold ? chalk.bold : chalk).gray; + + return this.log(style(message), options); + }, + + success(message: string = '', options: any = { bold: false }) { + const style = options.bold ? chalk.bold.green : chalk.green; + + return this.log(style(message), options); + }, + + error(message: string = '', options: any = { bold: false }) { + const style = options.bold ? chalk.bold.red : chalk.red; + + return this.log(style(message), options); + } +}; diff --git a/e2e/utilities/reporters/console/console.ts b/e2e/utilities/reporters/console/console.ts new file mode 100755 index 000000000..2f92e394a --- /dev/null +++ b/e2e/utilities/reporters/console/console.ts @@ -0,0 +1,90 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { log } from './console-logger'; + +const errors = []; + +export const consoleReporter = { + jasmineStarted(suiteInfo) { + log.blank().info( + `Running ${suiteInfo.totalSpecsDefined} tests`, + { bold: true, title: true } + ).blank(); + }, + + suiteStarted(suite) { + log.info(suite.description).indent(); + }, + + specDone: (spec) => { + const { + status, + description, + failedExpectations + } = spec; + + if (status === 'passed') { + log.success(`∙ ${description}`); + } + + if (status === 'failed') { + log.error(`✕ ${description}`, { bold: true }); + + errors.push(spec); + + failedExpectations.forEach((failed) => { + log.error(` ${failed.message}`); + }); + } + }, + + suiteDone: (result) => { + log.dedent(); + }, + + jasmineDone: (result) => { + if (!!errors.length) { + log .blank() + .blank() + .info(`${errors.length} failing tests`, { bold: true, title: true }); + + errors.forEach(error => { + log .blank() + .error(`✕ ${error.fullName}`, { bold: true }); + + error.failedExpectations.forEach(failed => { + log .info(`${failed.message}`) + .blank() + .error(`${failed.stack}`); + }); + }); + } else { + log.success(`All tests passed!`, { bold: true }); + } + + log.blank().blank(); + } +}; diff --git a/e2e/utilities/rest-client/rest-client-models.ts b/e2e/utilities/rest-client/rest-client-models.ts new file mode 100755 index 000000000..6b5098d40 --- /dev/null +++ b/e2e/utilities/rest-client/rest-client-models.ts @@ -0,0 +1,63 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +interface RequestConfig { + timeout?: number; + noDelay?: boolean; + keepAlive?: boolean; + keepAliveDelay?: number; +} + +interface ResponseConfig { + timeout?: number; +} + +interface ResponseRequest { + method: string; + path: string; + data: string; +} + +export interface NodeRestClient { + get(uri: string, callback: Function): Function; + post(uri: string, callback: Function): Function; + put(uri: string, callback: Function): Function; + delete(uri: string, callback: Function): Function; +} + +export interface RestClientArgs { + data?: any; + parameters?: any; + headers?: any; + requestConfig?: RequestConfig; + responseConfig?: ResponseConfig; +} + +export interface RestClientResponse { + request: ResponseRequest; + data: any; + statusMessage: string; + statusCode: number; +} diff --git a/e2e/utilities/rest-client/rest-client.ts b/e2e/utilities/rest-client/rest-client.ts new file mode 100755 index 000000000..49b724a9b --- /dev/null +++ b/e2e/utilities/rest-client/rest-client.ts @@ -0,0 +1,89 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Client } from 'node-rest-client'; +import { NodeRestClient, RestClientArgs, RestClientResponse } from './rest-client-models'; + +export * from './rest-client-models'; + +export class RestClient { + private static DEFAULT_HEADERS = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + private client: NodeRestClient; + + constructor(user: string, password: string) { + this.client = (new Client({ user, password })); + } + + get(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('get', uri, args); + } + + post(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('post', uri, args); + } + + put(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('put', uri, args); + } + + delete(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('delete', uri, args); + } + + private createArgs(args: RestClientArgs = {}): RestClientArgs { + const data = JSON.stringify(args.data); + + return Object.assign({}, RestClient.DEFAULT_HEADERS, args, { data }); + } + + private promisify(fnName: string, uri: string, args: RestClientArgs): Promise { + const fn: Function = this.client[fnName]; + const fnArgs = [ encodeURI(uri), this.createArgs(args) ]; + + return new Promise((resolve, reject) => { + const fnCallback = (data, rawResponse) => { + const { + statusCode, statusMessage, + req: { method, path } + } = rawResponse; + + const response: RestClientResponse = { + data, statusCode, statusMessage, + request: { method, path, data: args.data } + }; + + (response.statusCode >= 400) + ? reject(response) + : resolve(response); + }; + + fn(...fnArgs, fnCallback); + }); + } +} diff --git a/e2e/utilities/utils.ts b/e2e/utilities/utils.ts new file mode 100755 index 000000000..901d8f5a4 --- /dev/null +++ b/e2e/utilities/utils.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, promise, ElementFinder, ExpectedConditions as EC } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../configs'; + +export class Utils { + // generate a random value + static random(): string { + return Math.random().toString(36).substring(3, 10).toLowerCase(); + } + + // local storage + static clearLocalStorage(): promise.Promise { + return browser.executeScript('window.localStorage.clear();'); + } + + // session storage + static clearSessionStorage(): promise.Promise { + return browser.executeScript('window.sessionStorage.clear();'); + } + + static retryCall(fn: () => Promise , retry: number = 30, delay: number = 1000): Promise { + const rerun = (retries, fn) => { + fn().catch(err => retries > 1 + ? rerun(retries - 1, fn) + : Promise.reject(err)); + }; + + const pause = (duration) => new Promise(res => setTimeout(res, duration)); + + const run = (retries, fn, delay = 1000) => + fn().catch(err => retries > 1 + ? pause(delay).then(() => run(retries - 1, fn, delay)) + : Promise.reject(err)); + + return run(retry, fn); + } + + static waitUntilElementClickable(element: ElementFinder) { + return browser.wait(EC.elementToBeClickable(element), BROWSER_WAIT_TIMEOUT); + } + + static typeInField(elem: ElementFinder, value: string) { + for ( let i = 0; i < value.length; i++ ) { + const c = value.charAt(i); + elem.sendKeys(c); + browser.sleep(100); + } + } +} diff --git a/package-lock.json b/package-lock.json index f998d1fae..3696d78d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,15 +40,6 @@ "zone.js": "0.8.14" }, "dependencies": { - "alfresco-js-api": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.3.0.tgz", - "integrity": "sha512-IhsSNoPl8cbw/V24kw420sGoVp6rBakC2kN4gKe3bPdERvSWRehw5bojMQhnSPDmS2PqC5C23HaVV+whOwkpDg==", - "requires": { - "event-emitter": "0.3.4", - "superagent": "3.8.2" - } - }, "core-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", @@ -90,18 +81,38 @@ "@ngx-translate/core": "9.1.1", "alfresco-js-api": "2.3.0", "chart.js": "2.5.0", - "core-js": "2.5.3", + "core-js": "2.4.1", "hammerjs": "2.0.8", "minimatch": "3.0.4", "moment": "2.20.1", "ng2-charts": "1.6.0", - "pdfjs-dist": "2.0.303", + "pdfjs-dist": "1.5.404", "raphael": "2.2.7", "reflect-metadata": "0.1.10", "rxjs": "5.5.2", "systemjs": "0.19.27", "tslib": "1.9.0", - "zone.js": "0.8.20" + "zone.js": "0.8.14" + }, + "dependencies": { + "core-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "pdfjs-dist": { + "version": "1.5.404", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-1.5.404.tgz", + "integrity": "sha1-hYXGUWquIU1ZCXXo+ys8PzrxTO8=", + "requires": { + "node-ensure": "0.0.0" + } + }, + "zone.js": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.14.tgz", + "integrity": "sha1-DE2ySxeCMidMy0P3jJnbfzZCts8=" + } } }, "@angular-devkit/build-optimizer": { @@ -132,7 +143,7 @@ "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", - "rxjs": "5.5.9", + "rxjs": "5.5.10", "source-map": "0.5.7" }, "dependencies": { @@ -149,9 +160,9 @@ } }, "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -172,13 +183,13 @@ "dev": true, "requires": { "@ngtools/json-schema": "1.2.0", - "rxjs": "5.5.9" + "rxjs": "5.5.10" }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -253,11 +264,11 @@ "portfinder": "1.0.13", "postcss": "6.0.21", "postcss-import": "11.1.0", - "postcss-loader": "2.1.3", + "postcss-loader": "2.1.4", "postcss-url": "7.3.2", "raw-loader": "0.5.1", "resolve": "1.7.1", - "rxjs": "5.5.9", + "rxjs": "5.5.10", "sass-loader": "6.0.7", "semver": "5.5.0", "silent-error": "1.1.0", @@ -265,7 +276,7 @@ "style-loader": "0.19.1", "stylus": "0.54.5", "stylus-loader": "3.0.2", - "uglifyjs-webpack-plugin": "1.2.4", + "uglifyjs-webpack-plugin": "1.2.5", "url-loader": "0.6.2", "webpack": "3.11.0", "webpack-dev-middleware": "1.12.2", @@ -276,9 +287,9 @@ }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -466,15 +477,15 @@ "integrity": "sha512-7aVP4994Hu8vRdTTohXkfGWEwLhrdNP3EZnWyBootm5zshWqlQojUGweZe5zwewsKcixeVOiy2YtW+aI4aGSLA==", "dev": true, "requires": { - "rxjs": "5.5.9", + "rxjs": "5.5.10", "semver": "5.5.0", "semver-intersect": "1.3.1" }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -516,9 +527,9 @@ "dev": true }, "@types/selenium-webdriver": { - "version": "2.53.43", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", - "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.8.tgz", + "integrity": "sha512-yrqQvb1EZhH+ONMzUmsEnBjzitortVv0lynRe5US2+FofdoMWUE4wf7v4peCd62fqXq8COCVTbpS1/jIg5EbuQ==", "dev": true }, "@types/strip-bom": { @@ -600,9 +611,9 @@ "optional": true }, "adm-zip": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", - "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", + "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", "dev": true }, "after": { @@ -1011,7 +1022,7 @@ "dev": true, "requires": { "browserslist": "2.11.3", - "caniuse-lite": "1.0.30000828", + "caniuse-lite": "1.0.30000830", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "6.0.21", @@ -1257,9 +1268,9 @@ "dev": true }, "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", "dev": true }, "base64id": { @@ -1372,9 +1383,9 @@ } }, "blocking-proxy": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-0.0.5.tgz", - "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", "dev": true, "requires": { "minimist": "1.2.0" @@ -1576,7 +1587,7 @@ "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", "dev": true, "requires": { - "base64-js": "1.2.3", + "base64-js": "1.3.0", "ieee754": "1.1.11" } }, @@ -1727,7 +1738,7 @@ "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000828", + "caniuse-lite": "1.0.30000830", "electron-to-chromium": "1.3.42" } }, @@ -1737,7 +1748,7 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.2.3", + "base64-js": "1.3.0", "ieee754": "1.1.11", "isarray": "1.0.0" } @@ -1906,9 +1917,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000828", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000828.tgz", - "integrity": "sha512-v+ySC6Ih8N8CyGZYd4svPipuFIqskKsTOi18chFM0qtu1G8mGuSYajb+h49XDWgmzX8MRDOp1Agw6KQaPUdIhg==", + "version": "1.0.30000830", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000830.tgz", + "integrity": "sha512-yMqGkujkoOIZfvOYiWdqPALgY/PVGiqCHUJb6yNq7xhI/pR+gQO0U2K6lRDqAiJv4+CIU3CtTLblNGw0QGnr6g==", "dev": true }, "caseless": { @@ -2578,7 +2589,7 @@ "loader-utils": "1.1.0", "minimatch": "3.0.4", "p-limit": "1.2.0", - "serialize-javascript": "1.4.0" + "serialize-javascript": "1.5.0" }, "dependencies": { "is-extglob": { @@ -2659,7 +2670,7 @@ "cipher-base": "1.0.4", "inherits": "2.0.3", "md5.js": "1.3.4", - "ripemd160": "2.0.1", + "ripemd160": "2.0.2", "sha.js": "2.4.11" } }, @@ -2672,7 +2683,7 @@ "cipher-base": "1.0.4", "create-hash": "1.2.0", "inherits": "2.0.3", - "ripemd160": "2.0.1", + "ripemd160": "2.0.2", "safe-buffer": "5.1.1", "sha.js": "2.4.11" } @@ -3263,9 +3274,9 @@ "dev": true }, "ejs": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.8.tgz", - "integrity": "sha512-QIDZL54fyV8MDcAsO91BMH1ft2qGGaHIJsJIA/+t+7uvXol1dm413fPcUgUb4k8F/9457rx4/KFE4XfDifrQxQ==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", + "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==", "dev": true }, "electron-to-chromium": { @@ -3493,6 +3504,12 @@ } } }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -5673,9 +5690,9 @@ "dev": true }, "html-minifier": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.14.tgz", - "integrity": "sha512-sZjw6zhQgyUnIlIPU+W80XpRjWjdxHtNcxjfyOskOsCTDKytcfLY04wsQY/83Yqb4ndoiD2FtauiL7Yg6uUQFQ==", + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.15.tgz", + "integrity": "sha512-OZa4rfb6tZOZ3Z8Xf0jKxXkiDcFWldQePGYFDcgKqES2sXeWaEv9y6QQvWUtX3ySI3feApQi5uCsHLINQ6NoAw==", "dev": true, "requires": { "camel-case": "3.0.0", @@ -5694,7 +5711,7 @@ "dev": true, "requires": { "bluebird": "3.5.1", - "html-minifier": "3.5.14", + "html-minifier": "3.5.15", "loader-utils": "0.2.17", "lodash": "4.17.5", "pretty-error": "2.1.1", @@ -5939,6 +5956,12 @@ "dev": true, "optional": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, "import-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", @@ -6541,23 +6564,21 @@ "handlebars": "4.0.11" } }, + "items": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", + "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=", + "dev": true + }, "jasmine": { - "version": "2.99.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.99.0.tgz", - "integrity": "sha1-jKctEC5jm4Z8ZImFbg4YqceqQrc=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", "dev": true, "requires": { "exit": "0.1.2", "glob": "7.1.2", - "jasmine-core": "2.99.1" - }, - "dependencies": { - "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true - } + "jasmine-core": "2.8.0" } }, "jasmine-core": { @@ -6772,6 +6793,53 @@ } } }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "dev": true, + "requires": { + "core-js": "2.3.0", + "es6-promise": "3.0.2", + "lie": "3.1.1", + "pako": "1.0.6", + "readable-stream": "2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "karma": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.0.tgz", @@ -7017,7 +7085,16 @@ "integrity": "sha512-NqAFodJdpBUuf1iD+Ij8hQvF0rCFKlO2KaieoQzAPhFgzLCtJnC7Z7x5gQbGNjoe++wOKAtAmwVEIBLqq2Yp1A==", "dev": true, "requires": { - "ejs": "2.5.8" + "ejs": "2.5.9" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "requires": { + "immediate": "3.0.6" } }, "load-json-file": { @@ -7932,7 +8009,7 @@ "stream-browserify": "2.0.1", "stream-http": "2.8.1", "string_decoder": "1.1.1", - "timers-browserify": "2.0.6", + "timers-browserify": "2.0.10", "tty-browserify": "0.0.0", "url": "0.11.0", "util": "0.10.3", @@ -7953,6 +8030,60 @@ "integrity": "sha1-QAlrCM560OoUaAhjr0ScfHWl0cg=", "dev": true }, + "node-rest-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-rest-client/-/node-rest-client-3.1.0.tgz", + "integrity": "sha1-4L623aeyDMC2enhHzxLF/EGcN8M=", + "dev": true, + "requires": { + "debug": "2.2.0", + "follow-redirects": "1.4.1", + "xml2js": "0.4.19" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "follow-redirects": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", + "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", + "dev": true, + "requires": { + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, "node-sass": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz", @@ -8771,7 +8902,7 @@ "requires": { "create-hash": "1.2.0", "create-hmac": "1.1.7", - "ripemd160": "2.0.1", + "ripemd160": "2.0.2", "safe-buffer": "5.1.1", "sha.js": "2.4.11" } @@ -8861,20 +8992,20 @@ "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", "dev": true, "requires": { - "chalk": "2.3.2", + "chalk": "2.4.0", "source-map": "0.6.1", - "supports-color": "5.3.0" + "supports-color": "5.4.0" }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "supports-color": "5.4.0" } }, "has-flag": { @@ -8890,9 +9021,9 @@ "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -8945,9 +9076,9 @@ } }, "postcss-loader": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.3.tgz", - "integrity": "sha512-RuBcNE8rjCkIB0IsbmkGFRmQJTeQJfCI88E0VTarPNTvaNSv9OFv1DvTwgtAN/qlzyiELsmmmtX/tEzKp/cdug==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.4.tgz", + "integrity": "sha512-L2p654oK945B/gDFUGgOhh7uzj19RWoY1SVMeJVoKno1H2MdbQ0RppR/28JGju4pMb22iRC7BJ9aDzbxXSLf4A==", "dev": true, "requires": { "loader-utils": "1.1.0", @@ -9025,32 +9156,44 @@ "dev": true }, "protractor": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz", - "integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.3.1.tgz", + "integrity": "sha512-AW9qJ0prx2QEMy1gnhJ1Sl1WBQL2R3fx/VnG09FEmWprPIQPK14t0B83OB/pAGddpxiDCAAV0KiNNLf2c2Y/lQ==", "dev": true, "requires": { - "@types/node": "6.0.105", + "@types/node": "6.0.106", "@types/q": "0.0.32", "@types/selenium-webdriver": "2.53.43", - "blocking-proxy": "0.0.5", + "blocking-proxy": "1.0.1", "chalk": "1.1.3", "glob": "7.1.2", - "jasmine": "2.99.0", + "jasmine": "2.8.0", "jasminewd2": "2.2.0", "optimist": "0.6.1", "q": "1.4.1", "saucelabs": "1.3.0", - "selenium-webdriver": "3.0.1", + "selenium-webdriver": "3.6.0", "source-map-support": "0.4.18", "webdriver-js-extender": "1.0.0", "webdriver-manager": "12.0.6" }, "dependencies": { "@types/node": { - "version": "6.0.105", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.105.tgz", - "integrity": "sha512-fMIbw7iw91TSInS3b2DtDse5VaQEZqs0oTjvRNIFHnoHbnji+dLwpzL1L6dYGL39RzDNPHM/Off+VNcMk4ahwQ==", + "version": "6.0.106", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.106.tgz", + "integrity": "sha512-U4Zv5fx7letrisRv6HgSSPSY00FZM4NMIkilt+IAExvQLuNa6jYVwCKcnSs2NqTN4+KDl9PskvcCiMce9iePCA==", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "dev": true + }, + "adm-zip": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", + "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", "dev": true }, "ansi-styles": { @@ -9119,12 +9262,33 @@ "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", "dev": true }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "3.1.5", + "rimraf": "2.6.2", + "tmp": "0.0.30", + "xml2js": "0.4.19" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, "webdriver-manager": { "version": "12.0.6", "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", @@ -9831,24 +9995,13 @@ } }, "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "2.0.2", + "hash-base": "3.0.4", "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - } } }, "run-queue": { @@ -9860,6 +10013,12 @@ "aproba": "1.2.0" } }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "rxjs": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", @@ -9962,12 +10121,12 @@ "dev": true }, "selenium-webdriver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", - "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", + "version": "4.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.1.tgz", + "integrity": "sha512-z88rdjHAv3jmTZ7KSGUkTvo4rGzcDGMq0oXWHNIDK96Gs31JKVdu9+FMtT4KBrVoibg8dUicJDok6GnqqttO5Q==", "dev": true, "requires": { - "adm-zip": "0.4.7", + "jszip": "3.1.5", "rimraf": "2.6.2", "tmp": "0.0.30", "xml2js": "0.4.19" @@ -10056,9 +10215,9 @@ } }, "serialize-javascript": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.4.0.tgz", - "integrity": "sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", "dev": true }, "serve-index": { @@ -11113,9 +11272,9 @@ "dev": true }, "timers-browserify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", - "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "dev": true, "requires": { "setimmediate": "1.0.5" @@ -11288,7 +11447,7 @@ "dev": true, "requires": { "arrify": "1.0.1", - "chalk": "2.3.2", + "chalk": "2.4.0", "diff": "3.5.0", "make-error": "1.3.4", "minimist": "1.2.0", @@ -11300,14 +11459,14 @@ }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "supports-color": "5.4.0" } }, "has-flag": { @@ -11338,9 +11497,9 @@ } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -11401,7 +11560,7 @@ "requires": { "babel-code-frame": "6.26.0", "builtin-modules": "1.1.1", - "chalk": "2.3.2", + "chalk": "2.4.0", "commander": "2.15.1", "diff": "3.5.0", "glob": "7.1.2", @@ -11414,14 +11573,14 @@ }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "supports-color": "5.4.0" } }, "has-flag": { @@ -11431,9 +11590,9 @@ "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -11505,9 +11664,9 @@ "dev": true }, "typescript": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", - "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", "dev": true }, "uglify-js": { @@ -11536,15 +11695,15 @@ "optional": true }, "uglifyjs-webpack-plugin": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz", - "integrity": "sha512-z0IbjpW8b3O/OVn+TTZN4pI29RN1zktFBXLIzzfZ+++cUtZ1ERSlLWgpE/5OERuEUs1ijVQnpYAkSlpoVmQmSQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz", + "integrity": "sha512-hIQJ1yxAPhEA2yW/i7Fr+SXZVMp+VEI3d42RTHBgQd2yhp/1UdBcR3QEWPV5ahBxlqQDMEMTuTEvDHSFINfwSw==", "dev": true, "requires": { "cacache": "10.0.4", "find-cache-dir": "1.0.0", "schema-utils": "0.4.5", - "serialize-javascript": "1.4.0", + "serialize-javascript": "1.5.0", "source-map": "0.6.1", "uglify-es": "3.3.9", "webpack-sources": "1.1.0", @@ -11939,6 +12098,52 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "wait-on": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-2.1.0.tgz", + "integrity": "sha512-hDwJ674+7dfiiK/cxtYCwPxlnjXDjto/pCz1PF02sXUhqCqCWsgvxZln0699PReWqXXgkxqkF6DDo5Rj9sjNvw==", + "dev": true, + "requires": { + "core-js": "2.5.3", + "joi": "9.2.0", + "minimist": "1.2.0", + "request": "2.81.0", + "rx": "4.1.0" + }, + "dependencies": { + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, + "isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=", + "dev": true + }, + "joi": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-9.2.0.tgz", + "integrity": "sha1-M4WseQGSEwy+Iw6ALsAskhW7/to=", + "dev": true, + "requires": { + "hoek": "4.2.1", + "isemail": "2.2.1", + "items": "2.1.1", + "moment": "2.20.1", + "topo": "2.0.2" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "watchpack": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", @@ -12325,10 +12530,10 @@ "selenium-webdriver": "2.53.3" }, "dependencies": { - "adm-zip": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", - "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", + "@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", "dev": true }, "sax": { @@ -12719,7 +12924,7 @@ "sockjs-client": "1.1.4", "spdy": "3.4.7", "strip-ansi": "3.0.1", - "supports-color": "5.3.0", + "supports-color": "5.4.0", "webpack-dev-middleware": "1.12.2", "yargs": "6.6.0" }, @@ -13086,9 +13291,9 @@ } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "3.0.0" diff --git a/package.json b/package.json index 331c14da8..770480b5e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "ng": "ng", "start": "npm run server-versions && ng serve --open", "start:prod": "npm run server-versions && ng serve --prod --open", - "start:docker": "docker-compose up --build", "build": "npm run server-versions && ng build --prod", "build:prod": "npm run server-versions && ng build --prod", "build:dev": "npm run server-versions && ng build", @@ -14,8 +13,13 @@ "test": "ng test --code-coverage", "test:ci": "ng test --code-coverage --single-run --no-progress && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage", "lint": "ng lint", - "e2e": "ng e2e", - "server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0" + "server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0", + "_e2e": "ng e2e", + "wd:update": "webdriver-manager update --gecko=false", + "e2e": "npm run wd:update && protractor protractor.conf.js", + "start:docker": "docker-compose up -d --build && wait-on http://localhost:8080 && wait-on http://localhost:3000", + "stop:docker": "docker-compose stop", + "e2e:docker": "npm run start:docker && npm run e2e && npm run stop:docker" }, "private": true, "dependencies": { @@ -53,22 +57,27 @@ "@types/jasmine": "^2.5.53", "@types/jasminewd2": "^2.0.2", "@types/node": "9.3.0", + "@types/selenium-webdriver": "^3.0.8", "codacy-coverage": "^2.0.3", "codelyzer": "^4.0.1", "jasmine-core": "~2.8.0", "jasmine-reporters": "^2.2.1", "jasmine-spec-reporter": "~4.2.1", "jasmine2-protractor-utils": "^1.3.0", + "jasminewd2": "^2.2.0", "karma": "~2.0.0", "karma-chrome-launcher": "~2.2.0", "karma-cli": "~1.0.1", "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", + "node-rest-client": "^3.1.0", + "protractor": "5.3.1", "rimraf": "2.6.2", + "selenium-webdriver": "4.0.0-alpha.1", "ts-node": "~4.1.0", "tslint": "~5.9.1", - "typescript": "~2.5.3" + "typescript": "~2.7.2", + "wait-on": "2.1.0" } } diff --git a/protractor.conf.js b/protractor.conf.js old mode 100644 new mode 100755 index 9a1a1b39c..3971e2d2c --- a/protractor.conf.js +++ b/protractor.conf.js @@ -11,9 +11,15 @@ const width = 1366; const height = 768; exports.config = { - allScriptsTimeout: 30000, + allScriptsTimeout: 60000, specs: [ + './e2e/suites/authentication/*.test.ts', + './e2e/suites/list-views/*.test.ts', + './e2e/suites/application/page-titles.test.ts', + './e2e/suites/navigation/*.test.ts', + './e2e/suites/pagination/*.test.ts', + './e2e/suites/actions/*.test.ts' ], capabilities: { @@ -28,12 +34,12 @@ exports.config = { directConnect: true, - baseUrl: 'http://localhost:4200', + baseUrl: 'http://localhost:3000', framework: 'jasmine2', jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 50000, + defaultTimeoutInterval: 90000, print: function() {} },