From 7d16f1335599110a9bbe74fc16c69143b461d90e Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Thu, 14 Dec 2017 11:00:57 +0000 Subject: [PATCH 01/15] Sync with development (#144) * fix navigation docs issues * update documentation (#142) * update documentation changed start command used a tags for links nested inside table and p tags, because Github did not render them correctly * set link to navigation on side-nav.md --- README.md | 2 +- docs/README.md | 2 +- docs/configuration.md | 4 ++-- docs/doc-list.md | 6 +++--- docs/images/navigation-01.png | Bin 33283 -> 25301 bytes docs/images/navigation-02.png | Bin 31178 -> 28120 bytes docs/images/navigation-03.png | Bin 77214 -> 32887 bytes docs/navigation.md | 33 +++++++++++++++------------------ docs/side-nav.md | 3 +-- 9 files changed, 23 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d8e014825..e1e1bdc9b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This project was generated with [Angular CLI](https://github.com/angular/angular ## Development server -Run `ng start` for a dev server. Navigate to `http://localhost:3000/` (opens by default). +Run `npm start` for a dev server. Navigate to `http://localhost:3000/` (opens by default). The app will automatically reload if you change any of the source files. ## Code scaffolding diff --git a/docs/README.md b/docs/README.md index b139bd934..dd04a4138 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ This application uses the latest releases from Alfresco: - [Alfresco Community Edition 201707](https://www.alfresco.com/products/community/download)

-You also need [node.js](https://nodejs.org/en/) (8.9.1 or later) installed to build it locally from source code. +You also need node.js (8.9.1 or later) installed to build it locally from source code.

The latest version of the Alfresco Content platform is required diff --git a/docs/configuration.md b/docs/configuration.md index c07b42711..bdf2ad0f0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -111,7 +111,7 @@ By default, the application ships with the following rules already predefined: ```

-You can get more details on the supported rules in the following article: [Upload Service](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/upload.service.md). +You can get more details on the supported rules in the following article: Upload Service.

### Pagination settings @@ -137,5 +137,5 @@ You can change the default settings of the pagination that gets applied to all t You can store any information in the application configuration file, and access it at runtime by using the `AppConfigService` service provided by ADF.

-Please refer to the [AppConfigService](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/app-config.service.md) documentation to get more details on Application Configuration features and API available. +Please refer to the AppConfigService documentation to get more details on Application Configuration features and API available.

diff --git a/docs/doc-list.md b/docs/doc-list.md index 49f698bac..8fe776bc7 100644 --- a/docs/doc-list.md +++ b/docs/doc-list.md @@ -84,7 +84,7 @@ actions are automatically hidden when the user does not have permission. View - Opens the selected file using the [Preview](https://github.com/Alfresco/alfresco-content-app/tree/development/src/app/components/preview) component, + Opens the selected file using the Preview component, where the file cannot be displayed natively in a browser a PDF rendition is obtained from the repository. Not applicable @@ -110,7 +110,7 @@ actions are automatically hidden when the user does not have permission. Copy Files and folders can be copied to another location in the content repository using the - [content-node-selector](https://alfresco.github.io/adf-component-catalog/components/ContentNodeSelectorComponent.html) component; + content-node-selector component; once the copy action has completed the user is notified and can undo the action (which permanently deletes the created copies). @@ -118,7 +118,7 @@ actions are automatically hidden when the user does not have permission. Move Files and folders can be moved to another location in the content repository using the - [content-node-selector](https://alfresco.github.io/adf-component-catalog/components/ContentNodeSelectorComponent.html) component; + content-node-selector component; once the move action has completed the user is notified and can undo the action (which moves the items back to the original location). diff --git a/docs/images/navigation-01.png b/docs/images/navigation-01.png index 55d205fef8968de9ad38b57c19e61f7de773b448..51fff38660d0cbb57859bb066abb6669187375f9 100644 GIT binary patch delta 21907 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 E4+wCP)c^nh literal 33283 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 Yus4KWE!PMCe&B$iuBlF$mUHBP0l`Joc>n+a literal 31178 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 literal 77214 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

o}Pl9B7#mX)KYMe_s7r@?TR8{!EpU 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 UikWwTCkB6}#^#4}4mn@`4+RTMKmY&$ diff --git a/docs/navigation.md b/docs/navigation.md index e1c656b3a..4a283a272 100644 --- a/docs/navigation.md +++ b/docs/navigation.md @@ -61,7 +61,7 @@ To change the `title` and `label` of navigation links edit the values under `BRO ```json "APP" : { ... - BROWSE: { + "BROWSE": { "PERSONAL": { "TITLE": "Personal Files", "SIDENAV_LINK": { @@ -80,21 +80,19 @@ For more information about internationalization see [Internationalization (i18n) To add custom navigation link for the application, first we need to create a component. -```src/app/components/custom-page/custom-page.component.ts``` +`src/app/components/custom-page/custom-page.component.ts` -```javascript +```js +import { Component } from '@angular/core'; - import { Component } from '@angular/core'; - - @Component({ - template: ` -

{{ title }}

+@Component({ +template: ` +

{{ title }}

` - }) - export class CustomPage { +}) +export class CustomPage { title = 'My Custom Page' - } - +} ``` Register the component in ```app.module.ts``` @@ -107,9 +105,9 @@ Register the component in ```app.module.ts``` @NgModule({ ... declarations: [ - ..., - CustomPage - ] + ..., + CustomPage + ], ... }) @@ -118,12 +116,11 @@ Register the component in ```app.module.ts``` In the `app.config.json` define a link entry which will point to the custom page ```json - { ..., "navigation": [ "main": [ ... ], - "secondary: [ ... ], + "secondary": [ ... ], "custom": [ { "icon": "work", @@ -141,7 +138,7 @@ In the `app.config.json` define a link entry which will point to the custom page Map the `/custom-route` in `app.routes.ts` as a child of `LayoutComponent` definition. -```json +```js import { CustomPage } from './components/custom-page/custom-page.component.ts'; diff --git a/docs/side-nav.md b/docs/side-nav.md index 6a5b3480d..98ac4258d 100644 --- a/docs/side-nav.md +++ b/docs/side-nav.md @@ -24,5 +24,4 @@ and uploads can be canceled which will stop uploads in progress or permanently d The navigation links are configurable via the [app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json). Default configuration creates two sections. -See [How to work with the side navigation](/) for more information about configuring the side navigation. - +See [Navigation](/navigation) for more information about configuring the side navigation. From e36ad93a984bd5fa43471c986b9c3de53e7d3289 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Thu, 14 Dec 2017 18:55:01 +0000 Subject: [PATCH 02/15] Sync latest development (#147) * fix navigation docs issues * update documentation (#142) * update documentation changed start command used a tags for links nested inside table and p tags, because Github did not render them correctly * set link to navigation on side-nav.md * [ACA-1061] update project version (#145) [ACA-1061] update project version * move e2e to a separate repo * fix "view" button (toolbar) --- e2e/components/component.ts | 43 -- e2e/components/components.ts | 32 -- e2e/components/data-table/data-table.ts | 196 ------- .../dialog/create-edit-folder-dialog.ts | 108 ---- e2e/components/header/header.ts | 42 -- e2e/components/header/user-info.ts | 64 --- e2e/components/login/login.ts | 75 --- e2e/components/menu/menu.ts | 73 --- e2e/components/pagination/pagination.ts | 75 --- e2e/components/sidenav/sidenav.ts | 68 --- e2e/components/toolbar/toolbar-actions.ts | 68 --- e2e/components/toolbar/toolbar-breadcrumb.ts | 63 --- e2e/components/toolbar/toolbar.ts | 42 -- e2e/configs.ts | 78 --- e2e/pages/browsing-page.ts | 40 -- e2e/pages/login-page.ts | 74 --- e2e/pages/logout-page.ts | 43 -- e2e/pages/page.ts | 79 --- e2e/pages/pages.ts | 28 - e2e/suites/actions/create-folder.test.ts | 279 --------- e2e/suites/actions/edit-folder.test.ts | 190 ------- e2e/suites/actions/permanently-delete.test.ts | 113 ---- e2e/suites/actions/restore.test.ts | 147 ----- .../toolbar-multiple-selection.test.ts | 535 ------------------ .../actions/toolbar-single-selection.test.ts | 535 ------------------ e2e/suites/application/page-titles.test.ts | 133 ----- e2e/suites/authentication/login.test.ts | 179 ------ e2e/suites/authentication/logout.test.ts | 74 --- e2e/suites/list-views/empty-list.test.ts | 109 ---- e2e/suites/list-views/favorites.test.ts | 157 ----- e2e/suites/list-views/file-libraries.test.ts | 131 ----- e2e/suites/list-views/personal-files.test.ts | 159 ------ e2e/suites/list-views/recent-files.test.ts | 149 ----- e2e/suites/list-views/shared-files.test.ts | 155 ----- e2e/suites/list-views/trash.test.ts | 188 ------ e2e/suites/navigation/side-navigation.test.ts | 129 ----- e2e/suites/pagination/pag-favorites.test.ts | 186 ------ .../pagination/pag-personal-files.test.ts | 183 ------ .../pagination/pag-recent-files.test.ts | 189 ------- .../pagination/pag-shared-files.test.ts | 193 ------- e2e/suites/pagination/pag-trash.test.ts | 184 ------ .../apis/favorites/favorites-api.ts | 86 --- .../apis/nodes/node-body-create.ts | 38 -- .../apis/nodes/node-content-tree.ts | 85 --- .../repo-client/apis/nodes/nodes-api.ts | 104 ---- .../apis/people/people-api-models.ts | 43 -- .../repo-client/apis/people/people-api.ts | 60 -- e2e/utilities/repo-client/apis/repo-api.ts | 71 --- .../apis/shared-links/shared-links-api.ts | 50 -- .../apis/sites/sites-api-models.ts | 42 -- .../repo-client/apis/sites/sites-api.ts | 111 ---- .../repo-client/apis/trashcan/trashcan-api.ts | 54 -- .../repo-client/repo-client-models.ts | 46 -- e2e/utilities/repo-client/repo-client.ts | 57 -- .../reporters/console/console-logger.ts | 79 --- e2e/utilities/reporters/console/console.ts | 90 --- .../rest-client/rest-client-models.ts | 63 --- e2e/utilities/rest-client/rest-client.ts | 89 --- e2e/utilities/utils.ts | 44 -- package.json | 3 +- protractor.conf.js | 6 - .../favorites/favorites.component.html | 2 +- src/app/components/files/files.component.html | 2 +- src/app/components/files/files.component.ts | 8 + .../recent-files/recent-files.component.html | 2 +- .../shared-files/shared-files.component.html | 2 +- yarn.lock | 225 +++++--- 67 files changed, 148 insertions(+), 6902 deletions(-) delete mode 100644 e2e/components/component.ts delete mode 100644 e2e/components/components.ts delete mode 100644 e2e/components/data-table/data-table.ts delete mode 100644 e2e/components/dialog/create-edit-folder-dialog.ts delete mode 100644 e2e/components/header/header.ts delete mode 100644 e2e/components/header/user-info.ts delete mode 100644 e2e/components/login/login.ts delete mode 100644 e2e/components/menu/menu.ts delete mode 100644 e2e/components/pagination/pagination.ts delete mode 100644 e2e/components/sidenav/sidenav.ts delete mode 100644 e2e/components/toolbar/toolbar-actions.ts delete mode 100644 e2e/components/toolbar/toolbar-breadcrumb.ts delete mode 100644 e2e/components/toolbar/toolbar.ts delete mode 100644 e2e/configs.ts delete mode 100644 e2e/pages/browsing-page.ts delete mode 100644 e2e/pages/login-page.ts delete mode 100644 e2e/pages/logout-page.ts delete mode 100644 e2e/pages/page.ts delete mode 100644 e2e/pages/pages.ts delete mode 100644 e2e/suites/actions/create-folder.test.ts delete mode 100644 e2e/suites/actions/edit-folder.test.ts delete mode 100644 e2e/suites/actions/permanently-delete.test.ts delete mode 100644 e2e/suites/actions/restore.test.ts delete mode 100644 e2e/suites/actions/toolbar-multiple-selection.test.ts delete mode 100644 e2e/suites/actions/toolbar-single-selection.test.ts delete mode 100644 e2e/suites/application/page-titles.test.ts delete mode 100644 e2e/suites/authentication/login.test.ts delete mode 100644 e2e/suites/authentication/logout.test.ts delete mode 100644 e2e/suites/list-views/empty-list.test.ts delete mode 100644 e2e/suites/list-views/favorites.test.ts delete mode 100644 e2e/suites/list-views/file-libraries.test.ts delete mode 100644 e2e/suites/list-views/personal-files.test.ts delete mode 100644 e2e/suites/list-views/recent-files.test.ts delete mode 100644 e2e/suites/list-views/shared-files.test.ts delete mode 100644 e2e/suites/list-views/trash.test.ts delete mode 100644 e2e/suites/navigation/side-navigation.test.ts delete mode 100644 e2e/suites/pagination/pag-favorites.test.ts delete mode 100644 e2e/suites/pagination/pag-personal-files.test.ts delete mode 100644 e2e/suites/pagination/pag-recent-files.test.ts delete mode 100644 e2e/suites/pagination/pag-shared-files.test.ts delete mode 100644 e2e/suites/pagination/pag-trash.test.ts delete mode 100644 e2e/utilities/repo-client/apis/favorites/favorites-api.ts delete mode 100644 e2e/utilities/repo-client/apis/nodes/node-body-create.ts delete mode 100644 e2e/utilities/repo-client/apis/nodes/node-content-tree.ts delete mode 100644 e2e/utilities/repo-client/apis/nodes/nodes-api.ts delete mode 100644 e2e/utilities/repo-client/apis/people/people-api-models.ts delete mode 100644 e2e/utilities/repo-client/apis/people/people-api.ts delete mode 100644 e2e/utilities/repo-client/apis/repo-api.ts delete mode 100644 e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts delete mode 100644 e2e/utilities/repo-client/apis/sites/sites-api-models.ts delete mode 100644 e2e/utilities/repo-client/apis/sites/sites-api.ts delete mode 100644 e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts delete mode 100644 e2e/utilities/repo-client/repo-client-models.ts delete mode 100644 e2e/utilities/repo-client/repo-client.ts delete mode 100644 e2e/utilities/reporters/console/console-logger.ts delete mode 100644 e2e/utilities/reporters/console/console.ts delete mode 100644 e2e/utilities/rest-client/rest-client-models.ts delete mode 100644 e2e/utilities/rest-client/rest-client.ts delete mode 100644 e2e/utilities/utils.ts diff --git a/e2e/components/component.ts b/e2e/components/component.ts deleted file mode 100644 index 24d7f1cb1..000000000 --- a/e2e/components/component.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 70992f11d..000000000 --- a/e2e/components/components.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index ea809fcf3..000000000 --- a/e2e/components/data-table/data-table.ts +++ /dev/null @@ -1,196 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -export class DataTable extends Component { - private static selectors = { - root: 'adf-datatable', - - head: 'table > thead', - columnHeader: 'tr > th', - sortedColumnHeader: ` - th.adf-data-table__header--sorted-asc, - th.adf-data-table__header--sorted-desc - `, - - body: 'table > tbody', - row: 'tr', - selectedRow: 'tr.is-selected', - cell: 'td', - locationLink: 'app-location-link', - - emptyListContainer: 'td.adf-no-content-container', - emptyFolderDragAndDrop: '.adf-empty-list_template .adf-empty-folder', - - emptyListTitle: 'div .empty-list__title', - emptyListText: 'div .empty-list__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)); - 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); - } - - 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)); - } - - getNthRow(nth: number): ElementFinder { - return this.getRows().get(nth - 1); - } - - getRowByName(name: string): ElementFinder { - return this.body.element(by.cssContainingText(`.adf-data-table-cell`, name)); - } - - countRows(): promise.Promise { - return this.getRows().count(); - } - - // Navigation/selection methods - doubleClickOnItemName(name: string): promise.Promise { - const dblClick = browser.actions() - .mouseMove(this.getRowByName(name)) - .click() - .click(); - - return dblClick.perform(); - } - - clickOnItemName(name: string): promise.Promise { - return this.getRowByName(name).click(); - } - - selectMultipleItems(names: string[]): promise.Promise { - return browser.actions().sendKeys(protractor.Key.COMMAND).perform() - .then(() => { - names.forEach(name => { - this.clickOnItemName(name); - }); - }) - .then(() => browser.actions().sendKeys(protractor.Key.NULL).perform()); - } - - clearSelection() { - this.getSelectedRows().count() - .then(count => { - if (count !== 0) { browser.refresh().then(() => this.waitForHeader()); } - }); - } - - getItemLocation(name: string) { - const rowLocator = by.cssContainingText(DataTable.selectors.row, name); - return this.body.element(rowLocator).element(this.locationLink); - } - - 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(); - }) - .catch(() => ''); - } - - getEmptyStateTitle(): promise.Promise { - return this.isEmptyList() - .then(() => { - return this.emptyListTitle.getText(); - }) - .catch(() => ''); - } - - getEmptyStateText(): promise.Promise { - return this.isEmptyList() - .then(() => { - return this.emptyListText.getText(); - }) - .catch(() => ''); - } -} diff --git a/e2e/components/dialog/create-edit-folder-dialog.ts b/e2e/components/dialog/create-edit-folder-dialog.ts deleted file mode 100644 index a6d7f29e0..000000000 --- a/e2e/components/dialog/create-edit-folder-dialog.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -export class CreateOrEditFolderDialog extends Component { - private static selectors = { - root: 'adf-folder-dialog', - - title: '.mat-dialog-title', - nameInput: '.mat-input-element[placeholder="Name" i]', - descriptionTextArea: '.mat-input-element[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); - } - - 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()) - .catch(() => ''); - } - - enterName(name: string): promise.Promise { - return this.nameInput.clear() - .then(() => this.nameInput.sendKeys(name)) - .then(() => this); - } - - enterDescription(description: string): promise.Promise { - return this.descriptionTextArea.clear() - .then(() => { - browser.actions().click(this.descriptionTextArea).sendKeys(description).perform(); - }) - .then(() => this); - } - - 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 deleted file mode 100644 index be8d271d4..000000000 --- a/e2e/components/header/header.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index da110f0a9..000000000 --- a/e2e/components/header/user-info.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, element, 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 deleted file mode 100644 index 307059de3..000000000 --- a/e2e/components/login/login.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'), - submitButton: by.css('button#login-button'), - errorMessage: by.css('.login-error-message') - }; - - 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); - - 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(); - } -} diff --git a/e2e/components/menu/menu.ts b/e2e/components/menu/menu.ts deleted file mode 100644 index 1f94354ec..000000000 --- a/e2e/components/menu/menu.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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: 'button.mat-menu-item' - }; - - items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item)); - - constructor(ancestor?: ElementFinder) { - super(Menu.selectors.root, ancestor); - } - - wait() { - return browser.wait(EC.visibilityOf(this.items.get(0)), BROWSER_WAIT_TIMEOUT); - } - - getNthItem(nth: number): ElementFinder { - return this.items.get(nth - 1); - } - - getItemByLabel(label: string): ElementFinder { - return this.component.element(by.cssContainingText(Menu.selectors.item, label)); - } - - getItemTooltip(label: string): promise.Promise { - return this.getItemByLabel(label).getAttribute('title'); - } - - getItemsCount(): promise.Promise { - return this.items.count(); - } - - clickNthItem(nth: number): promise.Promise { - return this.getNthItem(nth).click(); - } - - clickMenuItem(label: string): promise.Promise { - return this.getItemByLabel(label).click(); - } - - isMenuItemPresent(title: string): promise.Promise { - return this.component.element(by.cssContainingText(Menu.selectors.item, title)).isPresent(); - } -} diff --git a/e2e/components/pagination/pagination.ts b/e2e/components/pagination/pagination.ts deleted file mode 100644 index 7c52e3312..000000000 --- a/e2e/components/pagination/pagination.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 } from 'protractor'; -import { BROWSER_WAIT_TIMEOUT } from '../../configs'; -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.wait()) - .then(() => menu); - } - - openCurrentPageMenu(): promise.Promise { - const { menu, pagesButton } = this; - - return this.pagesButton.click() - .then(() => menu.wait()) - .then(() => menu); - } -} diff --git a/e2e/components/sidenav/sidenav.ts b/e2e/components/sidenav/sidenav.ts deleted file mode 100644 index 406a5f8bb..000000000 --- a/e2e/components/sidenav/sidenav.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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-link', - activeLink: '.sidenav-menu__item-link--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.wait()) - .then(() => menu); - } - - isActiveByLabel(label: string): promise.Promise { - return this.getLinkByLabel(label).getAttribute('class') - .then(className => className.includes(Sidenav.selectors.activeLink.replace('.', ''))); - } - - getLinkByLabel(label: string): ElementFinder { - return this.component.element(by.cssContainingText(Sidenav.selectors.link, label)); - } - - 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 deleted file mode 100644 index 5998cdedf..000000000 --- a/e2e/components/toolbar/toolbar-actions.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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(): promise.Promise { - const { menu } = this; - const moreButton = this.getButtonByTitleAttribute('More actions'); - - return moreButton - .click() - .then(() => menu.wait()) - .then(() => menu); - } -} diff --git a/e2e/components/toolbar/toolbar-breadcrumb.ts b/e2e/components/toolbar/toolbar-breadcrumb.ts deleted file mode 100644 index 872782a6d..000000000 --- a/e2e/components/toolbar/toolbar-breadcrumb.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 ToolbarBreadcrumb extends Component { - private static selectors = { - root: 'adf-breadcrumb', - item: '.adf-breadcrumb-item' - }; - - items: ElementArrayFinder = this.component.all(by.css(ToolbarBreadcrumb.selectors.item)); - - constructor(ancestor?: ElementFinder) { - super(ToolbarBreadcrumb.selectors.root, ancestor); - } - - getNthItem(nth: number): ElementFinder { - return this.items.get(nth - 1); - } - - getItemsCount(): promise.Promise { - return this.items.count(); - } - - getFirstItemName(): promise.Promise { - return this.items.get(0).getAttribute('title'); - } - - getCurrentItem(): promise.Promise { - return this.getItemsCount() - .then(count => this.getNthItem(count)); - } - - getCurrentItemName(): promise.Promise { - return this.getCurrentItem() - .then(node => node.getAttribute('title')); - } -} diff --git a/e2e/components/toolbar/toolbar.ts b/e2e/components/toolbar/toolbar.ts deleted file mode 100644 index ed0bbe4d5..000000000 --- a/e2e/components/toolbar/toolbar.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 } 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 deleted file mode 100644 index f0f304335..000000000 --- a/e2e/configs.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 = 20000; - -// 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 deleted file mode 100644 index 033859939..000000000 --- a/e2e/pages/browsing-page.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 0a64257b0..000000000 --- a/e2e/pages/login-page.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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.login.enterCredentials(username, pass).submit() - .then(() => super.waitForApp()); - } - - loginWithAdmin(): promise.Promise { - return this.loginWith(ADMIN_USERNAME, ADMIN_PASSWORD); - } - - tryLoginWith(username: string, password?: string): promise.Promise { - const pass = password || username; - return 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 deleted file mode 100644 index e250a9627..000000000 --- a/e2e/pages/logout-page.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 2ac3b031b..000000000 --- a/e2e/pages/page.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 = false; - - private locators = { - app: by.css('app-root'), - layout: by.css('app-layout'), - overlay: by.css('.cdk-overlay-container'), - snackBar: by.css('simple-snack-bar'), - snackBarAction: by.css('.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); - public snackBar: ElementFinder = element(this.locators.snackBar); - - 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); - } - - refresh(): promise.Promise { - return browser.refresh(); - } - - isSnackBarDisplayed(): promise.Promise { - return this.snackBar.isDisplayed(); - } - - getSnackBarMessage(): promise.Promise { - return this.isSnackBarDisplayed() - .then(() => this.snackBar.getText()) - .catch(() => ''); - } - - getSnackBarAction(): ElementFinder { - return this.snackBar.element(this.locators.snackBarAction); - } -} diff --git a/e2e/pages/pages.ts b/e2e/pages/pages.ts deleted file mode 100644 index ce52fea89..000000000 --- a/e2e/pages/pages.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index b26b47686..000000000 --- a/e2e/suites/actions/create-folder.test.ts +++ /dev/null @@ -1,279 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by, ElementFinder } from 'protractor'; - -import { SIDEBAR_LABELS, BROWSER_WAIT_TIMEOUT, 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, NodeContentTree } 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; - - function openCreateDialog(): any { - return personalFilesPage.sidenav.openNewMenu() - .then(menu => menu.clickMenuItem('Create folder')) - .then(() => createDialog.waitForDialogToOpen()); - } - - 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.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) - .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('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(() => openCreateDialog()) - .then(() => createDialog.enterName(folderName1)) - .then(() => createDialog.clickCreate()) - .then(() => createDialog.waitForDialogToClose()) - .then(() => dataTable.waitForHeader()) - .then(() => { - const isPresent = dataTable.getRowByName(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(() => openCreateDialog()) - .then(() => createDialog.enterName(folderName2)) - .then(() => createDialog.enterDescription(folderDescription)) - .then(() => createDialog.clickCreate()) - .then(() => createDialog.waitForDialogToClose()) - .then(() => dataTable.waitForHeader()) - .then(() => { - const isPresent = dataTable.getRowByName(folderName2).isPresent(); - expect(isPresent).toBe(true, 'Folder not displayed in list view'); - }) - .then(() => { - expect(apis.user.nodes.getNodeDescription(folderName2)).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(() => openCreateDialog()) - .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(() => openCreateDialog()) - .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(() => openCreateDialog()) - .then(() => createDialog.enterName('folder-name.')) - .then(dialog => { - const isCreateEnabled = dialog.createButton.isEnabled(); - const validationMessage = dialog.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(() => openCreateDialog()) - .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(() => openCreateDialog()) - .then(() => createDialog.enterName(' ')) - .then(dialog => { - const isCreateEnabled = dialog.createButton.isEnabled(); - const validationMessage = dialog.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(() => openCreateDialog()) - .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(() => openCreateDialog()) - .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(() => openCreateDialog()) - .then(() => createDialog.enterName(nameWithSpaces)) - .then(() => createDialog.clickCreate()) - .then(() => createDialog.waitForDialogToClose()) - .then(() => dataTable.waitForHeader()) - .then(() => { - const isPresent = dataTable.getRowByName(nameWithSpaces.trim()).isPresent(); - expect(isPresent).toBe(true, 'Folder not displayed in list view'); - }); - }); -}); diff --git a/e2e/suites/actions/edit-folder.test.ts b/e2e/suites/actions/edit-folder.test.ts deleted file mode 100644 index 7a8826620..000000000 --- a/e2e/suites/actions/edit-folder.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, element, browser, by, ElementFinder, promise } 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 = 'my folder 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.createNodeWithProperties( folderName, '', folderDescription, parent )) - .then(() => apis.user.nodes.createFolders([ folderNameToEdit, duplicateFolderName ], parent)) - .then(() => loginPage.load()) - .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.enterName(folderNameEdited)) - .then(() => editDialog.enterDescription(folderDescriptionEdited)) - .then(() => editDialog.clickUpdate()) - .then(() => editDialog.waitForDialogToClose()) - .then(() => dataTable.waitForHeader()) - .then(() => { - const isPresent = dataTable.getRowByName(folderNameEdited).isPresent(); - expect(isPresent).toBe(true, 'Folder not displayed in list view'); - }) - .then(() => { - expect(apis.user.nodes.getNodeDescription(folderNameEdited)).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.getRowByName(folderName).isPresent()).toBe(true, 'Folder not displayed in list view'); - }); - }); -}); diff --git a/e2e/suites/actions/permanently-delete.test.ts b/e2e/suites/actions/permanently-delete.test.ts deleted file mode 100644 index 7cf88a95f..000000000 --- a/e2e/suites/actions/permanently-delete.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, 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 } = trashPage; - const { 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.load()) - .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', () => { - dataTable.clickOnItemName(file1) - .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toBe(`${file1} deleted`); - expect(dataTable.getRowByName(file1).isPresent()).toBe(false, 'Item was not deleted'); - }); - }); - - it('delete folder', () => { - dataTable.clickOnItemName(folder1) - .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toBe(`${folder1} deleted`); - expect(dataTable.getRowByName(folder1).isPresent()).toBe(false, 'Item was not deleted'); - }); - }); - - it('delete multiple items', () => { - dataTable.selectMultipleItems([ file2, folder2 ]) - .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toBe(`2 items deleted`); - expect(dataTable.getRowByName(file2).isPresent()).toBe(false, 'Item was not deleted'); - expect(dataTable.getRowByName(folder2).isPresent()).toBe(false, 'Item was not deleted'); - }); - }); -}); diff --git a/e2e/suites/actions/restore.test.ts b/e2e/suites/actions/restore.test.ts deleted file mode 100644 index 179408cb5..000000000 --- a/e2e/suites/actions/restore.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, 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 file1 = `file-${Utils.random()}.txt`; - const file2 = `file-${Utils.random()}.txt`; - const file3 = `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 personalFilesPage = new BrowsingPage(); - const { dataTable } = trashPage; - const { toolbar } = trashPage; - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => apis.user.nodes.createFiles([ file1, file2, file3 ])) - .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.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterAll(done => { - Promise.all([ - apis.user.nodes.deleteNodesById(filesIds), - apis.user.nodes.deleteNodesById(foldersIds), - apis.admin.trashcan.emptyTrash(), - logoutPage.load() - ]) - .then(done); - }); - - it('restore file', () => { - dataTable.clickOnItemName(file1) - .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toContain(`${file1} restored`); - expect(text).toContain(`View`); - expect(dataTable.getRowByName(file1).isPresent()).toBe(false, 'Item was not removed from list'); - }) - .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) - .then(() => personalFilesPage.dataTable.waitForHeader()) - .then(() => { - expect(personalFilesPage.dataTable.getRowByName(file1).isPresent()).toBe(true, 'Item not displayed in list'); - }); - }); - - it('restore folder', () => { - dataTable.clickOnItemName(folder1) - .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toContain(`${folder1} restored`); - expect(text).toContain(`View`); - expect(dataTable.getRowByName(folder1).isPresent()).toBe(false, 'Item was not removed from list'); - }) - .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) - .then(() => personalFilesPage.dataTable.waitForHeader()) - .then(() => { - expect(personalFilesPage.dataTable.getRowByName(folder1).isPresent()).toBe(true, 'Item not displayed in list'); - }); - }); - - it('restore multiple items', () => { - dataTable.selectMultipleItems([ file2, folder2 ]) - .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) - .then(() => trashPage.getSnackBarMessage()) - .then(text => { - expect(text).toContain(`Restore successful`); - expect(text).not.toContain(`View`); - expect(dataTable.getRowByName(file2).isPresent()).toBe(false, 'Item was not removed from list'); - expect(dataTable.getRowByName(folder2).isPresent()).toBe(false, 'Item was not removed from list'); - }) - .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) - .then(() => personalFilesPage.dataTable.waitForHeader()) - .then(() => { - expect(personalFilesPage.dataTable.getRowByName(file2).isPresent()).toBe(true, 'Item not displayed in list'); - expect(personalFilesPage.dataTable.getRowByName(folder2).isPresent()).toBe(true, 'Item not displayed in list'); - }); - }); - - it('View from notification', () => { - dataTable.clickOnItemName(file3) - .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) - .then(() => trashPage.getSnackBarAction().click()) - .then(() => personalFilesPage.dataTable.waitForHeader()) - .then(() => { - expect(personalFilesPage.sidenav.isActiveByLabel('Personal Files')).toBe(true, 'Personal Files sidebar link not active'); - expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - }); -}); diff --git a/e2e/suites/actions/toolbar-multiple-selection.test.ts b/e2e/suites/actions/toolbar-multiple-selection.test.ts deleted file mode 100644 index 7a0f00ab8..000000000 --- a/e2e/suites/actions/toolbar-multiple-selection.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { APP_ROUTES, 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.shareFileById(file1Id)) - .then(() => apis.user.shared.shareFileById(file2Id)) - - .then(() => apis.user.favorites.addFavoriteById('file', file1Id)) - .then(() => apis.user.favorites.addFavoriteById('file', file2Id)) - .then(() => apis.user.favorites.addFavoriteById('folder', folder1Id)) - .then(() => apis.user.favorites.addFavoriteById('folder', folder2Id)) - - .then(() => apis.user.nodes.deleteNodeById(fileForDelete1Id, false)) - .then(() => apis.user.nodes.deleteNodeById(fileForDelete2Id, false)) - .then(() => apis.user.nodes.deleteNodeById(folderForDelete1Id, false)) - .then(() => apis.user.nodes.deleteNodeById(folderForDelete2Id, false)) - - .then(done); - }); - - afterAll(done => { - Promise.all([ - apis.user.nodes.deleteNodeById(file1Id), - apis.user.nodes.deleteNodeById(file2Id), - apis.user.nodes.deleteNodeById(folder1Id), - apis.user.nodes.deleteNodeById(folder2Id), - - apis.user.trashcan.permanentlyDelete(fileForDelete1Id), - apis.user.trashcan.permanentlyDelete(fileForDelete2Id), - apis.user.trashcan.permanentlyDelete(folderForDelete1Id), - apis.user.trashcan.permanentlyDelete(folderForDelete2Id), - - logoutPage.load() - ]) - .then(done); - }); - - xit(''); - - describe('Personal Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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('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(() => 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(() => 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(() => 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.load() - .then(() => 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(() => 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(() => 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(() => dataTable.clearSelection()); - }); - }); - - describe('user is Consumer', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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(() => 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(() => 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(() => dataTable.clearSelection()); - }); - }); - }); - - describe('Shared Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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(() => dataTable.clearSelection()); - }); - }); - - describe('Recent Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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(() => dataTable.clearSelection()); - }); - }); - - describe('Favorites', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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(() => 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(() => 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(() => dataTable.clearSelection()); - }); - }); - - describe('Trash', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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 deleted file mode 100644 index 9721ef5d0..000000000 --- a/e2e/suites/actions/toolbar-single-selection.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { APP_ROUTES, 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('Personal Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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.$('body').click()); - }); - - 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.$('body').click()); - }); - }); - - 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.load() - .then(() => 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.$('body').click()); - }); - - 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.$('body').click()); - }); - }); - - describe('user is Consumer', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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.$('body').click()); - }); - - 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.$('body').click()); - }); - }); - }); - - describe('Shared Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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.$('body').click()); - }); - }); - - describe('Recent Files', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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.$('body').click()); - }); - }); - - describe('Favorites', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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.$('body').click()); - }); - - 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.$('body').click()); - }); - }); - - describe('Trash', () => { - beforeAll(done => { - apis.user.nodes.deleteNodeById(fileForDeleteId, false) - .then(() => apis.user.nodes.deleteNodeById(folderForDeleteId, false)) - .then(() => loginPage.load()) - .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/application/page-titles.test.ts b/e2e/suites/application/page-titles.test.ts deleted file mode 100644 index 69ee99f2e..000000000 --- a/e2e/suites/application/page-titles.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -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.load() - .then(() => loginPage.loginWithAdmin()) - .then(() => page.signOut()) - .then(() => { - expect(browser.getTitle()).toContain('Sign in'); - }); - }); - - it('when pressing Back after Logout', () => { - loginPage.load() - .then(() => loginPage.loginWithAdmin()) - .then(() => page.signOut()) - .then(() => browser.navigate().back()) - .then(() => { - expect(browser.getTitle()).toContain('Sign in'); - }); - }); - }); - - describe('on list views', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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 deleted file mode 100644 index 50f976259..000000000 --- a/e2e/suites/authentication/login.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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' - }; - - 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 - }) - ]) - .then(done); - }); - - beforeEach(done => { - loginPage.load().then(done); - }); - - afterEach(done => { - logoutPage.load() - .then(() => Utils.clearLocalStorage()) - .then(done); - }); - - xit(''); - - 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); - }); - }); - }); - - describe('with invalid credentials', () => { - const { login: loginComponent } = loginPage; - const { submitButton, errorMessage } = loginComponent; - - 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); - }); - }); - - 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); - }); - }); - }); -}); diff --git a/e2e/suites/authentication/logout.test.ts b/e2e/suites/authentication/logout.test.ts deleted file mode 100644 index 4e4a78212..000000000 --- a/e2e/suites/authentication/logout.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, BROWSER_WAIT_TIMEOUT } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient } from '../../utilities/repo-client/repo-client'; - -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.load() - .then(() => loginPage.loginWith(johnDoe)) - .then(done); - }); - - afterEach((done) => { - logoutPage.load() - .then(() => Utils.clearLocalStorage()) - .then(done); - }); - - it('redirects to Login page, on sign out', () => { - page.signOut() - .then(() => { - expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); - }); - }); - - it('redirects to Login page when pressing browser Back after logout', () => { - page.signOut() - .then(() => browser.navigate().back()) - .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 deleted file mode 100644 index 5e20cb87f..000000000 --- a/e2e/suites/list-views/empty-list.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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.load()) - .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', () => { - 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.getEmptyStateText()).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.getEmptyStateText()).toContain('Items you share using the Share option are shown here.'); - }); - }); - - it('empty Recent Files', () => { - 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.getEmptyStateText()).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.getEmptyStateText()).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 deleted file mode 100644 index 8073b7d96..000000000 --- a/e2e/suites/list-views/favorites.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by } from 'protractor'; - -import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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 = `file-${Utils.random()}.txt`; - const fileName2 = `file-${Utils.random()}.txt`; - - 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(() => loginPage.load()) - .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 ]), - 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', () => { - expect(dataTable.countRows()).toEqual(3, 'Incorrect number of items displayed'); - expect(dataTable.getRowByName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); - expect(dataTable.getRowByName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); - expect(dataTable.getRowByName(folderName).isPresent()).toBe(true, `${folderName} not displayed`); - }); - - it('Location column displays the parent folder of the files', () => { - const itemsLocations = { - [fileName1]: siteName, - [fileName2]: folderName, - [folderName]: 'Personal Files' - }; - - 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]; - return acc; - }, {}); - }) - .then((favoritesList) => { - Object.keys(itemsLocations).forEach((item) => { - expect(favoritesList[item]).toEqual(itemsLocations[item]); - }); - }); - }); - - it('Location column redirect - item in user Home', () => { - dataTable.clickItemLocation(folderName) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); - - it('Location column redirect - file in folder', () => { - dataTable.clickItemLocation(fileName2) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(folderName); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); - - it('Location column redirect - file in site', () => { - dataTable.clickItemLocation(fileName1) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(siteName); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('File Libraries'); - }); - }); - -}); diff --git a/e2e/suites/list-views/file-libraries.test.ts b/e2e/suites/list-views/file-libraries.test.ts deleted file mode 100644 index 89ac75a5b..000000000 --- a/e2e/suites/list-views/file-libraries.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by } from 'protractor'; - -import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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 adminSite = `admin-${Utils.random()}`; - - const apis = { - admin: new RepoClient(), - user: new RepoClient(username, password) - }; - - const loginPage = new LoginPage(); - const logoutPage = new LogoutPage(); - const fileLibrariesPage = new BrowsingPage(APP_ROUTES.FILE_LIBRARIES); - 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), - apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE), - apis.admin.sites.createSite(adminSite, SITE_VISIBILITY.PUBLIC) - ]) - .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(() => loginPage.load()) - .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.deleteSite(sitePublic), - apis.admin.sites.deleteSite(siteModerated), - apis.admin.sites.deleteSite(sitePrivate), - apis.admin.sites.deleteSite(adminSite), - 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', () => { - const sitesCount = dataTable.countRows(); - - const expectedSites = { - [sitePrivate]: SITE_VISIBILITY.PRIVATE, - [siteModerated]: SITE_VISIBILITY.MODERATED, - [sitePublic]: SITE_VISIBILITY.PUBLIC - }; - - expect(sitesCount).toEqual(3, 'Incorrect number of sites displayed'); - expect(dataTable.getRowByName(adminSite).isPresent()).toBe(false, 'Incorrect site appears in list'); - - dataTable.getRows() - .map((row) => { - return row.all(by.css('td')).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', () => { - // // cannot be implemented until ACA-987 is fixed - // }); -}); diff --git a/e2e/suites/list-views/personal-files.test.ts b/e2e/suites/list-views/personal-files.test.ts deleted file mode 100644 index 7a26d04eb..000000000 --- a/e2e/suites/list-views/personal-files.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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('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.load() - .then(() => 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', () => { - expect(dataTable.getRowByName('Data Dictionary').isPresent()).toBe(true); - }); - - it('has created content', () => { - expect(dataTable.getRowByName(adminFolder).isPresent()).toBe(true); - }); - }); - - describe(`Regular user's personal files`, () => { - beforeAll(done => { - loginPage.load() - .then(() => 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', () => { - 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', () => { - expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); - }); - - it('has user created content', () => { - expect(dataTable.getRowByName(userFolder).isPresent()) - .toBe(true); - }); - - it('navigates to folder', () => { - 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.getRowByName(userFile).isPresent()) - .toBe(true, 'user file is missing'); - }); - }); - }); -}); diff --git a/e2e/suites/list-views/recent-files.test.ts b/e2e/suites/list-views/recent-files.test.ts deleted file mode 100644 index 6ce587ec5..000000000 --- a/e2e/suites/list-views/recent-files.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by } from 'protractor'; - -import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } from '../../utilities/repo-client/repo-client'; - -describe('Recent Files', () => { - const username = `user-${Utils.random()}`; - const password = username; - - const folderName = `folder-${Utils.random()}`; - let folderId; - const fileName1 = `file-${Utils.random()}.txt`; - - const fileName2 = `file-${Utils.random()}.txt`; - let file2Id; - - const apis = { - admin: new RepoClient(), - user: new RepoClient(username, password) - }; - - 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(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) - .then(() => dataTable.isEmptyList()) - .then(empty => { - if (empty) { - browser.sleep(5000); - recentFilesPage.refresh(); - } - }) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterAll(done => { - Promise.all([ - apis.user.nodes.deleteNodesById([ folderId, file2Id ]), - logoutPage.load() - ]) - .then(done); - }); - - it('has the correct columns', () => { - 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('displays the files added by the current user in the last 30 days', () => { - expect(dataTable.countRows()).toEqual(2, 'Incorrect number of sites displayed'); - expect(dataTable.getRowByName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); - expect(dataTable.getRowByName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); - }); - - it('Location column displays the parent folder of the file', () => { - const itemsLocations = { - [fileName2]: 'Personal Files', - [fileName1]: folderName - }; - - 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]; - return acc; - }, {}); - }) - .then((recentList) => { - Object.keys(itemsLocations).forEach((item) => { - expect(recentList[item]).toEqual(itemsLocations[item]); - }); - }); - }); - - it('Location column redirect - file in user Home', () => { - dataTable.clickItemLocation(fileName1) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(folderName); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); - - it('Location column redirect - file in folder', () => { - dataTable.clickItemLocation(fileName2) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); -}); diff --git a/e2e/suites/list-views/shared-files.test.ts b/e2e/suites/list-views/shared-files.test.ts deleted file mode 100644 index c4bb2cddc..000000000 --- a/e2e/suites/list-views/shared-files.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by } from 'protractor'; - -import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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 fileUser = `file-${Utils.random()}.txt`; - - 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([ fileUser ], folderUser)) - .then(resp => apis.user.shared.shareFileById(resp.data.entry.id)) - - .then(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - sharedFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) - .then(() => dataTable.isEmptyList()) - .then(empty => { - if (empty) { - browser.sleep(5000); - sharedFilesPage.refresh(); - } - }) - .then(() => dataTable.waitForHeader()) - .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', () => { - 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('displays the files shared by everyone', () => { - expect(dataTable.countRows()).toEqual(2, 'Incorrect number of items displayed'); - expect(dataTable.getRowByName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); - expect(dataTable.getRowByName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); - }); - - it('Location column displays the parent folder of the file', () => { - const itemsLocations = { - [fileAdmin]: siteName, - [fileUser]: folderUser - }; - - 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]; - return acc; - }, {}); - }) - .then((recentList) => { - Object.keys(itemsLocations).forEach((item) => { - expect(recentList[item]).toEqual(itemsLocations[item]); - }); - }); - }); - - it('Location column redirect - file in user Home', () => { - dataTable.clickItemLocation(fileUser) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(folderUser); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); - - it('Location column redirect - file in site', () => { - dataTable.clickItemLocation(fileAdmin) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(siteName); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('File Libraries'); - }); - }); -}); diff --git a/e2e/suites/list-views/trash.test.ts b/e2e/suites/list-views/trash.test.ts deleted file mode 100644 index 7c4969bc4..000000000 --- a/e2e/suites/list-views/trash.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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, by } from 'protractor'; - -import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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.load() - .then(() => 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', () => { - expect(dataTable.countRows()).toEqual(5, 'Incorrect number of deleted items displayed'); - - expect(dataTable.getRowByName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); - expect(dataTable.getRowByName(folderAdmin).isPresent()).toBe(true, `${folderAdmin} not displayed`); - expect(dataTable.getRowByName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); - expect(dataTable.getRowByName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); - expect(dataTable.getRowByName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); - }); - }); - - describe('as user', () => { - beforeAll(done => { - loginPage.load() - .then(() => 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', () => { - expect(dataTable.countRows()).toEqual(3, 'Incorrect number of deleted items displayed'); - - expect(dataTable.getRowByName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); - expect(dataTable.getRowByName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); - expect(dataTable.getRowByName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); - expect(dataTable.getRowByName(fileAdmin).isPresent()).toBe(false, `${fileAdmin} is displayed`); - }); - - it('Location column redirect - file in user Home', () => { - dataTable.clickItemLocation(fileUser) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe('Personal Files'); - }); - }); - - it('Location column redirect - file in site', () => { - dataTable.clickItemLocation(fileSite) - .then(() => breadcrumb.getCurrentItemName()) - .then(name => { - expect(name).toBe(siteName); - }) - .then(() => breadcrumb.getFirstItemName()) - .then(name => { - expect(name).toBe('File Libraries'); - }); - }); - }); -}); diff --git a/e2e/suites/navigation/side-navigation.test.ts b/e2e/suites/navigation/side-navigation.test.ts deleted file mode 100644 index 569c0fc5b..000000000 --- a/e2e/suites/navigation/side-navigation.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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('Side navigation', () => { - const loginPage = new LoginPage(); - const logoutPage = new LogoutPage(); - const page = new BrowsingPage(); - - beforeAll(done => { - loginPage.load() - .then(() => loginPage.loginWithAdmin()) - .then(done); - }); - - beforeEach(done => { - page.dataTable.wait().then(done); - }); - - afterAll(done => { - logoutPage.load().then(done); - }); - - it('has "Personal Files" as default', () => { - expect(browser.getCurrentUrl()) - .toContain(APP_ROUTES.PERSONAL_FILES); - - expect(page.sidenav.isActiveByLabel('Personal Files')) - .toBe(true, 'Active link'); - }); - - it('navigates to "File Libraries"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.FILE_LIBRARIES); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true); - }); - }); - - it('navigates to "Personal Files"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.PERSONAL_FILES); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.PERSONAL_FILES)).toBe(true); - }); - }); - - it('navigates to "Shared Files"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.SHARED_FILES); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.SHARED_FILES)).toBe(true); - }); - }); - - it('navigates to "Recent Files"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.RECENT_FILES); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.RECENT_FILES)).toBe(true); - }); - }); - - it('navigates to "Favorites"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.FAVORITES); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FAVORITES)).toBe(true); - }); - }); - - it('navigates to "Trash"', () => { - const { sidenav, dataTable } = page; - - sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) - .then(() => dataTable.wait()) - .then(() => browser.getCurrentUrl()) - .then(url => { - expect(url).toContain(APP_ROUTES.TRASHCAN); - expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.TRASH)).toBe(true); - }); - }); -}); diff --git a/e2e/suites/pagination/pag-favorites.test.ts b/e2e/suites/pagination/pag-favorites.test.ts deleted file mode 100644 index 18c86311d..000000000 --- a/e2e/suites/pagination/pag-favorites.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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; - - function resetToDefaultPageSize(): promise.Promise { - return pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('25')) - .then(() => dataTable.waitForHeader()); - } - - function resetToDefaultPageNumber(): promise.Promise { - return pagination.openCurrentPageMenu() - .then(menu => menu.clickMenuItem('1')) - .then(() => dataTable.waitForHeader()); - } - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => nodesApi.createFiles(files, parent)) - .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) - - .then(() => favoritesApi.addFavoritesByIds('file', filesIds)) - - .then(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterEach(done => { - browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); - }); - - afterAll(done => { - Promise.all([ - nodesApi.deleteNodes([ parent ]), - logoutPage.load() - ]) - .then(done); - }); - - it('default values', () => { - 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', () => { - pagination.openMaxItemsMenu() - .then(menu => { - const [ first, second, third ] = [1, 2, 3] - .map(nth => menu.getNthItem(nth).getText()); - expect(first).toBe('25'); - expect(second).toBe('50'); - expect(third).toBe('100'); - }); - }); - - it('change the page size', () => { - pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('50')) - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.maxItems.getText()).toContain('50'); - expect(pagination.totalPages.getText()).toContain('of 3'); - }) - - .then(() => resetToDefaultPageSize()); - }); - - it('current page menu items', () => { - pagination.openCurrentPageMenu() - .then(menu => { - expect(menu.getItemsCount()).toBe(5); - }); - }); - - it('change the current page from menu', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-40.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to next page', () => { - pagination.nextButton.click() - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.range.getText()).toContain('26-50 of 101'); - expect(dataTable.getRowByName('file-70.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to previous page', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-88.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('last page', () => { - pagination.openCurrentPageMenu() - .then(menu => menu.clickNthItem(5)) - .then(() => dataTable.waitForHeader()) - .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'); - }); - }); -}); diff --git a/e2e/suites/pagination/pag-personal-files.test.ts b/e2e/suites/pagination/pag-personal-files.test.ts deleted file mode 100644 index aed53568d..000000000 --- a/e2e/suites/pagination/pag-personal-files.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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`); - - function resetToDefaultPageSize(): promise.Promise { - return pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('25')) - .then(() => dataTable.waitForHeader()); - } - - function resetToDefaultPageNumber(): promise.Promise { - return pagination.openCurrentPageMenu() - .then(menu => menu.clickMenuItem('1')) - .then(() => dataTable.waitForHeader()); - } - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => nodesApi.createFiles(files, parent)) - - .then(() => loginPage.load()) - .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().sendKeys(protractor.Key.ESCAPE).perform().then(done); - }); - - afterAll(done => { - Promise.all([ - nodesApi.deleteNodes([ parent ]), - logoutPage.load() - ]) - .then(done); - }); - - it('default values', () => { - 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', () => { - pagination.openMaxItemsMenu() - .then(menu => { - const [ first, second, third ] = [1, 2, 3] - .map(nth => menu.getNthItem(nth).getText()); - expect(first).toBe('25'); - expect(second).toBe('50'); - expect(third).toBe('100'); - }); - }); - - it('change the page size', () => { - pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('50')) - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.maxItems.getText()).toContain('50'); - expect(pagination.totalPages.getText()).toContain('of 3'); - }) - - .then(() => resetToDefaultPageSize()); - }); - - it('current page menu items', () => { - pagination.openCurrentPageMenu() - .then(menu => { - expect(menu.getItemsCount()).toBe(5); - }); - }); - - it('change the current page from menu', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-60.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to next page', () => { - pagination.nextButton.click() - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.range.getText()).toContain('26-50 of 101'); - expect(dataTable.getRowByName('file-30.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to previous page', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-12.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('last page', () => { - pagination.openCurrentPageMenu() - .then(menu => menu.clickNthItem(5)) - .then(() => dataTable.waitForHeader()) - .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'); - }); - }); -}); diff --git a/e2e/suites/pagination/pag-recent-files.test.ts b/e2e/suites/pagination/pag-recent-files.test.ts deleted file mode 100644 index 7a1089e12..000000000 --- a/e2e/suites/pagination/pag-recent-files.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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 } = 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`); - - function resetToDefaultPageSize(): promise.Promise { - return pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('25')) - .then(() => dataTable.waitForHeader()); - } - - function resetToDefaultPageNumber(): promise.Promise { - return pagination.openCurrentPageMenu() - .then(menu => menu.clickMenuItem('1')) - .then(() => dataTable.waitForHeader()); - } - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => nodesApi.createFiles(files, parent)) - - .then(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) - .then(() => dataTable.isEmptyList()) - .then(empty => { - if (empty) { - browser.sleep(3000); - page.refresh(); - } - }) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterEach(done => { - browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); - }); - - afterAll(done => { - Promise.all([ - nodesApi.deleteNodes([ parent ]), - logoutPage.load() - ]) - .then(done); - }); - - it('default values', () => { - 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', () => { - pagination.openMaxItemsMenu() - .then(menu => { - const [ first, second, third ] = [1, 2, 3] - .map(nth => menu.getNthItem(nth).getText()); - expect(first).toBe('25'); - expect(second).toBe('50'); - expect(third).toBe('100'); - }); - }); - - it('change the page size', () => { - pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('50')) - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.maxItems.getText()).toContain('50'); - expect(pagination.totalPages.getText()).toContain('of 3'); - }) - - .then(() => resetToDefaultPageSize()); - }); - - it('current page menu items', () => { - pagination.openCurrentPageMenu() - .then(menu => { - expect(menu.getItemsCount()).toBe(5); - }); - }); - - it('change the current page from menu', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-40.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to next page', () => { - pagination.nextButton.click() - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.range.getText()).toContain('26-50 of 101'); - expect(dataTable.getRowByName('file-70.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to previous page', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-88.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('last page', () => { - pagination.openCurrentPageMenu() - .then(menu => menu.clickNthItem(5)) - .then(() => dataTable.waitForHeader()) - .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'); - }); - }); -}); diff --git a/e2e/suites/pagination/pag-shared-files.test.ts b/e2e/suites/pagination/pag-shared-files.test.ts deleted file mode 100644 index ac6faca9c..000000000 --- a/e2e/suites/pagination/pag-shared-files.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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; - - function resetToDefaultPageSize(): promise.Promise { - return pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('25')) - .then(() => dataTable.waitForHeader()); - } - - function resetToDefaultPageNumber(): promise.Promise { - return pagination.openCurrentPageMenu() - .then(menu => menu.clickMenuItem('1')) - .then(() => dataTable.waitForHeader()); - } - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => nodesApi.createFiles(files, parent)) - .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) - - .then(() => sharedApi.shareFilesByIds(filesIds)) - - .then(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) - .then(() => dataTable.isEmptyList()) - .then(empty => { - if (empty) { - browser.sleep(6000); - page.refresh(); - } - }) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterEach(done => { - browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); - }); - - afterAll(done => { - Promise.all([ - nodesApi.deleteNodes([ parent ]), - logoutPage.load() - ]) - .then(done); - }); - - it('default values', () => { - 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', () => { - pagination.openMaxItemsMenu() - .then(menu => { - const [ first, second, third ] = [1, 2, 3] - .map(nth => menu.getNthItem(nth).getText()); - expect(first).toBe('25'); - expect(second).toBe('50'); - expect(third).toBe('100'); - }); - }); - - it('change the page size', () => { - pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('50')) - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.maxItems.getText()).toContain('50'); - expect(pagination.totalPages.getText()).toContain('of 3'); - }) - - .then(() => resetToDefaultPageSize()); - }); - - it('current page menu items', () => { - pagination.openCurrentPageMenu() - .then(menu => { - expect(menu.getItemsCount()).toBe(5); - }); - }); - - it('change the current page from menu', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-40.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to next page', () => { - pagination.nextButton.click() - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.range.getText()).toContain('26-50 of 101'); - expect(dataTable.getRowByName('file-70.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to previous page', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-88.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('last page', () => { - pagination.openCurrentPageMenu() - .then(menu => menu.clickNthItem(5)) - .then(() => dataTable.waitForHeader()) - .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'); - }); - }); -}); diff --git a/e2e/suites/pagination/pag-trash.test.ts b/e2e/suites/pagination/pag-trash.test.ts deleted file mode 100644 index 4f318877c..000000000 --- a/e2e/suites/pagination/pag-trash.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; -import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; -import { Utils } from '../../utilities/utils'; -import { RepoClient, NodeContentTree } 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; - - function resetToDefaultPageSize(): promise.Promise { - return pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('25')) - .then(() => dataTable.waitForHeader()); - } - - function resetToDefaultPageNumber(): promise.Promise { - return pagination.openCurrentPageMenu() - .then(menu => menu.clickMenuItem('1')) - .then(() => dataTable.waitForHeader()); - } - - beforeAll(done => { - apis.admin.people.createUser(username) - .then(() => nodesApi.createFiles(filesForDelete)) - .then(resp => filesDeletedIds = resp.data.list.entries.map(entries => entries.entry.id)) - .then(() => nodesApi.deleteNodesById(filesDeletedIds, false)) - - .then(() => loginPage.load()) - .then(() => loginPage.loginWith(username)) - .then(done); - }); - - beforeEach(done => { - page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) - .then(() => dataTable.waitForHeader()) - .then(done); - }); - - afterEach(done => { - browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); - }); - - afterAll(done => { - Promise.all([ - trashApi.emptyTrash(), - logoutPage.load() - ]) - .then(done); - }); - - it('default values', () => { - 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', () => { - pagination.openMaxItemsMenu() - .then(menu => { - const [ first, second, third ] = [1, 2, 3] - .map(nth => menu.getNthItem(nth).getText()); - expect(first).toBe('25'); - expect(second).toBe('50'); - expect(third).toBe('100'); - }); - }); - - it('change the page size', () => { - pagination.openMaxItemsMenu() - .then(menu => menu.clickMenuItem('50')) - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.maxItems.getText()).toContain('50'); - expect(pagination.totalPages.getText()).toContain('of 3'); - }) - - .then(() => resetToDefaultPageSize()); - }); - - it('current page menu items', () => { - pagination.openCurrentPageMenu() - .then(menu => { - expect(menu.getItemsCount()).toBe(5); - }); - }); - - it('change the current page from menu', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-40.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to next page', () => { - pagination.nextButton.click() - .then(() => dataTable.waitForHeader()) - .then(() => { - expect(pagination.range.getText()).toContain('26-50 of 101'); - expect(dataTable.getRowByName('file-70.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('navigate to previous page', () => { - pagination.openCurrentPageMenu() - .then(menu => 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.getRowByName('file-88.txt').isPresent()) - .toBe(true, 'File not found on page'); - }) - - .then(() => resetToDefaultPageNumber()); - }); - - it('last page', () => { - pagination.openCurrentPageMenu() - .then(menu => menu.clickNthItem(5)) - .then(() => dataTable.waitForHeader()) - .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'); - }); - }); -}); diff --git a/e2e/utilities/repo-client/apis/favorites/favorites-api.ts b/e2e/utilities/repo-client/apis/favorites/favorites-api.ts deleted file mode 100644 index dd1cd7b47..000000000 --- a/e2e/utilities/repo-client/apis/favorites/favorites-api.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -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: string, id: string): Promise { - const data = [{ - target: { - [nodeType]: { - guid: id - } - } - }]; - return this.post(`/people/-me-/favorites`, { data }) - .catch(this.handleError); - } - - addFavoritesByIds(nodeType: string, ids: string[]): Promise { - return ids.reduce((previous, current) => ( - previous.then(() => this.addFavoriteById(nodeType, current)) - ), Promise.resolve()); - } - - getFavorite(api: RepoClient, name: string): Promise { - return api.nodes.getNodeByPath(name) - .then((response) => { - const { id } = response.data.entry; - return this.get(`/people/-me-/favorites/${id}`); - }) - .catch((response) => Promise.resolve(response)); - } - - 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); - } -} diff --git a/e2e/utilities/repo-client/apis/nodes/node-body-create.ts b/e2e/utilities/repo-client/apis/nodes/node-body-create.ts deleted file mode 100644 index 31c369d34..000000000 --- a/e2e/utilities/repo-client/apis/nodes/node-body-create.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 45516429b..000000000 --- a/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 410cebc92..000000000 --- a/e2e/utilities/repo-client/apis/nodes/nodes-api.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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): Promise { - return this.getNodeByPath(name) - .then(response => response.data.entry.properties['cm:description']) - .catch(() => Promise.resolve('')); - } - - 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); - } - - 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)); - } - - createNodeWithProperties(name: string, title?: string, description?: string, relativePath: string = '/'): Promise { - return this.createContent({ name, title, description }, 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); - } -} diff --git a/e2e/utilities/repo-client/apis/people/people-api-models.ts b/e2e/utilities/repo-client/apis/people/people-api-models.ts deleted file mode 100644 index d00dc7dd4..000000000 --- a/e2e/utilities/repo-client/apis/people/people-api-models.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index b13811080..000000000 --- a/e2e/utilities/repo-client/apis/people/people-api.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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); - } -} diff --git a/e2e/utilities/repo-client/apis/repo-api.ts b/e2e/utilities/repo-client/apis/repo-api.ts deleted file mode 100644 index 0a7993f9c..000000000 --- a/e2e/utilities/repo-client/apis/repo-api.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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): string { - const { defaults, config } = this; - const { host, tenant } = Object.assign(defaults, config); - - return `${host}/alfresco/api/${tenant}/public/alfresco/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 = {}) { - return this.client.get(this.createEndpointUri(endpoint), args); - } - - protected post(endpoint: string, args: RestClientArgs = {}) { - return this.client.post(this.createEndpointUri(endpoint), args); - } - - protected put(endpoint: string, args: RestClientArgs = {}) { - return this.client.put(this.createEndpointUri(endpoint), args); - } - - protected delete(endpoint: string, args: RestClientArgs = {}) { - return this.client.delete(this.createEndpointUri(endpoint), args); - } -} 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 deleted file mode 100644 index 537aef7a5..000000000 --- a/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -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()); - } - - getSharedLinks(): Promise { - return this.get(`/shared-links`) - .catch(this.handleError); - } - -} diff --git a/e2e/utilities/repo-client/apis/sites/sites-api-models.ts b/e2e/utilities/repo-client/apis/sites/sites-api-models.ts deleted file mode 100644 index f6729c1d7..000000000 --- a/e2e/utilities/repo-client/apis/sites/sites-api-models.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index acf25c04b..000000000 --- a/e2e/utilities/repo-client/apis/sites/sites-api.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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) { - return this - .get(`/sites/${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 deleted file mode 100644 index 935fc19b5..000000000 --- a/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -export class TrashcanApi extends RepoApi { - permanentlyDelete(id: string): Promise { - return this - .delete(`/deleted-nodes/${id}`) - .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); - } - -} diff --git a/e2e/utilities/repo-client/repo-client-models.ts b/e2e/utilities/repo-client/repo-client-models.ts deleted file mode 100644 index 46222947b..000000000 --- a/e2e/utilities/repo-client/repo-client-models.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 8ffe00b95..000000000 --- a/e2e/utilities/repo-client/repo-client.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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'; - -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); - - 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 deleted file mode 100644 index 8b82d83da..000000000 --- a/e2e/utilities/reporters/console/console-logger.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 7985e52c5..000000000 --- a/e2e/utilities/reporters/console/console.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 89e3c9916..000000000 --- a/e2e/utilities/rest-client/rest-client-models.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 45b4c1ca8..000000000 --- a/e2e/utilities/rest-client/rest-client.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 deleted file mode 100644 index 368b7aa43..000000000 --- a/e2e/utilities/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2017 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 } from 'protractor'; - -export class Utils { - // generate a random value - static random(): string { - return Math.random().toString(36).substring(3, 10); - } - - // local storage - static clearLocalStorage(): promise.Promise { - return browser.executeScript('window.localStorage.clear();'); - } - - // session storage - static clearSessionStorage(): promise.Promise { - return browser.executeScript('window.sessionStorage.clear();'); - } - -} diff --git a/package.json b/package.json index 334a7cba8..3e582ae2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alfresco-content-app", - "version": "0.1.0", + "version": "1.0.0", "license": "LGPL-3.0", "scripts": { "ng": "ng", @@ -58,7 +58,6 @@ "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", - "node-rest-client": "^3.1.0", "protractor": "^5.1.2", "rimraf": "2.6.2", "ts-node": "~3.2.0", diff --git a/protractor.conf.js b/protractor.conf.js index ba46ec01b..bc0ae870c 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -14,12 +14,6 @@ exports.config = { allScriptsTimeout: 30000, specs: [ - './e2e/suites/authentication/*.test.ts', - './e2e/suites/list-views/*.test.ts', - './e2e/suites/application/page-titles.test.ts', - './e2e/suites/navigation/side-navigation.test.ts', - './e2e/suites/pagination/*.test.ts', - './e2e/suites/actions/*.test.ts' ], capabilities: { diff --git a/src/app/components/favorites/favorites.component.html b/src/app/components/favorites/favorites.component.html index 323786641..7c1f3b135 100644 --- a/src/app/components/favorites/favorites.component.html +++ b/src/app/components/favorites/favorites.component.html @@ -9,7 +9,7 @@ mat-icon-button *ngIf="canPreviewFile(documentList.selection)" title="{{ 'APP.ACTIONS.VIEW' | translate }}" - (click)="showPreview(documentList.selection[0]?.entry?.id)"> + (click)="onNodeDoubleClick(documentList.selection[0]?.entry)"> open_in_browser diff --git a/src/app/components/files/files.component.html b/src/app/components/files/files.component.html index 6461f7859..f28e822a2 100644 --- a/src/app/components/files/files.component.html +++ b/src/app/components/files/files.component.html @@ -11,7 +11,7 @@ mat-icon-button *ngIf="canPreviewFile(documentList.selection)" title="{{ 'APP.ACTIONS.VIEW' | translate }}" - (click)="showPreview(documentList.selection[0]?.entry?.id)"> + (click)="showPreview(documentList.selection[0]?.entry)"> open_in_browser diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index bf656c69b..8d3d7e729 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -155,6 +155,14 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { } } + showPreview(node: MinimalNodeEntryEntity) { + if (node) { + if (node.isFile) { + this.router.navigate(['/preview', node.id]); + } + } + } + onBreadcrumbNavigate(route: PathElementEntity) { // todo: review this approach once 5.2.3 is out if (this.nodePath && this.nodePath.length > 2) { diff --git a/src/app/components/recent-files/recent-files.component.html b/src/app/components/recent-files/recent-files.component.html index daaf8a436..185a49732 100644 --- a/src/app/components/recent-files/recent-files.component.html +++ b/src/app/components/recent-files/recent-files.component.html @@ -9,7 +9,7 @@ mat-icon-button *ngIf="canPreviewFile(documentList.selection)" title="{{ 'APP.ACTIONS.VIEW' | translate }}" - (click)="showPreview(documentList.selection[0]?.entry?.id)"> + (click)="onNodeDoubleClick(documentList.selection[0]?.entry)"> open_in_browser diff --git a/src/app/components/shared-files/shared-files.component.html b/src/app/components/shared-files/shared-files.component.html index e7eb1481d..2cfc02f49 100644 --- a/src/app/components/shared-files/shared-files.component.html +++ b/src/app/components/shared-files/shared-files.component.html @@ -9,7 +9,7 @@ mat-icon-button *ngIf="canPreviewFile(documentList.selection)" title="{{ 'APP.ACTIONS.VIEW' | translate }}" - (click)="showPreview(documentList.selection[0]?.entry?.nodeId)"> + (click)="onNodeDoubleClick(documentList.selection[0]?.entry)"> open_in_browser diff --git a/yarn.lock b/yarn.lock index 58008d198..afc2765ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,26 +68,27 @@ zone.js "0.8.14" "@angular-devkit/build-optimizer@~0.0.31": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.33.tgz#d040f283ed7300b5be8bc970228b835ed02df42f" + version "0.0.36" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.36.tgz#e816ee9be22dbb777724f0281acfa72cfff184b7" dependencies: loader-utils "^1.1.0" source-map "^0.5.6" - typescript "^2.3.3" + typescript "~2.6.1" webpack-sources "^1.0.1" -"@angular-devkit/core@0.0.21": - version "0.0.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.21.tgz#e2ba4ac0a4e1156f884c083b15eb1c26ddfb2ba8" +"@angular-devkit/core@0.0.22": + version "0.0.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.22.tgz#e90f46bf7ff47d260a767959267bc65ffee39ef1" dependencies: source-map "^0.5.6" "@angular-devkit/schematics@~0.0.34": - version "0.0.37" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.37.tgz#d86ff93b14a44b766e72bcea1569b5d36063210e" + version "0.0.42" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.42.tgz#34eea7136455545c8abd21edf94a36870a073fea" dependencies: - "@angular-devkit/core" "0.0.21" + "@angular-devkit/core" "0.0.22" "@ngtools/json-schema" "^1.1.0" + "@schematics/schematics" "0.0.11" minimist "^1.2.0" rxjs "^5.5.2" @@ -216,8 +217,8 @@ resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-4.4.5.tgz#ccef139b8d3e1684b01afa35c6fbf2172e2bb676" "@angular/material-moment-adapter@^5.0.0-rc0": - version "5.0.0-rc0" - resolved "https://registry.yarnpkg.com/@angular/material-moment-adapter/-/material-moment-adapter-5.0.0-rc0.tgz#d0357aae1fef9e9181ee80ee444c2dfe9ce1df08" + version "5.0.1" + resolved "https://registry.yarnpkg.com/@angular/material-moment-adapter/-/material-moment-adapter-5.0.1.tgz#058371d4d60bb91555bc89e9044c9e6412bbda71" dependencies: tslib "^1.7.1" @@ -266,10 +267,14 @@ resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-8.0.0.tgz#751fd6b512d80f3a748d2de8dfc96dfefa29afe0" "@schematics/angular@~0.1.0": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.1.7.tgz#2306aeec118ca185e180882eff54f5116de4ef05" + version "0.1.11" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.1.11.tgz#b5f15320bbb60969d66c76a8ef6545058ac81ece" dependencies: - "@angular-devkit/core" "0.0.21" + "@angular-devkit/core" "0.0.22" + +"@schematics/schematics@0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.0.11.tgz#c8f70f270ed38f29b2873248126fd59abd635862" "@types/jasmine@*", "@types/jasmine@^2.5.53": version "2.8.2" @@ -282,8 +287,8 @@ "@types/jasmine" "*" "@types/node@^6.0.46", "@types/node@~6.0.60": - version "6.0.92" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.92.tgz#e7f721ae282772e12ba2579968c00d9cce422c5d" + version "6.0.94" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.94.tgz#70e509b07ed9f961c8f6f4a73a61d922be5029a7" "@types/q@^0.0.32": version "0.0.32" @@ -356,8 +361,8 @@ ajv@^4.9.1: json-stable-stringify "^1.0.1" ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5: - version "5.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9" + version "5.5.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2" dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" @@ -494,7 +499,7 @@ arraybuffer.slice@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" -arrify@^1.0.0: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -706,13 +711,13 @@ block-stream@*: dependencies: inherits "~2.0.0" -blocking-proxy@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blocking-proxy/-/blocking-proxy-0.0.5.tgz#462905e0dcfbea970f41aa37223dda9c07b1912b" +blocking-proxy@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/blocking-proxy/-/blocking-proxy-1.0.1.tgz#81d6fd1fe13a4c0d6957df7f91b75e98dac40cb2" dependencies: minimist "^1.2.0" -bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1: +bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -880,7 +885,7 @@ bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" -cacache@^10.0.0: +cacache@^10.0.0, cacache@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.1.tgz#3e05f6e616117d9b54665b1b20c8aeb93ea5d36f" dependencies: @@ -942,8 +947,8 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000775" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000775.tgz#04bccdd0214edf25b97f61a096609f7ad6904333" + version "1.0.30000783" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000783.tgz#16b30d47266a4f515cc69ae0316b670c9603cdbe" caseless@~0.11.0: version "0.11.0" @@ -1292,23 +1297,27 @@ copy-concurrently@^1.0.0: run-queue "^1.0.0" copy-webpack-plugin@^4.1.1: - version "4.2.3" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.2.3.tgz#4a3c61089f3b635777f0f0af346c338b39d63755" + version "4.3.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.3.0.tgz#cfdf4d131c78d66917a1bb863f86630497aacf42" dependencies: - bluebird "^3.5.1" - glob "^7.1.2" + cacache "^10.0.1" + find-cache-dir "^1.0.0" + globby "^7.1.1" is-glob "^4.0.0" loader-utils "^0.2.15" lodash "^4.3.0" minimatch "^3.0.4" + p-limit "^1.0.0" + pify "^3.0.0" + serialize-javascript "^1.4.0" core-js@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" core-js@^2.2.0, core-js@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" core-js@~2.3.0: version "2.3.0" @@ -1560,7 +1569,7 @@ debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: dependencies: ms "2.0.0" -debug@2.2.0, debug@~2.2.0: +debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -1681,6 +1690,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" + dependencies: + arrify "^1.0.1" + path-type "^3.0.0" + directory-encoder@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/directory-encoder/-/directory-encoder-0.7.2.tgz#59b4e2aa4f25422f6c63b527b462f5e2d0dd2c58" @@ -1783,8 +1799,8 @@ ejs@^2.5.7: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" electron-to-chromium@^1.2.7: - version "1.3.27" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" + version "1.3.28" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.28.tgz#8dd4e6458086644e9f9f0a1cf32e2a1f9dffd9ee" elliptic@^6.0.0: version "6.4.0" @@ -1873,10 +1889,10 @@ entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" errno@^0.1.1, errno@^0.1.3, errno@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" + version "0.1.6" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026" dependencies: - prr "~0.0.0" + prr "~1.0.1" error-ex@^1.2.0: version "1.3.1" @@ -2145,10 +2161,14 @@ extract-text-webpack-plugin@3.0.0: schema-utils "^0.3.0" webpack-sources "^1.0.1" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" @@ -2257,12 +2277,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" -follow-redirects@>=1.2.0: - version "1.2.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.6.tgz#4dcdc7e4ab3dd6765a97ff89c3b4c258117c79bf" - dependencies: - debug "^3.1.0" - for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -2352,8 +2366,8 @@ fs-extra@^0.26.5: rimraf "^2.2.8" fs-extra@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" dependencies: graceful-fs "^4.1.2" jsonfile "^4.0.0" @@ -2517,6 +2531,17 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + globule@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" @@ -2821,6 +2846,10 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" +ignore@^3.3.5: + version "3.3.7" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -3039,8 +3068,8 @@ is-path-in-cwd@^1.0.0: is-path-inside "^1.0.0" is-path-inside@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" dependencies: path-is-inside "^1.0.1" @@ -3238,8 +3267,8 @@ jasminewd2@^2.1.0: resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.2.0.tgz#e37cf0b17f199cce23bea71b2039395246b4ec4e" js-base64@^2.1.5, js-base64@^2.1.8, js-base64@^2.1.9: - version "2.3.2" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" + version "2.4.0" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa" js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" @@ -3367,8 +3396,8 @@ karma-jasmine-html-reporter@^0.2.2: karma-jasmine "^1.0.2" karma-jasmine@^1.0.2, karma-jasmine@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf" + version "1.1.1" + resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.1.tgz#6fe840e75a11600c9d91e84b33c458e1c46a3529" karma-source-map-support@^1.2.0: version "1.2.0" @@ -3718,8 +3747,8 @@ miller-rabin@^4.0.0: brorand "^1.0.1" "mime-db@>= 1.30.0 < 2": - version "1.31.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb" + version "1.32.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414" mime-db@~1.30.0: version "1.30.0" @@ -3804,8 +3833,8 @@ moment-es6@1.0.0: moment "*" moment@*, moment@^2.10.6: - version "2.19.3" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f" + version "2.19.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.4.tgz#17e5e2c6ead8819c8ecfad83a0acccb312e94682" moment@2.15.2: version "2.15.2" @@ -3949,14 +3978,6 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" -node-rest-client@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/node-rest-client/-/node-rest-client-3.1.0.tgz#e0beb6dda7b20cc0b67a7847cf12c5fc419c37c3" - dependencies: - debug "~2.2.0" - follow-redirects ">=1.2.0" - xml2js ">=0.2.4" - node-sass@^4.3.0: version "4.7.2" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" @@ -4168,7 +4189,7 @@ p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" -p-limit@^1.1.0: +p-limit@^1.0.0, p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" @@ -4299,6 +4320,12 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + pbkdf2@^3.0.3: version "3.0.14" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" @@ -4686,13 +4713,13 @@ promise@^7.1.1: asap "~2.0.3" protractor@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.2.0.tgz#d3f39b195e85f3539ad9d8cb6560a9d2b63297c4" + version "5.2.2" + resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.2.2.tgz#80eff170761455eff6e2f111088a03c438844a41" dependencies: "@types/node" "^6.0.46" "@types/q" "^0.0.32" "@types/selenium-webdriver" "~2.53.39" - blocking-proxy "0.0.5" + blocking-proxy "^1.0.0" chalk "^1.1.3" glob "^7.0.3" jasmine "^2.5.3" @@ -4712,9 +4739,9 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" -prr@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" pseudomap@^1.0.2: version "1.0.2" @@ -4953,8 +4980,8 @@ regenerate@^1.2.1: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" regenerator-runtime@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" regex-cache@^0.4.2: version "0.4.4" @@ -5152,12 +5179,18 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@5.5.2, rxjs@^5.5.2: +rxjs@5.5.2: version "5.5.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.2.tgz#28d403f0071121967f18ad665563255d54236ac3" dependencies: symbol-observable "^1.0.1" +rxjs@^5.5.2: + version "5.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.5.tgz#e164f11d38eaf29f56f08c3447f74ff02dd84e97" + dependencies: + symbol-observable "1.0.1" + safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -5281,6 +5314,10 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" +serialize-javascript@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" + serve-index@^1.7.2: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -5358,6 +5395,10 @@ silent-error@^1.0.0: dependencies: debug "^2.2.0" +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -5724,6 +5765,10 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + symbol-observable@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" @@ -5878,8 +5923,8 @@ tsickle@^0.24.0: source-map-support "^0.4.2" tslib@^1.7.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" + version "1.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" tslint@~5.7.0: version "5.7.0" @@ -5931,24 +5976,24 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.3.3: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" - typescript@~2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" +typescript@~2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + uglify-es@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.2.0.tgz#fbbfb9dc465ec7e5065701b9720d0de977d0bc24" + version "3.2.2" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.2.2.tgz#15c62b7775002c81b7987a1c49ecd3f126cace73" dependencies: commander "~2.12.1" source-map "~0.6.1" uglify-js@3.2.x: - version "3.2.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.0.tgz#cb411ee4ca0e0cadbfe3a4e1a1da97e6fa0d19c1" + version "3.2.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.2.tgz#870e4b34ed733d179284f9998efd3293f7fd73f6" dependencies: commander "~2.12.1" source-map "~0.6.1" @@ -6215,8 +6260,8 @@ webpack-dev-middleware@^1.11.0, webpack-dev-middleware@~1.12.0: time-stamp "^2.0.0" webpack-dev-server@~2.9.3: - version "2.9.5" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.5.tgz#79336fba0087a66ae491f4869f6545775b18daa8" + version "2.9.7" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.7.tgz#100ad6a14775478924d417ca6dcfb9d52a98faed" dependencies: ansi-html "0.0.7" array-includes "^3.0.3" @@ -6260,8 +6305,8 @@ webpack-sources@^1.0.0, webpack-sources@^1.0.1: source-map "~0.6.1" webpack-subresource-integrity@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.0.1.tgz#1fc09d46497da66e46743a2a51d2cc385b9cb0ed" + version "1.0.3" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.0.3.tgz#c0606d40090b070cde428bec8df3603216e472eb" dependencies: webpack-core "^0.6.8" @@ -6394,7 +6439,7 @@ xml2js@0.4.4: sax "0.6.x" xmlbuilder ">=1.0.0" -xml2js@>=0.2.4, xml2js@^0.4.17: +xml2js@^0.4.17: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" dependencies: From 34914a4c340486e1c86cdff3522336207d6fbee9 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 15 Dec 2017 09:28:45 +0000 Subject: [PATCH 03/15] Sync with development (#155) * fix navigation docs issues * update documentation (#142) * update documentation changed start command used a tags for links nested inside table and p tags, because Github did not render them correctly * set link to navigation on side-nav.md * [ACA-1061] update project version (#145) [ACA-1061] update project version * move e2e to a separate repo * fix "view" button (toolbar) * Update doc-list.md (#148) Correct minor typo. * Update doc-list.md (#151) Minor typo fix * Update doc-list.md (#152) Fixing more url typos. * [ACA] Layout - fix tests (#150) * mock sidenav data * removed fdescribe * [ACA] Breacrumb - fix opacity for active crumb (#153) * full opacity when on first crumb * indentation * [ACA-1062] Document list - ellipses colums (#154) --- docs/doc-list.md | 11 ++++---- .../layout/layout.component.spec.ts | 12 ++++++++- .../ui/overrides/_alfresco-document-list.scss | 27 ++++++++++++------- src/app/ui/overrides/_breadcrumb.scss | 4 +++ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/docs/doc-list.md b/docs/doc-list.md index 8fe776bc7..3764ec5e8 100644 --- a/docs/doc-list.md +++ b/docs/doc-list.md @@ -13,27 +13,26 @@ The application has six different Document List views which share commonalities #### Personal Files -Personal Files retrieves all content from the logged in user's home area (`/User Homes//` in the repository); +Personal Files retrieves all content from the logged in user's home area (`/User Homes//`) in the repository; if the user is ‘admin’ who does not have a home folder then the repository root folder is shown. -Personal Files is the [Files component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files), +Personal Files is the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes). #### File Libraries File Libraries retrieves all the sites that the user is a member of including what type of site it is: public, moderated or private. -File Libraries is the [Libraries component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/libraries), +File Libraries is the [Libraries](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/libraries) component, using the [Sites API](https://api-explorer.alfresco.com/api-explorer/#/sites). When a user opens one of their sites then the content for the site's document library is shown. -To display the files and folders from a site (`/Sites//Document Library/`) the [Files component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files), +To display the files and folders from a site (`/Sites//Document Library/`) the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes) is used. #### Shared Files The Shared Files view aggregates all files that have been shared using the QuickShare feature in the content repository. -The [Shared Files component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/shared-files) -uses the [shared-links API](https://api-explorer.alfresco.com/api-explorer/#/shared-links) +The [Shared Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/shared-files) component uses the [shared-links API](https://api-explorer.alfresco.com/api-explorer/#/shared-links) and includes extra columns to display where the file is [located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) in the content repository and who created the shared link. diff --git a/src/app/components/layout/layout.component.spec.ts b/src/app/components/layout/layout.component.spec.ts index f4fc2930c..5bd92c25e 100644 --- a/src/app/components/layout/layout.component.spec.ts +++ b/src/app/components/layout/layout.component.spec.ts @@ -26,7 +26,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { CoreModule, ContentService, PeopleContentService } from '@alfresco/adf-core'; +import { CoreModule, ContentService, PeopleContentService, AppConfigService } from '@alfresco/adf-core'; import { Observable } from 'rxjs/Observable'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; @@ -43,6 +43,12 @@ describe('LayoutComponent', () => { let browsingFilesService: BrowsingFilesService; let contentService: ContentService; let node; + const navItem = { + label: 'some-label', + route: { + url: '/some-url' + } + }; beforeEach(() => { node = { id: 'node-id' }; @@ -61,6 +67,7 @@ describe('LayoutComponent', () => { CurrentUserComponent ], providers: [ + AppConfigService, { provide: PeopleContentService, useValue: { @@ -75,6 +82,9 @@ describe('LayoutComponent', () => { browsingFilesService = TestBed.get(BrowsingFilesService); contentService = TestBed.get(ContentService); + const appConfig = TestBed.get(AppConfigService); + spyOn(appConfig, 'get').and.returnValue([navItem]); + fixture.detectChanges(); }); diff --git a/src/app/ui/overrides/_alfresco-document-list.scss b/src/app/ui/overrides/_alfresco-document-list.scss index a73705b0d..6f97fa96f 100644 --- a/src/app/ui/overrides/_alfresco-document-list.scss +++ b/src/app/ui/overrides/_alfresco-document-list.scss @@ -32,6 +32,11 @@ adf-document-list { } } + td, th { + width: 100%; + text-align: left; + } + .adf-data-table__header--sorted-asc, .adf-data-table__header--sorted-desc { &:hover { @@ -58,13 +63,10 @@ adf-document-list { border: none !important; } - - .adf-data-table-cell--ellipsis { - width: 100%; - } - - .adf-data-table-cell--ellipsis__name { - width: 85%; + td:first-of-type, th:first-of-type { + padding-left: 24px; + padding-right: 0; + width: 10px; } .adf-data-table-cell--ellipsis .cell-value, @@ -77,11 +79,18 @@ adf-document-list { .adf-data-table-cell--ellipsis__name .adf-datatable-cell { white-space: nowrap; display: block; - position: absolute; - max-width: calc(100% - 2em); overflow: hidden; text-overflow: ellipsis; } + + .adf-data-table-cell--ellipsis .adf-datatable-cell { + max-width: 7vw; + } + + .adf-data-table-cell--ellipsis__name .adf-datatable-cell { + position: absolute; + max-width: calc(100% - 2em); + } } .empty-list { diff --git a/src/app/ui/overrides/_breadcrumb.scss b/src/app/ui/overrides/_breadcrumb.scss index 035d66e23..010468714 100644 --- a/src/app/ui/overrides/_breadcrumb.scss +++ b/src/app/ui/overrides/_breadcrumb.scss @@ -3,4 +3,8 @@ .adf-breadcrumb { color: $alfresco-secondary-text-color; width: 0; + + &-item:first-child:nth-last-child(1) { + opacity: 1; + } } From 7a3c636274a605c0288623c5f4d365638cd2b898 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 15 Dec 2017 12:07:00 +0000 Subject: [PATCH 04/15] Sync with development (#159) * Update README.md (#156) * Update README.md (#158) Updating the readme with correct introduction. --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1e1bdc9b..b993cc96e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,20 @@ -# Alfresco Content App +# Alfresco Example Content Application + +## Introduction + +The Alfresco Content Application is an example application built using +[Alfresco Application Development Framework (ADF)](https://github.com/Alfresco/alfresco-ng2-components) components and was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.4.7. + +### Who is this example application for + +This example application demonstrates to Angular software engineers +how to construct a content application using the Alfresco ADF. + +This example application represents a meaningful composition of ADF components that provide end users +with a simple and easy to use interface for working with files stored in the Alfresco Content Services repository. [Public documentation](https://alfresco.github.io/alfresco-content-app/) -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.4.7. - ## Development server Run `npm start` for a dev server. Navigate to `http://localhost:3000/` (opens by default). From c0f226d9b004ca0cb27b3b931ba76c2c35bff53e Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 15 Dec 2017 14:22:36 +0000 Subject: [PATCH 05/15] add logo to readme --- README.md | 4 ++++ alfresco.png | Bin 0 -> 8205 bytes 2 files changed, 4 insertions(+) create mode 100644 alfresco.png diff --git a/README.md b/README.md index b993cc96e..769f9c32c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Alfresco Example Content Application +

+ Alfresco +

+ ## Introduction The Alfresco Content Application is an example application built using diff --git a/alfresco.png b/alfresco.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6326b0b376141cc757e4bc128590a6cf48508b GIT binary patch literal 8205 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 literal 0 HcmV?d00001 From 67cc20f2c68c4fe36639fd2fed39a48ef82d586c Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 15 Dec 2017 14:23:13 +0000 Subject: [PATCH 06/15] add logo to readme (#160) --- README.md | 4 ++++ alfresco.png | Bin 0 -> 8205 bytes 2 files changed, 4 insertions(+) create mode 100644 alfresco.png diff --git a/README.md b/README.md index b993cc96e..769f9c32c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Alfresco Example Content Application +

+ Alfresco +

+ ## Introduction The Alfresco Content Application is an example application built using diff --git a/alfresco.png b/alfresco.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6326b0b376141cc757e4bc128590a6cf48508b GIT binary patch literal 8205 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 literal 0 HcmV?d00001 From dfb676a29b9a8861970304e1cb580a1deee508eb Mon Sep 17 00:00:00 2001 From: Bindu Wavell Date: Fri, 15 Dec 2017 12:40:55 -0700 Subject: [PATCH 07/15] Don't try to port map to 80 as it is < 1024 (#161) In the docker example we were proposing that folks map port 80 from the docker container to port 80 on localhost. Unless the user is running as root, they likely won't be able to bind to any port < 1024. As such, I am proposing we bind to the local port 8888. Could change it to 3000 to be in-line with the `docker compose` example; but to me the use cases are different so I like having different ports... --- docs/docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index ac19fb4fe..fcddc4aca 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -35,10 +35,10 @@ docker image build -t content-app . To run the image locally, you can use the following command: ```sh -docker container run -p 80:80 --rm content-app +docker container run -p 8888:80 --rm content-app ``` -Navigate to "http://localhost" to access the running application. +Navigate to "http://localhost:8888" to access the running application. ## Docker Compose file From 92c147fae3fcc6d953524c392e3567979ba10a11 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Mon, 18 Dec 2017 13:33:18 +0000 Subject: [PATCH 08/15] Code coverage reports (#165) * travis settings update * travis update * enable coverage reports --- .travis.yml | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b68812ed..4eb7bfa76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,7 @@ install: script: - xvfb-run -a npm run test -- --single-run --no-progress --browser=ChromeNoSandbox - #- xvfb-run -a npm run e2e -- --no-progress --config=protractor-ci.conf.js + +# Send coverage data to codecov +after_success: + bash <(curl -s https://codecov.io/bash) -X gcov diff --git a/package.json b/package.json index 3e582ae2a..9f4fd5f20 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:prod": "npm run server-versions && ng build --prod", "build:dev": "npm run server-versions && ng build && node postbuild-dev.js", "build:tomcat": "npm run server-versions && ng build --base-href ./", - "test": "ng test", + "test": "ng test --code-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" From 9428913900b698a58932f260b81ee2d4cd82425b Mon Sep 17 00:00:00 2001 From: Cilibiu Bogdan Date: Mon, 18 Dec 2017 16:19:31 +0200 Subject: [PATCH 09/15] ensure parameter node id is a folder instance (#166) --- src/app/components/files/files.component.spec.ts | 13 ++++++++++++- src/app/components/files/files.component.ts | 8 +++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/app/components/files/files.component.spec.ts b/src/app/components/files/files.component.spec.ts index 266be3433..884ee1a83 100644 --- a/src/app/components/files/files.component.spec.ts +++ b/src/app/components/files/files.component.spec.ts @@ -77,7 +77,7 @@ describe('FilesComponent', () => { })); beforeEach(() => { - node = { id: 'node-id' }; + node = { id: 'node-id', isFolder: true }; page = { list: { entries: ['a', 'b', 'c'], @@ -134,6 +134,17 @@ describe('FilesComponent', () => { expect(component.onFetchError).toHaveBeenCalled(); }); + + it('if should navigate to parent if node is not a folder', () => { + node.isFolder = false; + node.parentId = 'parent-id'; + spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(router, 'navigate'); + + fixture.detectChanges(); + + expect(router.navigate).toHaveBeenCalledWith([ '/personal-files', 'parent-id' ]); + }); }); describe('refresh on events', () => { diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index 8d3d7e729..96926da40 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -76,7 +76,13 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { this.isLoading = true; this.fetchNode(nodeId) - .do((node) => this.updateCurrentNode(node)) + .do((node) => { + if (node.isFolder) { + this.updateCurrentNode(node); + } else { + this.router.navigate(['/personal-files', node.parentId]); + } + }) .flatMap((node) => this.fetchNodes(node.id)) .subscribe( (page) => { From 5dea44c342edd7a57e34ce30eb2b3d4363a8eb86 Mon Sep 17 00:00:00 2001 From: Cilibiu Bogdan Date: Mon, 18 Dec 2017 17:30:08 +0200 Subject: [PATCH 10/15] fixed name column when checkbox is present (#167) --- src/app/ui/overrides/_alfresco-document-list.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/ui/overrides/_alfresco-document-list.scss b/src/app/ui/overrides/_alfresco-document-list.scss index 6f97fa96f..c0acf6fe9 100644 --- a/src/app/ui/overrides/_alfresco-document-list.scss +++ b/src/app/ui/overrides/_alfresco-document-list.scss @@ -63,7 +63,8 @@ adf-document-list { border: none !important; } - td:first-of-type, th:first-of-type { + th:first-of-type, th.adf-data-table-cell--image, + td:first-of-type, td.adf-data-table-cell--image { padding-left: 24px; padding-right: 0; width: 10px; From cf7b781b6de89519f33c35dda9527fb1cf22b117 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Mon, 18 Dec 2017 17:17:48 +0000 Subject: [PATCH 11/15] Docker Hub integration (#168) --- .editorconfig | 3 +++ .travis.yml | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9b7352176..4a874ce23 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,6 @@ trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/.travis.yml b/.travis.yml index 4eb7bfa76..1570c46bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ dist: trusty -sudo: false +sudo: required language: node_js node_js: @@ -9,9 +9,10 @@ cache: directories: - ./node_modules +services: + - docker before_install: - - export CHROME_BIN=chromium-browser - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" @@ -19,8 +20,17 @@ install: - npm install script: - - xvfb-run -a npm run test -- --single-run --no-progress --browser=ChromeNoSandbox + - npm run build:dev + - npm run test -- --single-run --no-progress --browser=ChromeNoSandbox # Send coverage data to codecov after_success: - bash <(curl -s https://codecov.io/bash) -X gcov + - bash <(curl -s https://codecov.io/bash) -X gcov + - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) + - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" + - export TAG=`if [ "$BRANCH" == "master" ]; then echo "latest"; else echo $BRANCH ; fi` + - docker build -t $DOCKER_REPO:$TAG . + # Publish extra image based on Travis build number + - docker tag $DOCKER_REPO:$TAG $DOCKER_REPO:travis-$TRAVIS_BUILD_NUMBER + - docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" + - docker push $DOCKER_REPO From 60cba5b93b3cd367bee4a2ca3a27b38fa186677e Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Tue, 19 Dec 2017 09:51:57 +0000 Subject: [PATCH 12/15] update travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1570c46bf..23b773bb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ install: - npm install script: - - npm run build:dev + - npm run build - npm run test -- --single-run --no-progress --browser=ChromeNoSandbox # Send coverage data to codecov From 887d8b811c28710112595dc16be915f04d847ed1 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Thu, 21 Dec 2017 13:55:40 +0000 Subject: [PATCH 13/15] headless chrome for unit tests (#169) --- .travis.yml | 6 +----- karma.conf.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23b773bb8..f9a1b2411 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,16 +12,12 @@ cache: services: - docker -before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - install: - npm install script: - npm run build - - npm run test -- --single-run --no-progress --browser=ChromeNoSandbox + - npm run test -- --single-run --no-progress # Send coverage data to codecov after_success: diff --git a/karma.conf.js b/karma.conf.js index 410aaa975..abe47fde7 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -38,11 +38,16 @@ module.exports = function (config) { colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'], + browsers: ['ChromeHeadless'], customLaunchers: { - ChromeNoSandbox: { + ChromeHeadless: { base: 'Chrome', - flags: ['--no-sandbox'] + flags: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--remote-debugging-port=9222' + ] } }, singleRun: false, From d768219bdbdf91006bb5972e1412d3185a938407 Mon Sep 17 00:00:00 2001 From: Cilibiu Bogdan Date: Wed, 3 Jan 2018 11:38:31 +0200 Subject: [PATCH 14/15] preview files child route (#170) --- src/app/app.routes.ts | 84 ++++++++++++++----- .../favorites/favorites.component.spec.ts | 2 +- .../favorites/favorites.component.ts | 5 +- .../components/files/files.component.spec.ts | 5 +- src/app/components/files/files.component.ts | 7 +- .../components/preview/preview.component.html | 6 +- .../components/preview/preview.component.ts | 25 +++++- .../recent-files.component.spec.ts | 4 +- .../recent-files/recent-files.component.ts | 5 +- .../search/search.component.spec.ts | 5 +- src/app/components/search/search.component.ts | 2 +- .../shared-files.component.spec.ts | 2 +- .../shared-files/shared-files.component.ts | 5 +- 13 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 3a28d1cef..8c66ca552 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -41,13 +41,6 @@ import { PreviewComponent } from './components/preview/preview.component'; import { GenericErrorComponent } from './components/generic-error/generic-error.component'; export const APP_ROUTES: Routes = [ - { - path: 'preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } - }, { path: 'login', component: LoginComponent, @@ -66,10 +59,22 @@ export const APP_ROUTES: Routes = [ }, { path: 'favorites', - component: FavoritesComponent, - data: { - i18nTitle: 'APP.BROWSE.FAVORITES.TITLE' - } + children: [ + { + path: '', + component: FavoritesComponent, + data: { + i18nTitle: 'APP.BROWSE.FAVORITES.TITLE' + } + }, + { + path: 'preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } + } + ] }, { path: 'libraries', @@ -85,7 +90,15 @@ export const APP_ROUTES: Routes = [ data: { i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE' } - }] + }, + { + path: ':id/preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } + } + ] }, { path: 'personal-files', @@ -102,21 +115,52 @@ export const APP_ROUTES: Routes = [ data: { i18nTitle: 'APP.BROWSE.PERSONAL.TITLE' } + }, + { + path: ':id/preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } }] }, { path: 'recent-files', - component: RecentFilesComponent, - data: { - i18nTitle: 'APP.BROWSE.RECENT.TITLE' - } + children: [ + { + path: '', + component: RecentFilesComponent, + data: { + i18nTitle: 'APP.BROWSE.RECENT.TITLE' + } + }, + { + path: 'preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } + } + ] }, { path: 'shared', - component: SharedFilesComponent, - data: { - i18nTitle: 'APP.BROWSE.SHARED.TITLE' - } + children: [ + { + path: '', + component: SharedFilesComponent, + data: { + i18nTitle: 'APP.BROWSE.SHARED.TITLE' + } + }, + { + path: 'preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } + } + ] }, { path: 'trashcan', diff --git a/src/app/components/favorites/favorites.component.spec.ts b/src/app/components/favorites/favorites.component.spec.ts index c121bd7ea..8a0c2a189 100644 --- a/src/app/components/favorites/favorites.component.spec.ts +++ b/src/app/components/favorites/favorites.component.spec.ts @@ -174,7 +174,7 @@ describe('Favorites Routed Component', () => { component.onNodeDoubleClick(node); - expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); + expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', 'folder-node']); }); }); diff --git a/src/app/components/favorites/favorites.component.ts b/src/app/components/favorites/favorites.component.ts index 99b858404..2626f5748 100644 --- a/src/app/components/favorites/favorites.component.ts +++ b/src/app/components/favorites/favorites.component.ts @@ -24,7 +24,7 @@ */ import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; import { MinimalNodeEntryEntity, MinimalNodeEntity, PathElementEntity, PathInfo } from 'alfresco-js-api'; @@ -46,6 +46,7 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr constructor( private router: Router, + private route: ActivatedRoute, private nodesApi: NodesApiService, private contentService: ContentService, private content: ContentManagementService, @@ -95,7 +96,7 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr } if (node.isFile) { - this.router.navigate(['/preview', node.id]); + this.router.navigate(['./preview', node.id], { relativeTo: this.route }); } } } diff --git a/src/app/components/files/files.component.spec.ts b/src/app/components/files/files.component.spec.ts index 884ee1a83..70c7e4f75 100644 --- a/src/app/components/files/files.component.spec.ts +++ b/src/app/components/files/files.component.spec.ts @@ -143,7 +143,7 @@ describe('FilesComponent', () => { fixture.detectChanges(); - expect(router.navigate).toHaveBeenCalledWith([ '/personal-files', 'parent-id' ]); + expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['/personal-files', 'parent-id']); }); }); @@ -315,6 +315,7 @@ describe('FilesComponent', () => { it('opens preview if node is file', () => { spyOn(router, 'navigate').and.stub(); node.isFile = true; + node.isFolder = false; const event: any = { detail: { @@ -325,7 +326,7 @@ describe('FilesComponent', () => { }; component.onNodeDoubleClick(event); - expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); + expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); }); it('navigate if node is folder', () => { diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index 96926da40..7854a44e7 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -80,9 +80,10 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { if (node.isFolder) { this.updateCurrentNode(node); } else { - this.router.navigate(['/personal-files', node.parentId]); + this.router.navigate(['/personal-files', node.parentId], { replaceUrl: true }); } }) + .skipWhile(node => !node.isFolder) .flatMap((node) => this.fetchNodes(node.id)) .subscribe( (page) => { @@ -154,7 +155,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { event.preventDefault(); } else if (node.isFile) { - this.router.navigate(['/preview', node.id]); + this.router.navigate(['./preview', node.id], { relativeTo: this.route }); } } @@ -164,7 +165,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { showPreview(node: MinimalNodeEntryEntity) { if (node) { if (node.isFile) { - this.router.navigate(['/preview', node.id]); + this.router.navigate(['./preview', node.id], { relativeTo: this.route }); } } } diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 2592514c9..536b6ebdc 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -1,3 +1,7 @@ - + + diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 99d92bfe9..6c42b3416 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -26,6 +26,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AlfrescoApiService } from '@alfresco/adf-core'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; @Component({ selector: 'app-preview', @@ -37,12 +38,17 @@ import { AlfrescoApiService } from '@alfresco/adf-core'; }) export class PreviewComponent implements OnInit { + private node: MinimalNodeEntryEntity; + private previewLocation: string = null; + private routesSkipNavigation = [ '/shared', '/recent-files', '/favorites' ]; nodeId: string = null; constructor( private router: Router, private route: ActivatedRoute, - private apiService: AlfrescoApiService) {} + private apiService: AlfrescoApiService) { + this.previewLocation = this.router.url.substr(0, this.router.url.indexOf('/', 1)); + } ngOnInit() { this.route.params.subscribe(params => { @@ -50,16 +56,29 @@ export class PreviewComponent implements OnInit { if (id) { this.apiService.getInstance().nodes.getNodeInfo(id).then( (node) => { + this.node = node; + if (node && node.isFile) { this.nodeId = id; return; } - this.router.navigate(['/personal-files', id]); + this.router.navigate([this.previewLocation, id]); }, - () => this.router.navigate(['/personal-files', id]) + () => this.router.navigate([this.previewLocation, id]) ); } }); } + onShowChange(isVisible) { + const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation); + + if (!isVisible) { + if ( !shouldSkipNavigation ) { + this.router.navigate([this.previewLocation, this.node.parentId ]); + } else { + this.router.navigate([this.previewLocation]); + } + } + } } diff --git a/src/app/components/recent-files/recent-files.component.spec.ts b/src/app/components/recent-files/recent-files.component.spec.ts index 890c5a6f9..7291b60b8 100644 --- a/src/app/components/recent-files/recent-files.component.spec.ts +++ b/src/app/components/recent-files/recent-files.component.spec.ts @@ -122,12 +122,12 @@ describe('RecentFiles Routed Component', () => { it('open preview if node is file', () => { spyOn(router, 'navigate').and.stub(); - const node: any = { isFile: true }; + const node: any = { id: 'node-id', isFile: true }; component.onNodeDoubleClick(node); fixture.detectChanges(); - expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); + expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); }); it('does not open preview if node is folder', () => { diff --git a/src/app/components/recent-files/recent-files.component.ts b/src/app/components/recent-files/recent-files.component.ts index 1e2617b36..c49048758 100644 --- a/src/app/components/recent-files/recent-files.component.ts +++ b/src/app/components/recent-files/recent-files.component.ts @@ -25,7 +25,7 @@ import { Subscription } from 'rxjs/Rx'; import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { UserPreferencesService } from '@alfresco/adf-core'; import { DocumentListComponent } from '@alfresco/adf-content-services'; @@ -45,6 +45,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes constructor( private router: Router, + private route: ActivatedRoute, private content: ContentManagementService, preferences: UserPreferencesService) { super(preferences); @@ -67,7 +68,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes event.preventDefault(); } else if (node && node.isFile) { - this.router.navigate(['/preview', node.id]); + this.router.navigate(['./preview', node.id], { relativeTo: this.route }); } } diff --git a/src/app/components/search/search.component.spec.ts b/src/app/components/search/search.component.spec.ts index 946c4d268..910e25cb0 100644 --- a/src/app/components/search/search.component.spec.ts +++ b/src/app/components/search/search.component.spec.ts @@ -60,11 +60,12 @@ describe('SearchComponent', () => { describe('onItemClicked()', () => { it('opens preview if node is file', () => { spyOn(router, 'navigate').and.stub(); - const node = { entry: { isFile: true, id: 'node-id' } }; + const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; component.onItemClicked(node); - expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]); + expect(router.navigate['calls'].argsFor(0)[0]) + .toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); }); it('navigates if node is folder', () => { diff --git a/src/app/components/search/search.component.ts b/src/app/components/search/search.component.ts index e4f14405e..10f1f5fec 100644 --- a/src/app/components/search/search.component.ts +++ b/src/app/components/search/search.component.ts @@ -41,7 +41,7 @@ export class SearchComponent { onItemClicked(node: MinimalNodeEntity) { if (node && node.entry) { if (node.entry.isFile) { - this.router.navigate(['/preview', node.entry.id]); + this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); } else if (node.entry.isFolder) { this.router.navigate([ '/personal-files', node.entry.id ]); } diff --git a/src/app/components/shared-files/shared-files.component.spec.ts b/src/app/components/shared-files/shared-files.component.spec.ts index 86df66efd..064b4a9cd 100644 --- a/src/app/components/shared-files/shared-files.component.spec.ts +++ b/src/app/components/shared-files/shared-files.component.spec.ts @@ -126,7 +126,7 @@ describe('SharedFilesComponent', () => { component.onNodeDoubleClick(link); tick(); - expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]); + expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.entry.id]); })); it('does nothing if node is folder', fakeAsync(() => { diff --git a/src/app/components/shared-files/shared-files.component.ts b/src/app/components/shared-files/shared-files.component.ts index 70265c61a..5c4c2bc4d 100644 --- a/src/app/components/shared-files/shared-files.component.ts +++ b/src/app/components/shared-files/shared-files.component.ts @@ -24,7 +24,7 @@ */ import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; @@ -45,6 +45,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes constructor( private router: Router, + private route: ActivatedRoute, private content: ContentManagementService, private apiService: AlfrescoApiService, preferences: UserPreferencesService) { @@ -68,7 +69,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes this.apiService.nodesApi.getNode(link.nodeId).then( (node: MinimalNodeEntity) => { if (node && node.entry && node.entry.isFile) { - this.router.navigate(['/preview', node.entry.id]); + this.router.navigate(['./preview', node.entry.id], { relativeTo: this.route }); } } ); From b6018facac5e066db42a914be299cb5e33150d4c Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Wed, 3 Jan 2018 10:05:54 +0000 Subject: [PATCH 15/15] Revert "preview files child route (#170)" (#171) This reverts commit d768219bdbdf91006bb5972e1412d3185a938407. --- src/app/app.routes.ts | 84 +++++-------------- .../favorites/favorites.component.spec.ts | 2 +- .../favorites/favorites.component.ts | 5 +- .../components/files/files.component.spec.ts | 5 +- src/app/components/files/files.component.ts | 7 +- .../components/preview/preview.component.html | 6 +- .../components/preview/preview.component.ts | 25 +----- .../recent-files.component.spec.ts | 4 +- .../recent-files/recent-files.component.ts | 5 +- .../search/search.component.spec.ts | 5 +- src/app/components/search/search.component.ts | 2 +- .../shared-files.component.spec.ts | 2 +- .../shared-files/shared-files.component.ts | 5 +- 13 files changed, 42 insertions(+), 115 deletions(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 8c66ca552..3a28d1cef 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -41,6 +41,13 @@ import { PreviewComponent } from './components/preview/preview.component'; import { GenericErrorComponent } from './components/generic-error/generic-error.component'; export const APP_ROUTES: Routes = [ + { + path: 'preview/:nodeId', + component: PreviewComponent, + data: { + i18nTitle: 'APP.PREVIEW.TITLE' + } + }, { path: 'login', component: LoginComponent, @@ -59,22 +66,10 @@ export const APP_ROUTES: Routes = [ }, { path: 'favorites', - children: [ - { - path: '', - component: FavoritesComponent, - data: { - i18nTitle: 'APP.BROWSE.FAVORITES.TITLE' - } - }, - { - path: 'preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } - } - ] + component: FavoritesComponent, + data: { + i18nTitle: 'APP.BROWSE.FAVORITES.TITLE' + } }, { path: 'libraries', @@ -90,15 +85,7 @@ export const APP_ROUTES: Routes = [ data: { i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE' } - }, - { - path: ':id/preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } - } - ] + }] }, { path: 'personal-files', @@ -115,52 +102,21 @@ export const APP_ROUTES: Routes = [ data: { i18nTitle: 'APP.BROWSE.PERSONAL.TITLE' } - }, - { - path: ':id/preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } }] }, { path: 'recent-files', - children: [ - { - path: '', - component: RecentFilesComponent, - data: { - i18nTitle: 'APP.BROWSE.RECENT.TITLE' - } - }, - { - path: 'preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } - } - ] + component: RecentFilesComponent, + data: { + i18nTitle: 'APP.BROWSE.RECENT.TITLE' + } }, { path: 'shared', - children: [ - { - path: '', - component: SharedFilesComponent, - data: { - i18nTitle: 'APP.BROWSE.SHARED.TITLE' - } - }, - { - path: 'preview/:nodeId', - component: PreviewComponent, - data: { - i18nTitle: 'APP.PREVIEW.TITLE' - } - } - ] + component: SharedFilesComponent, + data: { + i18nTitle: 'APP.BROWSE.SHARED.TITLE' + } }, { path: 'trashcan', diff --git a/src/app/components/favorites/favorites.component.spec.ts b/src/app/components/favorites/favorites.component.spec.ts index 8a0c2a189..c121bd7ea 100644 --- a/src/app/components/favorites/favorites.component.spec.ts +++ b/src/app/components/favorites/favorites.component.spec.ts @@ -174,7 +174,7 @@ describe('Favorites Routed Component', () => { component.onNodeDoubleClick(node); - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', 'folder-node']); + expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); }); }); diff --git a/src/app/components/favorites/favorites.component.ts b/src/app/components/favorites/favorites.component.ts index 2626f5748..99b858404 100644 --- a/src/app/components/favorites/favorites.component.ts +++ b/src/app/components/favorites/favorites.component.ts @@ -24,7 +24,7 @@ */ import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; +import { Router } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; import { MinimalNodeEntryEntity, MinimalNodeEntity, PathElementEntity, PathInfo } from 'alfresco-js-api'; @@ -46,7 +46,6 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr constructor( private router: Router, - private route: ActivatedRoute, private nodesApi: NodesApiService, private contentService: ContentService, private content: ContentManagementService, @@ -96,7 +95,7 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr } if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + this.router.navigate(['/preview', node.id]); } } } diff --git a/src/app/components/files/files.component.spec.ts b/src/app/components/files/files.component.spec.ts index 70c7e4f75..884ee1a83 100644 --- a/src/app/components/files/files.component.spec.ts +++ b/src/app/components/files/files.component.spec.ts @@ -143,7 +143,7 @@ describe('FilesComponent', () => { fixture.detectChanges(); - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['/personal-files', 'parent-id']); + expect(router.navigate).toHaveBeenCalledWith([ '/personal-files', 'parent-id' ]); }); }); @@ -315,7 +315,6 @@ describe('FilesComponent', () => { it('opens preview if node is file', () => { spyOn(router, 'navigate').and.stub(); node.isFile = true; - node.isFolder = false; const event: any = { detail: { @@ -326,7 +325,7 @@ describe('FilesComponent', () => { }; component.onNodeDoubleClick(event); - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); + expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); }); it('navigate if node is folder', () => { diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index 7854a44e7..96926da40 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -80,10 +80,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { if (node.isFolder) { this.updateCurrentNode(node); } else { - this.router.navigate(['/personal-files', node.parentId], { replaceUrl: true }); + this.router.navigate(['/personal-files', node.parentId]); } }) - .skipWhile(node => !node.isFolder) .flatMap((node) => this.fetchNodes(node.id)) .subscribe( (page) => { @@ -155,7 +154,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { event.preventDefault(); } else if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + this.router.navigate(['/preview', node.id]); } } @@ -165,7 +164,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { showPreview(node: MinimalNodeEntryEntity) { if (node) { if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + this.router.navigate(['/preview', node.id]); } } } diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 536b6ebdc..2592514c9 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -1,7 +1,3 @@ - - + diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 6c42b3416..99d92bfe9 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -26,7 +26,6 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { MinimalNodeEntryEntity } from 'alfresco-js-api'; @Component({ selector: 'app-preview', @@ -38,17 +37,12 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api'; }) export class PreviewComponent implements OnInit { - private node: MinimalNodeEntryEntity; - private previewLocation: string = null; - private routesSkipNavigation = [ '/shared', '/recent-files', '/favorites' ]; nodeId: string = null; constructor( private router: Router, private route: ActivatedRoute, - private apiService: AlfrescoApiService) { - this.previewLocation = this.router.url.substr(0, this.router.url.indexOf('/', 1)); - } + private apiService: AlfrescoApiService) {} ngOnInit() { this.route.params.subscribe(params => { @@ -56,29 +50,16 @@ export class PreviewComponent implements OnInit { if (id) { this.apiService.getInstance().nodes.getNodeInfo(id).then( (node) => { - this.node = node; - if (node && node.isFile) { this.nodeId = id; return; } - this.router.navigate([this.previewLocation, id]); + this.router.navigate(['/personal-files', id]); }, - () => this.router.navigate([this.previewLocation, id]) + () => this.router.navigate(['/personal-files', id]) ); } }); } - onShowChange(isVisible) { - const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation); - - if (!isVisible) { - if ( !shouldSkipNavigation ) { - this.router.navigate([this.previewLocation, this.node.parentId ]); - } else { - this.router.navigate([this.previewLocation]); - } - } - } } diff --git a/src/app/components/recent-files/recent-files.component.spec.ts b/src/app/components/recent-files/recent-files.component.spec.ts index 7291b60b8..890c5a6f9 100644 --- a/src/app/components/recent-files/recent-files.component.spec.ts +++ b/src/app/components/recent-files/recent-files.component.spec.ts @@ -122,12 +122,12 @@ describe('RecentFiles Routed Component', () => { it('open preview if node is file', () => { spyOn(router, 'navigate').and.stub(); - const node: any = { id: 'node-id', isFile: true }; + const node: any = { isFile: true }; component.onNodeDoubleClick(node); fixture.detectChanges(); - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); + expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]); }); it('does not open preview if node is folder', () => { diff --git a/src/app/components/recent-files/recent-files.component.ts b/src/app/components/recent-files/recent-files.component.ts index c49048758..1e2617b36 100644 --- a/src/app/components/recent-files/recent-files.component.ts +++ b/src/app/components/recent-files/recent-files.component.ts @@ -25,7 +25,7 @@ import { Subscription } from 'rxjs/Rx'; import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; +import { Router } from '@angular/router'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { UserPreferencesService } from '@alfresco/adf-core'; import { DocumentListComponent } from '@alfresco/adf-content-services'; @@ -45,7 +45,6 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes constructor( private router: Router, - private route: ActivatedRoute, private content: ContentManagementService, preferences: UserPreferencesService) { super(preferences); @@ -68,7 +67,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes event.preventDefault(); } else if (node && node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + this.router.navigate(['/preview', node.id]); } } diff --git a/src/app/components/search/search.component.spec.ts b/src/app/components/search/search.component.spec.ts index 910e25cb0..946c4d268 100644 --- a/src/app/components/search/search.component.spec.ts +++ b/src/app/components/search/search.component.spec.ts @@ -60,12 +60,11 @@ describe('SearchComponent', () => { describe('onItemClicked()', () => { it('opens preview if node is file', () => { spyOn(router, 'navigate').and.stub(); - const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; + const node = { entry: { isFile: true, id: 'node-id' } }; component.onItemClicked(node); - expect(router.navigate['calls'].argsFor(0)[0]) - .toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); + expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]); }); it('navigates if node is folder', () => { diff --git a/src/app/components/search/search.component.ts b/src/app/components/search/search.component.ts index 10f1f5fec..e4f14405e 100644 --- a/src/app/components/search/search.component.ts +++ b/src/app/components/search/search.component.ts @@ -41,7 +41,7 @@ export class SearchComponent { onItemClicked(node: MinimalNodeEntity) { if (node && node.entry) { if (node.entry.isFile) { - this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); + this.router.navigate(['/preview', node.entry.id]); } else if (node.entry.isFolder) { this.router.navigate([ '/personal-files', node.entry.id ]); } diff --git a/src/app/components/shared-files/shared-files.component.spec.ts b/src/app/components/shared-files/shared-files.component.spec.ts index 064b4a9cd..86df66efd 100644 --- a/src/app/components/shared-files/shared-files.component.spec.ts +++ b/src/app/components/shared-files/shared-files.component.spec.ts @@ -126,7 +126,7 @@ describe('SharedFilesComponent', () => { component.onNodeDoubleClick(link); tick(); - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.entry.id]); + expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]); })); it('does nothing if node is folder', fakeAsync(() => { diff --git a/src/app/components/shared-files/shared-files.component.ts b/src/app/components/shared-files/shared-files.component.ts index 5c4c2bc4d..70265c61a 100644 --- a/src/app/components/shared-files/shared-files.component.ts +++ b/src/app/components/shared-files/shared-files.component.ts @@ -24,7 +24,7 @@ */ import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; +import { Router } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; @@ -45,7 +45,6 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes constructor( private router: Router, - private route: ActivatedRoute, private content: ContentManagementService, private apiService: AlfrescoApiService, preferences: UserPreferencesService) { @@ -69,7 +68,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes this.apiService.nodesApi.getNode(link.nodeId).then( (node: MinimalNodeEntity) => { if (node && node.entry && node.entry.isFile) { - this.router.navigate(['./preview', node.entry.id], { relativeTo: this.route }); + this.router.navigate(['/preview', node.entry.id]); } } );