From 2733c69c9a8b0ae37c463c28baf35a66f1a01ae8 Mon Sep 17 00:00:00 2001 From: Adina Parpalita Date: Thu, 23 Jan 2020 17:47:25 +0200 Subject: [PATCH] [ACA-1921] automate tests for Create folder from template (#1320) * automate tests for Create folder from template added unit test for exclusion of folder links * fix stupid mistake --- e2e/components/menu/menu.ts | 46 ++- e2e/components/sidenav/sidenav.ts | 18 +- .../actions/create-file-from-template.test.ts | 37 +- .../create-folder-from-template.test.ts | 387 ++++++++++++++++++ e2e/suites/actions/new-menu.test.ts | 3 + e2e/suites/actions/upload-file.test.ts | 2 + e2e/utilities/admin-actions.ts | 52 ++- .../repo-client/apis/nodes/nodes-api.ts | 110 +++-- protractor.conf.js | 1 + .../services/node-template.service.spec.ts | 26 +- 10 files changed, 597 insertions(+), 85 deletions(-) create mode 100755 e2e/suites/actions/create-folder-from-template.test.ts diff --git a/e2e/components/menu/menu.ts b/e2e/components/menu/menu.ts index 6d0673c45..e6b44d3a6 100755 --- a/e2e/components/menu/menu.ts +++ b/e2e/components/menu/menu.ts @@ -33,7 +33,15 @@ export class Menu extends Component { root: '.mat-menu-panel', item: '.mat-menu-item', icon: '.mat-icon', - uploadFiles: 'app-upload-files', + + uploadFilesInput: 'app-upload-files', + + uploadFile: 'app.create.uploadFile', + uploadFolder: 'app.create.uploadFolder', + createFolder: 'app.create.folder', + createLibrary: 'app.create.library', + createFileFromTemplate: 'app.create.fileFromTemplate', + createFolderFromTemplate: 'app.create.folderFromTemplate', submenu: 'app-context-menu-item .mat-menu-item', @@ -46,14 +54,20 @@ export class Menu extends Component { items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item)); backdrop: ElementFinder = browser.element(by.css('.cdk-overlay-backdrop')); - uploadFiles: ElementFinder = browser.element(by.id(Menu.selectors.uploadFiles)); + + uploadFilesInput: ElementFinder = browser.element(by.id(Menu.selectors.uploadFilesInput)); submenus: ElementArrayFinder = browser.element.all(by.css(Menu.selectors.submenu)); + uploadFileAction: ElementFinder = this.component.element(by.id(Menu.selectors.uploadFile)); + uploadFolderAction: ElementFinder = this.component.element(by.id(Menu.selectors.uploadFolder)); + createFolderAction: ElementFinder = this.component.element(by.id(Menu.selectors.createFolder)); + createLibraryAction: ElementFinder = this.component.element(by.id(Menu.selectors.createLibrary)); + createFileFromTemplateAction: ElementFinder = this.component.element(by.id(Menu.selectors.createFileFromTemplate)); + createFolderFromTemplateAction: ElementFinder = this.component.element(by.id(Menu.selectors.createFolderFromTemplate)); + cancelEditingAction: ElementFinder = this.component.element(by.css(Menu.selectors.cancelEditing)); cancelJoinAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Cancel Join')); copyAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Copy')); - createFolderAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Create Folder')); - createLibraryAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Create Library')); deleteAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Delete')); downloadAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Download')); editFolderAction: ElementFinder = this.component.element(by.css(Menu.selectors.editFolder)); @@ -72,9 +86,6 @@ export class Menu extends Component { restoreAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Restore')); shareAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Share')); shareEditAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Shared Link Settings')); - uploadFileAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Upload File')); - uploadFolderAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Upload Folder')); - createFileFromTemplateAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'Create file from template')); viewAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'View')); viewDetailsAction: ElementFinder = this.component.element(by.cssContainingText(Menu.selectors.item, 'View Details')); @@ -235,7 +246,7 @@ export class Menu extends Component { } uploadFile(): ElementFinder { - return this.uploadFiles; + return this.uploadFilesInput; } async clickEditFolder(): Promise { @@ -364,6 +375,10 @@ export class Menu extends Component { return (await this.createFileFromTemplateAction.isPresent()) && (await this.createFileFromTemplateAction.isEnabled()); } + async isCreateFolderFromTemplateEnabled(): Promise { + return (await this.createFolderFromTemplateAction.isPresent()) && (await this.createFolderFromTemplateAction.isEnabled()); + } + async clickCreateFolder(): Promise { const action = this.createFolderAction; await action.click(); @@ -374,18 +389,13 @@ export class Menu extends Component { await action.click(); } - async clickUploadFile(): Promise { - const action = this.uploadFileAction; - await action.click(); - } - - async clickUploadFolder(): Promise { - const action = this.uploadFolderAction; - await action.click(); - } - async clickCreateFileFromTemplate(): Promise { const action = this.createFileFromTemplateAction; await action.click(); } + + async clickCreateFolderFromTemplate(): Promise { + const action = this.createFolderFromTemplateAction; + await action.click(); + } } diff --git a/e2e/components/sidenav/sidenav.ts b/e2e/components/sidenav/sidenav.ts index f49553c1f..24b231f2f 100755 --- a/e2e/components/sidenav/sidenav.ts +++ b/e2e/components/sidenav/sidenav.ts @@ -55,6 +55,7 @@ export class Sidenav extends Component { links: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.link)); activeLink: ElementFinder = this.component.element(by.css(Sidenav.selectors.activeClass)); + newButton: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.newButton)); personalFiles: ElementFinder = this.component.element(by.css(Sidenav.selectors.personalFiles)); @@ -90,25 +91,28 @@ export class Sidenav extends Component { } async openNewMenu(): Promise { - const { menu, newButton } = this; - - await newButton.click(); - await menu.waitForMenuToOpen(); + await this.newButton.click(); + await this.menu.waitForMenuToOpen(); } async openCreateFolderDialog(): Promise { await this.openNewMenu(); - await this.menu.clickMenuItem('Create Folder'); + await this.menu.clickCreateFolder(); } async openCreateLibraryDialog(): Promise { await this.openNewMenu(); - await this.menu.clickMenuItem('Create Library'); + await this.menu.clickCreateLibrary(); } async openCreateFileFromTemplateDialog(): Promise { await this.openNewMenu(); - await this.menu.clickMenuItem('Create file from template'); + await this.menu.clickCreateFileFromTemplate(); + } + + async openCreateFolderFromTemplateDialog(): Promise { + await this.openNewMenu(); + await this.menu.clickCreateFolderFromTemplate(); } async isActive(name: string): Promise { diff --git a/e2e/suites/actions/create-file-from-template.test.ts b/e2e/suites/actions/create-file-from-template.test.ts index a22409792..21f40ef69 100755 --- a/e2e/suites/actions/create-file-from-template.test.ts +++ b/e2e/suites/actions/create-file-from-template.test.ts @@ -67,7 +67,7 @@ describe('Create file from template', () => { title: `file site title`, description: `file site description` }; - const duplicateFileSite = `duplicate-file-site-${random}`; + const duplicateFileSite = `duplicate-file-site-${random}.txt`; let docLibUserSite: string; const userApi = new RepoClient(username, username); @@ -80,7 +80,7 @@ describe('Create file from template', () => { const createFromTemplateDialog = new CreateFromTemplateDialog(); const { sidenav } = page; - beforeAll( async (done) => { + beforeAll(async () => { await adminApiActions.createUser({ username }); parentId = (await userApi.nodes.createFolder(parent)).entry.id; @@ -88,17 +88,15 @@ describe('Create file from template', () => { await userApi.sites.createSite(siteName); docLibUserSite = await userApi.sites.getDocLibId(siteName); - await userApi.nodes.createFolder(duplicateFileSite, docLibUserSite); + await userApi.nodes.createFile(duplicateFileSite, docLibUserSite); await loginPage.loginWith(username); - done(); }); - afterAll(async (done) => { + afterAll(async () => { await userApi.nodes.deleteNodeById(parentId); await userApi.sites.deleteSite(siteName); - await adminApiActions.cleanNodeTemplatesFolder(); - done(); + await adminApiActions.cleanupNodeTemplatesFolder(); }); beforeEach(async () => { @@ -142,18 +140,16 @@ describe('Create file from template', () => { }; let link: string; - beforeAll(async (done) => { + beforeAll(async () => { await adminApiActions.createNodeTemplatesHierarchy(templates); - await adminApiActions.removeUserAccessOnNode(restrictedTemplateFolder); + await adminApiActions.removeUserAccessOnNodeTemplate(restrictedTemplateFolder); link = (await adminApiActions.createLinkToFileName(template2InRootFolder, await adminApiActions.getNodeTemplatesFolderId())).entry.name; - done(); }); describe('Select Template dialog', () => { - beforeEach(async (done) => { + beforeEach(async () => { await sidenav.openCreateFileFromTemplateDialog(); await selectTemplateDialog.waitForDialogToOpen(); - done(); }); it('Select template - dialog UI - with existing templates - [C325043]', async () => { @@ -230,13 +226,12 @@ describe('Create file from template', () => { }); describe('Create from template dialog', () => { - beforeEach(async (done) => { + beforeEach(async () => { await sidenav.openCreateFileFromTemplateDialog(); await selectTemplateDialog.waitForDialogToOpen(); await selectTemplateDialog.dataTable.selectItem(template1InRootFolder); await selectTemplateDialog.clickNext(); await createFromTemplateDialog.waitForDialogToOpen(); - done(); }); it('Create file from template - dialog UI - [C325020]', async () => { @@ -298,7 +293,7 @@ describe('Create file from template', () => { }); describe('On Personal Files', () => { - beforeEach(async (done) => { + beforeEach(async () => { await page.clickPersonalFilesAndWait(); await page.dataTable.doubleClickOnRowByName(parent); await sidenav.openCreateFileFromTemplateDialog(); @@ -306,7 +301,6 @@ describe('Create file from template', () => { await selectTemplateDialog.dataTable.selectItem(template1InRootFolder); await selectTemplateDialog.clickNext(); await createFromTemplateDialog.waitForDialogToOpen(); - done(); }); it('Create a file from a template - with a new Name - [C325030]', async () => { @@ -318,7 +312,7 @@ describe('Create file from template', () => { expect(await page.dataTable.isItemPresent(file1.name)).toBe(true, 'File not displayed in list view'); }); - it('Create a file from a template - with a Name, Title and Description - [C325026]', async (done) => { + it('Create a file from a template - with a Name, Title and Description - [C325026]', async () => { await createFromTemplateDialog.enterName(file2.name); await createFromTemplateDialog.enterTitle(file2.title); await createFromTemplateDialog.enterDescription(file2.description); @@ -331,7 +325,6 @@ describe('Create file from template', () => { expect(desc).toEqual(file2.description); const title = await userApi.nodes.getNodeTitle(file2.name, parentId); expect(title).toEqual(file2.title); - done(); }); it('Create a file with a duplicate name - [C325028]', async () => { @@ -356,14 +349,14 @@ describe('Create file from template', () => { await createFromTemplateDialog.waitForDialogToClose(); await page.dataTable.waitForHeader(); - expect(await page.dataTable.isItemPresent(nameWithSpaces.trim())).toBe(true, 'Folder not displayed in list view'); + expect(await page.dataTable.isItemPresent(nameWithSpaces.trim())).toBe(true, 'File not displayed in list view'); }); }); describe('On File Libraries', () => { const fileLibrariesPage = new BrowsingPage(); - beforeEach(async (done) => { + beforeEach(async () => { await fileLibrariesPage.goToMyLibrariesAndWait(); await page.dataTable.doubleClickOnRowByName(siteName); await sidenav.openCreateFileFromTemplateDialog(); @@ -371,10 +364,9 @@ describe('Create file from template', () => { await selectTemplateDialog.dataTable.selectItem(template1InRootFolder); await selectTemplateDialog.clickNext(); await createFromTemplateDialog.waitForDialogToOpen(); - done(); }); - it('Create a file from a template - with Name, Title and Description - [C325023]', async (done) => { + it('Create a file from a template - with Name, Title and Description - [C325023]', async () => { await createFromTemplateDialog.enterName(fileSite.name); await createFromTemplateDialog.enterTitle(fileSite.title); await createFromTemplateDialog.enterDescription(fileSite.description); @@ -387,7 +379,6 @@ describe('Create file from template', () => { expect(desc).toEqual(fileSite.description); const title = await userApi.nodes.getNodeTitle(fileSite.name, docLibUserSite); expect(title).toEqual(fileSite.title); - done(); }); it('Cancel file creation - [C325024]', async () => { diff --git a/e2e/suites/actions/create-folder-from-template.test.ts b/e2e/suites/actions/create-folder-from-template.test.ts new file mode 100755 index 000000000..02364c15a --- /dev/null +++ b/e2e/suites/actions/create-folder-from-template.test.ts @@ -0,0 +1,387 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, BrowsingPage } from '../../pages/pages'; +import { SelectTemplateDialog } from '../../components/dialog/select-template-dialog'; +import { CreateFromTemplateDialog } from '../../components/dialog/create-from-template-dialog'; +import { Utils } from '../../utilities/utils'; +import { AdminActions } from '../../utilities/admin-actions'; +import { RepoClient, NodeContentTree } from '../../utilities/repo-client/repo-client'; + +describe('Create folder from template', () => { + const random = Utils.random(); + + const username = `user-${random}`; + + const restrictedTemplateFolder = `restricted-folder-${random}`; + const fileInRestrictedFolder = `restricted-file-${random}.txt`; + + const templateFolder1 = `template-folder1-${random}`; + const fileInFolder1 = `file-1-${random}.txt`; + + const templateFolder2 = `template-folder2-${random}`; + const fileInFolder2 = `file-2-${random}.txt`; + const templateSubFolder = `template-sub-folder-${random}`; + + const fileInRootFolder = `file-in-root-${random}.txt`; + const folderInRootFolder = `folder-in-root-${random}`; + + const parent = `parent-${random}`; + let parentId: string; + + const folder1 = { + name: `folder1-${random}` + }; + const folder2 = { + name: `folder2-${random}`, + title: `folder2 title`, + description: `folder2 description` + }; + const duplicateFolderName = `duplicate-folder-${random}`; + const nameWithSpaces = ` folder-${random} `; + + const siteName = `site-${random}`; + const folderSite = { + name: `folder-site-${random}`, + title: `folder site title`, + description: `folder site description` + }; + const duplicateFolderSite = `duplicate-folder-site-${random}`; + let docLibUserSite: string; + + const templates: NodeContentTree = { + folders: [ + { + name: folderInRootFolder + }, + { + name: templateFolder1, + files: [fileInFolder1] + }, + { + name: templateFolder2, + folders: [ + { + name: templateSubFolder + } + ], + files: [fileInFolder2] + }, + { + name: restrictedTemplateFolder, + files: [fileInRestrictedFolder] + } + ], + files: [fileInRootFolder] + }; + let folderLink: string; + + const userApi = new RepoClient(username, username); + const adminApiActions = new AdminActions(); + + const loginPage = new LoginPage(); + const page = new BrowsingPage(); + const selectTemplateDialog = new SelectTemplateDialog(); + const createFromTemplateDialog = new CreateFromTemplateDialog(); + const { sidenav } = page; + + beforeAll(async () => { + await adminApiActions.createUser({ username }); + + parentId = (await userApi.nodes.createFolder(parent)).entry.id; + await userApi.nodes.createFolder(duplicateFolderName, parentId); + + await userApi.sites.createSite(siteName); + docLibUserSite = await userApi.sites.getDocLibId(siteName); + await userApi.nodes.createFolder(duplicateFolderSite, docLibUserSite); + + await adminApiActions.createSpaceTemplatesHierarchy(templates); + await adminApiActions.removeUserAccessOnSpaceTemplate(restrictedTemplateFolder); + folderLink = (await adminApiActions.createLinkToFolderName(folderInRootFolder, await adminApiActions.getSpaceTemplatesFolderId())).entry.name; + + await loginPage.loginWith(username); + }); + + afterAll(async () => { + await userApi.nodes.deleteNodeById(parentId); + await userApi.sites.deleteSite(siteName); + await adminApiActions.cleanupSpaceTemplatesFolder(); + }); + + beforeEach(async () => { + await page.closeOpenDialogs(); + }); + + describe('Select Template dialog', () => { + beforeEach(async () => { + await sidenav.openCreateFolderFromTemplateDialog(); + await selectTemplateDialog.waitForDialogToOpen(); + }); + + it('Select template - dialog UI - with existing templates - [C325147]', async () => { + expect(await selectTemplateDialog.getTitle()).toEqual('Select a folder template'); + expect(await selectTemplateDialog.dataTable.isEmpty()).toBe(false, 'Datatable is empty'); + expect(await selectTemplateDialog.dataTable.isItemPresent(templateFolder1)).toBe(true, 'template folder not displayed'); + expect(await selectTemplateDialog.dataTable.isItemPresent(templateFolder2)).toBe(true, 'template folder not displayed'); + expect(await selectTemplateDialog.dataTable.isItemPresent(fileInRootFolder)).toBe(true, 'file not displayed'); + expect(await selectTemplateDialog.breadcrumb.getCurrentFolderName()).toEqual('Space Templates'); + expect(await selectTemplateDialog.isNextButtonEnabled()).toBe(false, 'Next button is not disabled'); + expect(await selectTemplateDialog.isCancelButtonEnabled()).toBe(true, 'Cancel button is not enabled'); + }); + + it(`Templates don't appear if user doesn't have permissions to see them - [C325148]`, async () => { + expect(await selectTemplateDialog.dataTable.isItemPresent(restrictedTemplateFolder)).toBe(false, 'restricted template folder is displayed'); + }); + + it('Navigate through the templates list with folder hierarchy - [C325149]', async () => { + expect(await selectTemplateDialog.dataTable.isItemPresent(templateFolder2)).toBe(true, 'template folder not displayed'); + + await selectTemplateDialog.dataTable.doubleClickOnRowByName(templateFolder2); + + expect(await selectTemplateDialog.dataTable.isItemPresent(templateSubFolder)).toBe(true, 'template sub-folder not displayed'); + expect(await selectTemplateDialog.dataTable.isItemPresent(fileInFolder2)).toBe(true, 'template not displayed'); + expect(await selectTemplateDialog.dataTable.isItemPresent(templateFolder1)).toBe(false, 'template folder is displayed'); + expect(await selectTemplateDialog.breadcrumb.getCurrentFolderName()).toEqual(templateFolder2); + + await selectTemplateDialog.dataTable.doubleClickOnRowByName(templateSubFolder); + + expect(await selectTemplateDialog.breadcrumb.getCurrentFolderName()).toEqual(templateSubFolder); + expect(await selectTemplateDialog.dataTable.isEmpty()).toBe(true, 'datatable is not empty'); + + await selectTemplateDialog.breadcrumb.openPath(); + + expect(await selectTemplateDialog.breadcrumb.getPathItems()).toEqual([ templateFolder2, 'Space Templates' ]); + }); + + it(`Templates list doesn't allow multiple selection - [C325150]`, async () => { + expect(await selectTemplateDialog.dataTable.getSelectedRowsCount()).toEqual(0, 'Incorrect number of selected rows'); + + await selectTemplateDialog.dataTable.selectItem(templateFolder1); + expect(await selectTemplateDialog.dataTable.getSelectedRowsCount()).toEqual(1, 'Incorrect number of selected rows'); + expect(await selectTemplateDialog.dataTable.getSelectedRowsNames()).toEqual([ templateFolder1 ], 'Incorrect selected item'); + + await Utils.pressCmd(); + await selectTemplateDialog.dataTable.selectItem(templateFolder2); + await Utils.releaseKeyPressed(); + + expect(await selectTemplateDialog.dataTable.getSelectedRowsCount()).toEqual(1, 'Incorrect number of selected rows'); + expect(await selectTemplateDialog.dataTable.getSelectedRowsNames()).toEqual([ templateFolder2 ], 'Incorrect selected item'); + }); + + it('Links to folders are not displayed - [C325153]', async () => { + expect(await selectTemplateDialog.dataTable.isItemPresent(folderLink)).toBe(false, 'Link to folder is displayed'); + }); + + it('Cancel the Select template dialog - [C325151]', async () => { + expect(await selectTemplateDialog.isCancelButtonEnabled()).toBe(true, 'Cancel button is not enabled'); + + await selectTemplateDialog.clickCancel(); + + expect(await selectTemplateDialog.isDialogOpen()).toBe(false, 'Select Template dialog is open'); + }); + + it('Next button is disabled when selecting a file - [C325139]', async () => { + expect(await selectTemplateDialog.isNextButtonEnabled()).toBe(false, 'Next button is enabled'); + + await selectTemplateDialog.dataTable.selectItem(fileInRootFolder); + + expect(await selectTemplateDialog.isNextButtonEnabled()).toBe(false, 'Next button is enabled'); + }); + }); + + describe('Create from template dialog', () => { + beforeEach(async () => { + await sidenav.openCreateFolderFromTemplateDialog(); + await selectTemplateDialog.waitForDialogToOpen(); + await selectTemplateDialog.dataTable.selectItem(templateFolder1); + await selectTemplateDialog.clickNext(); + await createFromTemplateDialog.waitForDialogToOpen(); + }); + + it('Create folder from template - dialog UI - [C325142]', async () => { + expect(await createFromTemplateDialog.getTitle()).toEqual(`Create new folder from '${templateFolder1}'`); + expect(await createFromTemplateDialog.isNameFieldDisplayed()).toBe(true, 'Name field not displayed'); + expect(await createFromTemplateDialog.isTitleFieldDisplayed()).toBe(true, 'Title field not displayed'); + expect(await createFromTemplateDialog.isDescriptionFieldDisplayed()).toBe(true, 'Description field not displayed'); + expect(await createFromTemplateDialog.isCancelButtonEnabled()).toBe(true, 'Cancel button is not enabled'); + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(true, 'Create button is not enabled'); + }); + + it('Folder name is required - [C325143]', async () => { + expect(await createFromTemplateDialog.getName()).toEqual(templateFolder1); + await createFromTemplateDialog.deleteNameWithBackspace(); + + expect(await createFromTemplateDialog.getValidationMessage()).toEqual('Name is required'); + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + }); + + it('Special characters in folder name - [C325144]', async () => { + const namesWithSpecialChars = [ 'a*a', 'a"a', 'aa', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ]; + + for (const name of namesWithSpecialChars) { + await createFromTemplateDialog.enterName(name); + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + expect(await createFromTemplateDialog.getValidationMessage()).toContain(`Name can't contain these characters`); + } + }); + + it('Folder name ending with a dot - [C325145]', async () => { + await createFromTemplateDialog.enterName('folder-name.'); + + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + expect(await createFromTemplateDialog.getValidationMessage()).toMatch(`Name can't end with a period .`); + }); + + it('Folder name containing only spaces - [C325146]', async () => { + await createFromTemplateDialog.enterName(' '); + + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + expect(await createFromTemplateDialog.getValidationMessage()).toMatch(`Name can't contain only spaces`); + }); + + it('Title too long - [C325141]', async () => { + await createFromTemplateDialog.enterTitle(Utils.string257); + await Utils.pressTab(); + + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + expect(await createFromTemplateDialog.getValidationMessage()).toMatch(`Use 256 characters or less for title`); + }); + + it('Description too long - [C325140]', async () => { + await createFromTemplateDialog.enterDescription(Utils.string513); + await Utils.pressTab(); + + expect(await createFromTemplateDialog.isCreateButtonEnabled()).toBe(false, 'Create button is not disabled'); + expect(await createFromTemplateDialog.getValidationMessage()).toMatch(`Use 512 characters or less for description`); + }); + }); + + describe('On Personal Files', () => { + beforeEach(async () => { + await page.clickPersonalFilesAndWait(); + await page.dataTable.doubleClickOnRowByName(parent); + await sidenav.openCreateFolderFromTemplateDialog(); + await selectTemplateDialog.waitForDialogToOpen(); + await selectTemplateDialog.dataTable.selectItem(templateFolder1); + await selectTemplateDialog.clickNext(); + await createFromTemplateDialog.waitForDialogToOpen(); + }); + + it('Create a folder from a template - with a new Name - [C325157]', async () => { + await createFromTemplateDialog.enterName(folder1.name); + await createFromTemplateDialog.clickCreate(); + await createFromTemplateDialog.waitForDialogToClose(); + await page.dataTable.waitForHeader(); + + expect(await page.dataTable.isItemPresent(folder1.name)).toBe(true, 'Folder not displayed in list view'); + }); + + it('Create a folder from a template - with a Name, Title and Description - [C325154]', async () => { + await createFromTemplateDialog.enterName(folder2.name); + await createFromTemplateDialog.enterTitle(folder2.title); + await createFromTemplateDialog.enterDescription(folder2.description); + await createFromTemplateDialog.clickCreate(); + await createFromTemplateDialog.waitForDialogToClose(); + await page.dataTable.waitForHeader(); + + expect(await page.dataTable.isItemPresent(folder2.name)).toBe(true, 'Folder not displayed in list view'); + const desc = await userApi.nodes.getNodeDescription(folder2.name, parentId); + expect(desc).toEqual(folder2.description); + const title = await userApi.nodes.getNodeTitle(folder2.name, parentId); + expect(title).toEqual(folder2.title); + }); + + it('Create a folder with a duplicate name - [C325156]', async () => { + await createFromTemplateDialog.enterName(duplicateFolderName); + await createFromTemplateDialog.clickCreate(); + + expect(await page.getSnackBarMessage()).toEqual(`This name is already in use, try a different name.`); + expect(await createFromTemplateDialog.isDialogOpen()).toBe(true, 'dialog is not present'); + }); + + it('Cancel folder creation - [C325155]', async () => { + await createFromTemplateDialog.enterName('test'); + await createFromTemplateDialog.clickCancel(); + + expect(await createFromTemplateDialog.isDialogOpen()).not.toBe(true, 'dialog is not closed'); + expect(await page.dataTable.isItemPresent('test')).toBe(false, 'Folder should not appear in the list'); + }); + + it('Trim spaces from folder Name - [C325158]', async () => { + await createFromTemplateDialog.enterName(nameWithSpaces); + await createFromTemplateDialog.clickCreate(); + await createFromTemplateDialog.waitForDialogToClose(); + await page.dataTable.waitForHeader(); + + expect(await page.dataTable.isItemPresent(nameWithSpaces.trim())).toBe(true, 'Folder not displayed in list view'); + }); + }); + + describe('On File Libraries', () => { + const fileLibrariesPage = new BrowsingPage(); + + beforeEach(async () => { + await fileLibrariesPage.goToMyLibrariesAndWait(); + await page.dataTable.doubleClickOnRowByName(siteName); + await sidenav.openCreateFolderFromTemplateDialog(); + await selectTemplateDialog.waitForDialogToOpen(); + await selectTemplateDialog.dataTable.selectItem(templateFolder1); + await selectTemplateDialog.clickNext(); + await createFromTemplateDialog.waitForDialogToOpen(); + }); + + it('Create a folder from a template - with Name, Title and Description - [C325161]', async () => { + await createFromTemplateDialog.enterName(folderSite.name); + await createFromTemplateDialog.enterTitle(folderSite.title); + await createFromTemplateDialog.enterDescription(folderSite.description); + await createFromTemplateDialog.clickCreate(); + await createFromTemplateDialog.waitForDialogToClose(); + await page.dataTable.waitForHeader(); + + expect(await page.dataTable.isItemPresent(folderSite.name)).toBe(true, 'Folder not displayed in list view'); + const desc = await userApi.nodes.getNodeDescription(folderSite.name, docLibUserSite); + expect(desc).toEqual(folderSite.description); + const title = await userApi.nodes.getNodeTitle(folderSite.name, docLibUserSite); + expect(title).toEqual(folderSite.title); + }); + + it('Cancel folder creation - [C325162]', async () => { + await createFromTemplateDialog.enterName('test'); + await createFromTemplateDialog.clickCancel(); + + expect(await createFromTemplateDialog.isDialogOpen()).not.toBe(true, 'dialog is not closed'); + expect(await page.dataTable.isItemPresent('test')).toBe(false, 'Folder should not appear in the list'); + }); + + it('Create a folder with a duplicate name - [C325163]', async () => { + await createFromTemplateDialog.enterName(duplicateFolderSite); + await createFromTemplateDialog.clickCreate(); + + expect(await page.getSnackBarMessage()).toEqual(`This name is already in use, try a different name.`); + expect(await createFromTemplateDialog.isDialogOpen()).toBe(true, 'dialog is not present'); + }); + }); + +}); diff --git a/e2e/suites/actions/new-menu.test.ts b/e2e/suites/actions/new-menu.test.ts index 308df4d6a..59627e4a0 100755 --- a/e2e/suites/actions/new-menu.test.ts +++ b/e2e/suites/actions/new-menu.test.ts @@ -77,6 +77,7 @@ describe('New menu', () => { expect(await menu.isCreateLibraryEnabled()).toBe(true, 'Create Library option not enabled'); expect(await menu.isCreateFileFromTemplateEnabled()).toBe(true, 'Create file from template is not enabled'); + expect(await menu.isCreateFolderFromTemplateEnabled()).toBe(true, 'Create folder from template is not enabled'); }); it('Actions in File Libraries - user with enough permissions - [C280393]', async () => { @@ -91,6 +92,7 @@ describe('New menu', () => { expect(await menu.isCreateLibraryEnabled()).toBe(true, 'Create Library option not enabled'); expect(await menu.isCreateFileFromTemplateEnabled()).toBe(true, 'Create file from template is not enabled'); + expect(await menu.isCreateFolderFromTemplateEnabled()).toBe(true, 'Create folder from template is not enabled'); }); it('Actions in File Libraries - user without enough permissions - [C280397]', async () => { @@ -105,6 +107,7 @@ describe('New menu', () => { expect(await menu.isCreateLibraryEnabled()).toBe(true, 'Create Library option not enabled'); expect(await menu.isCreateFileFromTemplateEnabled()).toBe(false, 'Create file from template is not disabled'); + expect(await menu.isCreateFolderFromTemplateEnabled()).toBe(false, 'Create folder from template is not disabled'); }); it('Enabled actions tooltips - [C216342]', async () => { diff --git a/e2e/suites/actions/upload-file.test.ts b/e2e/suites/actions/upload-file.test.ts index 04642cb67..b9eb37dfb 100755 --- a/e2e/suites/actions/upload-file.test.ts +++ b/e2e/suites/actions/upload-file.test.ts @@ -63,5 +63,7 @@ describe('Upload files', () => { await dataTable.doubleClickOnRowByName(folder1); await page.sidenav.openNewMenu(); await page.sidenav.menu.uploadFile().sendKeys(`${__dirname}/create-folder.test.ts`); + + expect(await dataTable.isItemPresent('create-folder.test.ts')).toBe(true, 'file not uploaded'); }); }); diff --git a/e2e/utilities/admin-actions.ts b/e2e/utilities/admin-actions.ts index a8b859cd7..980c59825 100755 --- a/e2e/utilities/admin-actions.ts +++ b/e2e/utilities/admin-actions.ts @@ -54,6 +54,10 @@ export class AdminActions { return await this.adminApi.nodes.getNodeIdFromParent('Node Templates', await this.getDataDictionaryId()); } + async getSpaceTemplatesFolderId(): Promise { + return await this.adminApi.nodes.getNodeIdFromParent('Space Templates', await this.getDataDictionaryId()); + } + async createUser(user: PersonModel): Promise { return await this.adminApi.people.createUser(user); } @@ -68,19 +72,47 @@ export class AdminActions { return await this.adminApi.nodes.createContent(hierarchy, `Data Dictionary/Node Templates`); } - async removeUserAccessOnNode(nodeName: string): Promise { + async createSpaceTemplate(name: string, title: string = '', description: string = ''): Promise { + const templatesRootFolderId: string = await this.getSpaceTemplatesFolderId(); + + return await this.adminApi.nodes.createFolder(name, templatesRootFolderId, title, description); + } + + async createSpaceTemplatesHierarchy(hierarchy: NodeContentTree): Promise { + return await this.adminApi.nodes.createContent(hierarchy, `Data Dictionary/Space Templates`); + } + + async removeUserAccessOnNodeTemplate(nodeName: string): Promise { const templatesRootFolderId = await this.getNodeTemplatesFolderId(); const nodeId: string = await this.adminApi.nodes.getNodeIdFromParent(nodeName, templatesRootFolderId); return await this.adminApi.nodes.setInheritPermissions(nodeId, false); } - async cleanNodeTemplatesFolder(): Promise { + async removeUserAccessOnSpaceTemplate(nodeName: string): Promise { + const templatesRootFolderId = await this.getSpaceTemplatesFolderId(); + const nodeId: string = await this.adminApi.nodes.getNodeIdFromParent(nodeName, templatesRootFolderId); + + return await this.adminApi.nodes.setInheritPermissions(nodeId, false); + } + + async cleanupNodeTemplatesFolder(): Promise { return await this.adminApi.nodes.deleteNodeChildren(await this.getNodeTemplatesFolderId()); } + async cleanupSpaceTemplatesFolder(): Promise { + const spaceTemplatesNodeId = await this.getSpaceTemplatesFolderId(); + + // folder links are deleted automatically when original folder is deleted + // Software Engineering Project is the default folder template coming from ACS, should not be deleted + const nodesToDelete = (await this.adminApi.nodes.getNodeChildren(spaceTemplatesNodeId)).list.entries + .filter(node => (node.entry.nodeType !== 'app:folderlink') && (node.entry.name !== 'Software Engineering Project')) + .map(node => node.entry.id); + return await this.adminApi.nodes.deleteNodesById(nodesToDelete); + } + async createLinkToFileId(originalFileId: string, destinationParentId: string): Promise { - return await this.adminApi.nodes.createNodeLink(originalFileId, destinationParentId); + return await this.adminApi.nodes.createFileLink(originalFileId, destinationParentId); } async createLinkToFileName(originalFileName: string, originalFileParentId: string, destinationParentId?: string): Promise { @@ -93,4 +125,18 @@ export class AdminActions { return await this.createLinkToFileId(nodeId, destinationParentId); } + async createLinkToFolderId(originalFolderId: string, destinationParentId: string): Promise { + return await this.adminApi.nodes.createFolderLink(originalFolderId, destinationParentId); + } + + async createLinkToFolderName(originalFolderName: string, originalFolderParentId: string, destinationParentId?: string): Promise { + if (!destinationParentId) { + destinationParentId = originalFolderParentId + }; + + const nodeId = await this.adminApi.nodes.getNodeIdFromParent(originalFolderName, originalFolderParentId); + + return await this.createLinkToFolderId(nodeId, destinationParentId); + } + } diff --git a/e2e/utilities/repo-client/apis/nodes/nodes-api.ts b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts index 580d4476a..4db0b18c9 100755 --- a/e2e/utilities/repo-client/apis/nodes/nodes-api.ts +++ b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts @@ -179,11 +179,9 @@ export class NodesApi extends RepoApi { async deleteNodesById(ids: string[], permanent: boolean = true): Promise { try { - await ids.reduce(async (previous, current) => { - await previous; - const req = await this.deleteNodeById(current, permanent); - return req; - }, Promise.resolve()); + for (const id of ids) { + await this.deleteNodeById(id, permanent); + } } catch (error) { this.handleError(`${this.constructor.name} ${this.deleteNodesById.name}`, error); } @@ -202,10 +200,17 @@ export class NodesApi extends RepoApi { } } - async deleteNodeChildren(parentId: string): Promise { + async deleteNodeChildren(parentId: string, exceptNodesNamed?: string[]): Promise { try { const listEntries = (await this.getNodeChildren(parentId)).list.entries; - const nodeIds = listEntries.map(entries => entries.entry.id); + let nodeIds: string[]; + if (exceptNodesNamed) { + nodeIds = listEntries + .filter(entries => !exceptNodesNamed.includes(entries.entry.name)) + .map(entries => entries.entry.id); + } else { + nodeIds = listEntries.map(entries => entries.entry.id); + } await this.deleteNodesById(nodeIds); } catch (error) { this.handleError(`${this.constructor.name} ${this.deleteNodeChildren.name}`, error); @@ -225,26 +230,7 @@ export class NodesApi extends RepoApi { } } - async createNodeLink(originalNodeId: string, destinationId: string): Promise { - const name = (await this.getNodeById(originalNodeId)).entry.name; - const nodeBody = { - name: `Link to ${name}.url`, - nodeType: 'app:filelink', - properties: { - 'cm:destination': originalNodeId - } - } - - try { - await this.apiAuth(); - return await this.nodesApi.createNode(destinationId, nodeBody); - } catch (error) { - this.handleError(`${this.constructor.name} ${this.createNode.name}`, error); - return null; - } - } - - async createNode(nodeType: string, name: string, parentId: string = '-my-', title: string = '', description: string = '', imageProps: any = null, author: string = '', majorVersion: boolean = true): Promise { + async createNode(nodeType: string, name: string, parentId: string = '-my-', title: string = '', description: string = '', imageProps: any = null, author: string = '', majorVersion: boolean = true, aspectNames: string[] = null): Promise { const nodeBody = { name, nodeType, @@ -253,7 +239,7 @@ export class NodesApi extends RepoApi { 'cm:description': description, 'cm:author': author }, - aspectNames: ['cm:versionable'] // workaround for REPO-4772 + aspectNames }; if (imageProps) { nodeBody.properties = Object.assign(nodeBody.properties, imageProps); @@ -268,9 +254,12 @@ export class NodesApi extends RepoApi { } } - async createFile(name: string, parentId: string = '-my-', title: string = '', description: string = '', author: string = '', majorVersion: boolean = true): Promise { + async createFile(name: string, parentId: string = '-my-', title: string = '', description: string = '', author: string = '', majorVersion: boolean = true, aspectNames: string[] = null): Promise { + if (!aspectNames) { + aspectNames = ['cm:versionable'] // workaround for REPO-4772 + } try { - return await this.createNode('cm:content', name, parentId, title, description, null, author, majorVersion); + return await this.createNode('cm:content', name, parentId, title, description, null, author, majorVersion, aspectNames); } catch (error) { this.handleError(`${this.constructor.name} ${this.createFile.name}`, error); return null; @@ -286,9 +275,9 @@ export class NodesApi extends RepoApi { } } - async createFolder(name: string, parentId: string = '-my-', title: string = '', description: string = '', author: string = ''): Promise { + async createFolder(name: string, parentId: string = '-my-', title: string = '', description: string = '', author: string = '', aspectNames: string[] = null): Promise { try { - return await this.createNode('cm:folder', name, parentId, title, description, null, author); + return await this.createNode('cm:folder', name, parentId, title, description, null, author, null, aspectNames); } catch (error) { this.handleError(`${this.constructor.name} ${this.createFolder.name}`, error); return null; @@ -328,6 +317,61 @@ export class NodesApi extends RepoApi { } } + async addAspects(nodeId: string, aspectNames: string[]): Promise { + try { + await this.apiAuth(); + return this.nodesApi.updateNode(nodeId, { aspectNames }); + } catch (error) { + this.handleError(`${this.constructor.name} ${this.addAspects.name}`, error); + return null; + } + } + + async createFileLink(originalNodeId: string, destinationId: string): Promise { + const name = (await this.getNodeById(originalNodeId)).entry.name; + const nodeBody = { + name: `Link to ${name}.url`, + nodeType: 'app:filelink', + properties: { + 'cm:destination': originalNodeId + } + } + + try { + await this.apiAuth(); + const link = await this.nodesApi.createNode(destinationId, nodeBody); + await this.addAspects(originalNodeId, ['app:linked']); + return link; + } catch (error) { + this.handleError(`${this.constructor.name} ${this.createFileLink.name}`, error); + return null; + } + } + + async createFolderLink(originalNodeId: string, destinationId: string): Promise { + const name = (await this.getNodeById(originalNodeId)).entry.name; + const nodeBody = { + name: `Link to ${name}.url`, + nodeType: 'app:folderlink', + properties: { + 'cm:title': `Link to ${name}.url`, + 'cm:destination': originalNodeId, + 'cm:description': `Link to ${name}.url`, + 'app:icon': 'space-icon-link' + } + } + + try { + await this.apiAuth(); + const link = await this.nodesApi.createNode(destinationId, nodeBody); + await this.addAspects(originalNodeId, ['app:linked']); + return link; + } catch (error) { + this.handleError(`${this.constructor.name} ${this.createFolderLink.name}`, error); + return null; + } + } + // node content async getNodeContent(nodeId: string): Promise { try { @@ -405,7 +449,7 @@ export class NodesApi extends RepoApi { try { await this.apiAuth(); - return await this.nodesApi.lockNode(nodeId, data ); + return await this.nodesApi.lockNode(nodeId, data); } catch (error) { this.handleError(`${this.constructor.name} ${this.lockFile.name}`, error); return null; diff --git a/protractor.conf.js b/protractor.conf.js index 2e7c2adf2..4e74a0823 100755 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -69,6 +69,7 @@ exports.config = { addRemoveContent: [ './e2e/suites/actions/new-menu.test.ts', './e2e/suites/actions/create-folder.test.ts', + './e2e/suites/actions/create-folder-from-template.test.ts', './e2e/suites/actions/create-library.test.ts', './e2e/suites/actions/create-file-from-template.test.ts', './e2e/suites/actions/upload-file.test.ts', diff --git a/src/app/services/node-template.service.spec.ts b/src/app/services/node-template.service.spec.ts index 3e94713e5..7233c6941 100644 --- a/src/app/services/node-template.service.spec.ts +++ b/src/app/services/node-template.service.spec.ts @@ -150,7 +150,7 @@ describe('NodeTemplateService', () => { ).toBe(true); }); - it('should return false if row is a `link` nodeType', () => { + it('should return false if row is a `filelink` nodeType', () => { spyOn( alfrescoApiService.getInstance().nodes, 'getNodeInfo' @@ -174,6 +174,30 @@ describe('NodeTemplateService', () => { ).toBe(false); }); + it('should return false if row is a `folderlink` nodeType', () => { + spyOn( + alfrescoApiService.getInstance().nodes, + 'getNodeInfo' + ).and.returnValue( + of({ + id: 'templates-folder-id', + path: { + elements: [], + name: '/Company Home/Data Dictionary' + } + }) + ); + spyOn(dialog, 'open'); + + nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + + expect( + dialog.open['calls'].argsFor(0)[1].data.rowFilter({ + node: { entry: { nodeType: 'app:folderlink' } } + }) + ).toBe(false); + }); + describe('File templates', () => { it('should return false if selected node is not a file', () => { spyOn(