diff --git a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html index 0f04e5f167..ec4d917d22 100644 --- a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html @@ -6,9 +6,9 @@
- {{ + {{ 'PEOPLE_GROUPS_CLOUD.SINGLE' | translate }} - {{ + {{ 'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}
@@ -20,15 +20,15 @@ {{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"] - + {{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }} - + {{ 'PEOPLE_GROUPS_CLOUD.PRESELECTED_VALUE' | translate }} {{ DEFAULT_PEOPLE_PLACEHOLDER }} - + {{ 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }} @@ -47,7 +47,7 @@ (warning)="onUsersWarning($event)">
-
+

{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_USERS' | translate }}

@@ -77,9 +77,9 @@
- {{ + {{ 'PEOPLE_GROUPS_CLOUD.SINGLE' | translate }} - {{ + {{ 'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}
@@ -91,20 +91,31 @@ {{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"] - + {{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }} - + Preselect: {{ DEFAULT_GROUP_PLACEHOLDER }} - + - {{ - 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }} - {{ - 'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }} + + {{ 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }} + + + {{ 'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }} +
-
+

{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_GROUPS' | translate }}

diff --git a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts index 31084620c2..2dc73222e7 100644 --- a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts @@ -16,7 +16,7 @@ */ import { Component, ViewEncapsulation } from '@angular/core'; -import { PeopleCloudComponent, GroupCloudComponent } from '@alfresco/adf-process-services-cloud'; +import { ComponentSelectionMode } from '@alfresco/adf-process-services-cloud'; import { MatRadioChange, MatCheckboxChange } from '@angular/material'; import { IdentityGroupModel, IdentityUserModel } from '@alfresco/adf-core'; @@ -32,83 +32,83 @@ export class PeopleGroupCloudDemoComponent { DEFAULT_GROUP_PLACEHOLDER: string = `[{"id": "1", "name":"activitiUserGroup"}]`; DEFAULT_PEOPLE_PLACEHOLDER: string = `[{"id": "1", email": "user@user.com", "firstName":"user", "lastName": "lastName", "username": "user"}]`; - peopleMode: string = PeopleCloudComponent.MODE_SINGLE; + peopleMode: ComponentSelectionMode = 'single'; preSelectUsers: IdentityUserModel[] = []; invalidUsers: IdentityUserModel[] = []; peopleRoles: string[] = []; peopleAppName: string; peopleFilterMode: string = this.DEFAULT_FILTER_MODE; - peoplePreselectValidation: Boolean = false; + peoplePreselectValidation = false; groupPreselectValidation = false; peopleReadonly = false; groupReadonly = false; - groupMode: string = GroupCloudComponent.MODE_SINGLE; + groupMode: ComponentSelectionMode = 'single'; preSelectGroup: IdentityGroupModel[] = []; invalidGroups: IdentityGroupModel[] = []; groupRoles: string[]; groupAppName: string; groupFilterMode: string = this.DEFAULT_FILTER_MODE; - setPeoplePreselectValue(event: any) { - this.preSelectUsers = this.getArrayFromString(event.target.value); + setPeoplePreselectValue(value: string): void { + this.preSelectUsers = this.getArrayFromString(value); } - setGroupsPreselectValue(event: any) { - this.preSelectGroup = this.getArrayFromString(event.target.value); + setGroupsPreselectValue(value: string): void { + this.preSelectGroup = this.getArrayFromString(value); } - setPeopleRoles(event: any) { - this.peopleRoles = this.getArrayFromString(event.target.value); + setPeopleRoles(value: string): void { + this.peopleRoles = this.getArrayFromString(value); } - setGroupRoles(event: any) { - this.groupRoles = this.getArrayFromString(event.target.value); + setGroupRoles(value: string): void { + this.groupRoles = this.getArrayFromString(value); } - setPeopleAppName(event: any) { - this.peopleAppName = event.target.value; + setPeopleAppName(value: string): void { + this.peopleAppName = value; } - setGroupAppName(event: any) { - this.groupAppName = event.target.value; + setGroupAppName(value: string): void { + this.groupAppName = value; } - onChangePeopleMode(event: MatRadioChange) { + onChangePeopleMode(event: MatRadioChange): void { this.peopleMode = event.value; } - onChangePeopleReadonly(event: MatCheckboxChange) { + onChangePeopleReadonly(event: MatCheckboxChange): void { this.peopleReadonly = event.checked; } - onChangeGroupReadonly(event: MatCheckboxChange) { + onChangeGroupReadonly(event: MatCheckboxChange): void { this.groupReadonly = event.checked; } - onChangeGroupsMode(event: MatRadioChange) { + onChangeGroupsMode(event: MatRadioChange): void { this.groupMode = event.value; } - onChangePeopleFilterMode(event: MatRadioChange) { + onChangePeopleFilterMode(event: MatRadioChange): void { this.peopleFilterMode = event.value; this.resetPeopleFilter(); } - onChangeGroupsFilterMode(event: MatRadioChange) { + onChangeGroupsFilterMode(event: MatRadioChange): void { this.groupFilterMode = event.value; this.restGroupFilter(); } - isPeopleAppNameSelected() { + isPeopleAppNameSelected(): boolean { return this.peopleFilterMode === 'appName'; } - isGroupAppNameSelected() { + isGroupAppNameSelected(): boolean { return this.groupFilterMode === 'appName'; } - resetPeopleFilter() { + resetPeopleFilter(): void { if (this.isPeopleAppNameSelected()) { this.peopleRoles = []; } else { @@ -116,7 +116,7 @@ export class PeopleGroupCloudDemoComponent { } } - restGroupFilter() { + restGroupFilter(): void { if (this.isGroupAppNameSelected()) { this.groupRoles = []; } else { @@ -124,23 +124,23 @@ export class PeopleGroupCloudDemoComponent { } } - onChangePeopleValidation(event: MatCheckboxChange) { + onChangePeopleValidation(event: MatCheckboxChange): void { this.peoplePreselectValidation = event.checked; } - onChangeGroupValidation(event: MatCheckboxChange) { + onChangeGroupValidation(event: MatCheckboxChange): void { this.groupPreselectValidation = event.checked; } - onGroupsWarning(warning: any) { + onGroupsWarning(warning: any): void { this.invalidGroups = warning.groups; } - onUsersWarning(warning: any) { + onUsersWarning(warning: any): void { this.invalidUsers = warning.users; } - isStringArray(str: string) { + isStringArray(str: string): boolean { try { const result = JSON.parse(str); return Array.isArray(result); @@ -149,36 +149,11 @@ export class PeopleGroupCloudDemoComponent { } } - private getArrayFromString(value: string) { - + private getArrayFromString(value: string): T[] { if (this.isStringArray(value)) { return JSON.parse(value); } else { return []; } } - - canShowPeopleList() { - return this.peopleMode === GroupCloudComponent.MODE_MULTIPLE; - } - - canShowGroupList() { - return this.groupMode === GroupCloudComponent.MODE_MULTIPLE; - } - - get peopleSingleMode() { - return PeopleCloudComponent.MODE_SINGLE; - } - - get peopleMultipleMode() { - return PeopleCloudComponent.MODE_MULTIPLE; - } - - get groupSingleMode() { - return GroupCloudComponent.MODE_SINGLE; - } - - get groupMultipleMode() { - return GroupCloudComponent.MODE_MULTIPLE; - } } diff --git a/e2e/process-services-cloud/people-group-cloud-component.e2e.ts b/e2e/process-services-cloud/people-group-cloud-component.e2e.ts index 1a959ad183..202409a8bb 100644 --- a/e2e/process-services-cloud/people-group-cloud-component.e2e.ts +++ b/e2e/process-services-cloud/people-group-cloud-component.e2e.ts @@ -200,7 +200,6 @@ describe('People Groups Cloud Component', () => { await peopleGroupCloudComponentPage.clickPreselectValidation(); await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true'); - await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true'); await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"id":"${noRoleUser.idIdentityService}"}]`); await expect(await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`)); diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts index 2e4ce75776..4e5218bc01 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts @@ -45,13 +45,15 @@ describe('GroupCloudComponent', () => { } }; + function getElement(selector: string): T { + return fixture.nativeElement.querySelector(selector); + } + setupTestBed({ imports: [ CoreModule.forRoot(), ProcessServiceCloudTestingModule, - GroupCloudModule], - providers: [ - IdentityGroupService + GroupCloudModule ] }); @@ -59,19 +61,19 @@ describe('GroupCloudComponent', () => { fixture = TestBed.createComponent(GroupCloudComponent); component = fixture.componentInstance; element = fixture.nativeElement; + identityGroupService = TestBed.get(IdentityGroupService); alfrescoApiService = TestBed.get(AlfrescoApiService); - spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); - }); - it('should create GroupCloudComponent', () => { - expect(component instanceof GroupCloudComponent).toBeTruthy(); + spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); }); it('should populate placeholder when title is present', async(() => { component.title = 'TITLE_KEY'; fixture.detectChanges(); + const matLabel: HTMLInputElement = element.querySelector('#adf-group-cloud-title-id'); + fixture.whenStable().then(() => { fixture.detectChanges(); expect(matLabel.textContent).toEqual('TITLE_KEY'); @@ -82,18 +84,17 @@ describe('GroupCloudComponent', () => { beforeEach(async(() => { fixture.detectChanges(); - element = fixture.nativeElement; findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups)); })); it('should list the groups as dropdown options if the search term has results', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'Mock'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); - fixture.detectChanges(); + const input = getElement('input'); + input.focus(); + input.value = 'Mock'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(5); @@ -124,11 +125,13 @@ describe('GroupCloudComponent', () => { it('should hide result list if input is empty', (done) => { fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = ''; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = ''; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { expect(element.querySelector('mat-option')).toBeNull(); @@ -136,33 +139,76 @@ describe('GroupCloudComponent', () => { }); }); - it('should selectedGroup and groupsChanged emit, update selected groups when a group is selected', (done) => { - const group = { name: 'groupname' }; - fixture.detectChanges(); - const selectEmitSpy = spyOn(component.selectGroup, 'emit'); - const changedGroupsSpy = spyOn(component.changedGroups, 'emit'); - component.onSelect(group); + it('should update selected groups when a group is selected', (done) => { fixture.detectChanges(); + const selectEmitSpy = spyOn(component.selectGroup, 'emit'); + const changedGroupsSpy = spyOn(component.changedGroups, 'emit'); + + const group = { name: 'groupname' }; + component.onSelect(group); + + fixture.detectChanges(); fixture.whenStable().then(() => { expect(selectEmitSpy).toHaveBeenCalledWith(group); expect(changedGroupsSpy).toHaveBeenCalledWith([group]); - expect(component.getSelectedGroups()[0]).toEqual(group); + expect(component.selectedGroups).toEqual([group]); done(); }); }); + it('should replace the group in single-selection mode', () => { + component.mode = 'single'; + + const group1 = { name: 'group1' }; + const group2 = { name: 'group2' }; + + component.onSelect(group1); + expect(component.selectedGroups).toEqual([group1]); + + component.onSelect(group2); + expect(component.selectedGroups).toEqual([group2]); + }); + + it('should allow multiple groups in multi-selection mode', () => { + component.mode = 'multiple'; + + const group1 = { name: 'group1' }; + const group2 = { name: 'group2' }; + + component.onSelect(group1); + component.onSelect(group2); + + expect(component.selectedGroups).toEqual([group1, group2]); + }); + + it('should allow only unique groups in multi-selection mode', () => { + component.mode = 'multiple'; + + const group1 = { name: 'group1' }; + const group2 = { name: 'group2' }; + + component.onSelect(group1); + component.onSelect(group2); + component.onSelect(group1); + component.onSelect(group2); + + expect(component.selectedGroups).toEqual([group1, group2]); + }); + it('should show an error message if the search result empty', (done) => { findGroupsByNameSpy.and.returnValue(of([])); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'ZZZ'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'ZZZ'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { - inputHTMLElement.blur(); + input.blur(); fixture.detectChanges(); const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]'); expect(errorMessage).not.toBeNull(); @@ -173,7 +219,6 @@ describe('GroupCloudComponent', () => { }); describe('when application name defined', () => { - let checkGroupHasAnyClientAppRoleSpy: jasmine.Spy; let checkGroupHasClientAppSpy: jasmine.Spy; @@ -181,17 +226,20 @@ describe('GroupCloudComponent', () => { findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups)); checkGroupHasAnyClientAppRoleSpy = spyOn(identityGroupService, 'checkGroupHasAnyClientAppRole').and.returnValue(of(true)); checkGroupHasClientAppSpy = spyOn(identityGroupService, 'checkGroupHasClientApp').and.returnValue(of(true)); + component.preSelectGroups = []; component.appName = 'mock-app-name'; + fixture.detectChanges(); - element = fixture.nativeElement; })); it('should fetch the client ID if appName specified', async (() => { const getClientIdByApplicationNameSpy = spyOn(identityGroupService, 'getClientIdByApplicationName').and.callThrough(); component.appName = 'mock-app-name'; + const change = new SimpleChange(null, 'mock-app-name', false); component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -200,11 +248,12 @@ describe('GroupCloudComponent', () => { })); it('should list groups who have access to the app when appName is specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -216,11 +265,13 @@ describe('GroupCloudComponent', () => { it('should not list groups who do not have access to the app when appName is specified', (done) => { checkGroupHasClientAppSpy.and.returnValue(of(false)); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -231,11 +282,13 @@ describe('GroupCloudComponent', () => { it('should list groups if given roles mapped with client roles', (done) => { component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1']; - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -248,11 +301,13 @@ describe('GroupCloudComponent', () => { it('should not list groups if roles are not mapping with client roles', (done) => { checkGroupHasAnyClientAppRoleSpy.and.returnValue(of(false)); component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1']; - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -263,11 +318,12 @@ describe('GroupCloudComponent', () => { }); it('should not call client role mapping sevice if roles not specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -277,11 +333,12 @@ describe('GroupCloudComponent', () => { }); it('should validate access to the app when appName is specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -293,11 +350,13 @@ describe('GroupCloudComponent', () => { it('should not validate access to the app when appName is not specified', (done) => { component.appName = ''; fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -310,18 +369,22 @@ describe('GroupCloudComponent', () => { checkGroupHasClientAppSpy.and.returnValue(of(false)); findGroupsByNameSpy.and.returnValue(of([])); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'ZZZ'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'ZZZ'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { - inputHTMLElement.blur(); + input.blur(); fixture.detectChanges(); + const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]'); expect(errorMessage).not.toBeNull(); expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND'); + done(); }); }); @@ -335,6 +398,7 @@ describe('GroupCloudComponent', () => { it('should not pre-select any group when preSelectGroups is empty - single mode', () => { component.mode = 'single'; fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); expect(chips.length).toEqual(0); }); @@ -342,13 +406,13 @@ describe('GroupCloudComponent', () => { it('should not pre-select any group when preSelectGroups is empty - multiple mode', () => { component.mode = 'multiple'; fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); expect(chips.length).toEqual(0); }); }); describe('When roles defined', () => { - let checkGroupHasRoleSpy: jasmine.Spy; beforeEach(async(() => { @@ -356,15 +420,16 @@ describe('GroupCloudComponent', () => { spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups)); checkGroupHasRoleSpy = spyOn(identityGroupService, 'checkGroupHasRole').and.returnValue(of(true)); fixture.detectChanges(); - element = fixture.nativeElement; })); it('should filter if groups has any specified role', (done) => { fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -377,10 +442,12 @@ describe('GroupCloudComponent', () => { it('should not filter groups if group does not have any specified role', (done) => { fixture.detectChanges(); checkGroupHasRoleSpy.and.returnValue(of(false)); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -393,10 +460,12 @@ describe('GroupCloudComponent', () => { it('should not call checkGroupHasRole service when roles are not specified', (done) => { component.roles = []; fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -407,7 +476,6 @@ describe('GroupCloudComponent', () => { }); describe('Single Mode with pre-selected groups', () => { - const changes = new SimpleChange(null, mockIdentityGroups, false); beforeEach(async(() => { @@ -415,7 +483,6 @@ describe('GroupCloudComponent', () => { component.preSelectGroups = mockIdentityGroups; component.ngOnChanges({ 'preSelectGroups': changes }); fixture.detectChanges(); - element = fixture.nativeElement; })); it('should show only one mat chip with the first preSelectedGroup', () => { @@ -426,7 +493,6 @@ describe('GroupCloudComponent', () => { }); describe('Multiple Mode with pre-selected groups', () => { - const change = new SimpleChange(null, mockIdentityGroups, false); beforeEach(async(() => { @@ -434,10 +500,9 @@ describe('GroupCloudComponent', () => { component.preSelectGroups = mockIdentityGroups; component.ngOnChanges({ 'preSelectGroups': change }); fixture.detectChanges(); - element = fixture.nativeElement; })); - it('should pre-select all preSelectGroups', () => { + it('should render all preselected groups', () => { component.mode = 'multiple'; fixture.detectChanges(); component.ngOnChanges({ 'preSelectGroups': change }); @@ -460,7 +525,7 @@ describe('GroupCloudComponent', () => { fixture.whenStable().then(() => { expect(removeGroupEmitterSpy).toHaveBeenCalledWith(groupToRemove); expect(changedGroupsEmitterSpy).toHaveBeenCalledWith([mockIdentityGroups[1], mockIdentityGroups[2], mockIdentityGroups[3], mockIdentityGroups[4]]); - expect(component.getSelectedGroups().indexOf({ + expect(component.selectedGroups.indexOf({ id: groupToRemove.id, name: groupToRemove.name, path: groupToRemove.path @@ -482,42 +547,54 @@ describe('GroupCloudComponent', () => { const change = new SimpleChange(null, component.preSelectGroups, false); component.mode = 'multiple'; component.ngOnChanges({ 'preSelectGroups': change }); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); + const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip'); - const removeIcon = fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]'); + const removeIcon = getElement('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]'); + expect(chipList.length).toBe(2); expect(component.preSelectGroups[0].readonly).toBeTruthy(); expect(component.preSelectGroups[1].readonly).toBeTruthy(); expect(removeIcon).toBeNull(); + done(); }); }); it('Should be able to remove preselected groups if readonly property set to false', (done) => { fixture.detectChanges(); + component.preSelectGroups = [ { id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: false }, { id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: false } ]; + const change = new SimpleChange(null, component.preSelectGroups, false); component.mode = 'multiple'; component.ngOnChanges({ 'preSelectGroups': change }); + const removeGroupSpy = spyOn(component.removeGroup, 'emit'); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const removeIcon = fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]'); + const removeIcon = getElement('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]'); + expect(chips.length).toBe(2); expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable'); expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable'); + removeIcon.click(); fixture.detectChanges(); + expect(removeGroupSpy).toHaveBeenCalled(); expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(1); + done(); }); }); @@ -532,8 +609,10 @@ describe('GroupCloudComponent', () => { component.ngOnChanges({ 'preSelectGroups': change }); fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const chipList = fixture.nativeElement.querySelector('mat-chip-list'); + const chipList = getElement('mat-chip-list'); + expect(chips).toBeDefined(); expect(chipList).toBeDefined(); expect(chips.length).toBe(1); @@ -545,9 +624,12 @@ describe('GroupCloudComponent', () => { component.readOnly = true; component.preSelectGroups = mockIdentityGroups; component.ngOnChanges({ 'preSelectGroups': change }); + fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const chipList = fixture.nativeElement.querySelector('mat-chip-list'); + const chipList = getElement('mat-chip-list'); + expect(chips).toBeDefined(); expect(chipList).toBeDefined(); expect(chips.length).toBe(5); @@ -608,7 +690,9 @@ describe('GroupCloudComponent', () => { component.mode = 'multiple'; component.validate = true; component.preSelectGroups = [mockIdentityGroups[0], mockIdentityGroups[1]]; - component.ngOnChanges({ 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) }); + component.ngOnChanges({ + 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) + }); }); }); diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts index 0281a71326..25b444feae 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts @@ -35,12 +35,8 @@ import { trigger, state, style, transition, animate } from '@angular/animations' import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { debounceTime } from 'rxjs/internal/operators/debounceTime'; import { distinctUntilChanged, switchMap, mergeMap, filter, tap, map, takeUntil } from 'rxjs/operators'; -import { - IdentityGroupModel, - IdentityGroupSearchParam, - IdentityGroupService, - LogService -} from '@alfresco/adf-core'; +import { IdentityGroupModel, IdentityGroupService, LogService } from '@alfresco/adf-core'; +import { ComponentSelectionMode } from '../../types'; @Component({ selector: 'adf-cloud-group', @@ -60,9 +56,6 @@ import { }) export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { - static MODE_SINGLE = 'single'; - static MODE_MULTIPLE = 'multiple'; - /** Name of the application. If specified this shows the groups who have access to the app. */ @Input() appName: string; @@ -73,7 +66,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { /** Group selection mode (single/multiple). */ @Input() - mode: string = GroupCloudComponent.MODE_SINGLE; + mode: ComponentSelectionMode = 'single'; /** Array of groups to be pre-selected. This pre-selects all groups in multi selection mode and only the first group of the array in single selection mode. */ @Input() @@ -119,18 +112,16 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { private groupInput: ElementRef; private searchGroups: IdentityGroupModel[] = []; - private searchGroupsSubject: BehaviorSubject; private onDestroy$ = new Subject(); selectedGroups: IdentityGroupModel[] = []; invalidGroups: IdentityGroupModel[] = []; - searchGroups$: Observable; + searchGroups$ = new BehaviorSubject(this.searchGroups); _subscriptAnimationState = 'enter'; clientId: string; isFocused: boolean; - currentTimeout: any; validateGroupsMessage: string; searchedValue = ''; @@ -140,18 +131,12 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { private identityGroupService: IdentityGroupService, private logService: LogService) {} - ngOnInit() { - if (this.searchGroupsSubject === undefined) { - this.searchGroupsSubject = new BehaviorSubject(this.searchGroups); - this.searchGroups$ = this.searchGroupsSubject.asObservable(); - } - + ngOnInit(): void { this.loadClientId(); this.initSearch(); } - ngOnChanges(changes: SimpleChanges) { - + ngOnChanges(changes: SimpleChanges): void { if (this.hasPreselectedGroupsChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) { if (this.hasPreSelectGroups()) { this.loadPreSelectGroups(); @@ -172,17 +157,21 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } private isAppNameChanged(change: SimpleChange): boolean { - return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0; + return change + && change.previousValue !== change.currentValue + && this.appName + && this.appName.length > 0; } - private async loadClientId() { + private async loadClientId(): Promise { this.clientId = await this.identityGroupService.getClientIdByApplicationName(this.appName).toPromise(); + if (this.clientId) { this.searchGroupsControl.enable(); } } - initSearch() { + initSearch(): void { this.searchGroupsControl.valueChanges.pipe( debounceTime(500), distinctUntilChanged(), @@ -198,26 +187,21 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { this.searchGroupsControl.markAsUntouched(); } }), - tap(() => { - this.resetSearchGroups(); - }), - switchMap((inputValue) => { - const queryParams = this.createSearchParam(inputValue); - return this.identityGroupService.findGroupsByName(queryParams); - }), + tap(() => this.resetSearchGroups()), + switchMap((name: string) => + this.identityGroupService.findGroupsByName({ name: name.trim() }) + ), mergeMap((groups) => { this.resetSearchGroups(); return groups; }), - filter((group: any) => { - return !this.isGroupAlreadySelected(group); - }), - mergeMap((group: any) => { + filter(group => !this.isGroupAlreadySelected(group)), + mergeMap(group => { if (this.appName) { return this.checkGroupHasAccess(group.id).pipe( - mergeMap((hasRole) => { - return hasRole ? of(group) : of(); - }) + mergeMap( + hasRole => hasRole ? of(group) : of() + ) ); } else if (this.hasRoles()) { return this.filterGroupsByRoles(group); @@ -226,9 +210,9 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } }), takeUntil(this.onDestroy$) - ).subscribe((searchedGroup: any) => { + ).subscribe(searchedGroup => { this.searchGroups.push(searchedGroup); - this.searchGroupsSubject.next(this.searchGroups); + this.searchGroups$.next(this.searchGroups); }); } @@ -251,22 +235,22 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { return false; } - async searchGroup(groupName: string): Promise { - return (await this.identityGroupService.findGroupsByName({ name: groupName }).toPromise())[0]; + async searchGroup(name: string): Promise { + return (await this.identityGroupService.findGroupsByName({ name }).toPromise())[0]; + } + + private getPreselectedGroups(): IdentityGroupModel[] { + if (this.isSingleMode()) { + return [this.preSelectGroups[0]]; + } else { + return this.removeDuplicatedGroups(this.preSelectGroups); + } } async validatePreselectGroups(): Promise { this.invalidGroups = []; - let preselectedGroupsToValidate: IdentityGroupModel[] = []; - - if (this.isSingleMode()) { - preselectedGroupsToValidate = [this.preSelectGroups[0]]; - } else { - preselectedGroupsToValidate = this.removeDuplicatedGroups(this.preSelectGroups); - } - - await Promise.all(preselectedGroupsToValidate.map(async (group: IdentityGroupModel) => { + for (const group of this.getPreselectedGroups()) { try { const validationResult = await this.searchGroup(group.name); if (this.isPreselectedGroupInvalid(group, validationResult)) { @@ -276,13 +260,12 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { this.invalidGroups.push(group); this.logService.error(error); } - })); + } + this.checkPreselectValidationErrors(); - this.isLoading = false; } - public checkPreselectValidationErrors() { - + checkPreselectValidationErrors(): void { this.invalidGroups = this.removeDuplicatedGroups(this.invalidGroups); if (this.invalidGroups.length > 0) { @@ -295,7 +278,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { }); } - generateInvalidGroupsMessage() { + generateInvalidGroupsMessage(): void { this.validateGroupsMessage = ''; this.invalidGroups.forEach((invalidGroup: IdentityGroupModel, index) => { @@ -307,7 +290,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { }); } - private async loadPreSelectGroups() { + private async loadPreSelectGroups(): Promise { this.selectedGroups = []; if (this.isSingleMode()) { @@ -319,6 +302,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { if (this.isValidationEnabled()) { this.isLoading = true; await this.validatePreselectGroups(); + this.isLoading = false; } } @@ -329,8 +313,9 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { map((filteredGroup: { hasRole: boolean, group: IdentityGroupModel }) => filteredGroup.group)); } - onSelect(group: IdentityGroupModel) { + onSelect(group: IdentityGroupModel): void { this.selectGroup.emit(group); + if (this.isMultipleMode()) { if (!this.isGroupAlreadySelected(group)) { this.selectedGroups.push(group); @@ -347,7 +332,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { this.resetSearchGroups(); } - onRemove(groupToRemove: IdentityGroupModel) { + onRemove(groupToRemove: IdentityGroupModel): void { this.removeGroup.emit(groupToRemove); this.removeGroupFromSelected(groupToRemove); this.changedGroups.emit(this.selectedGroups); @@ -359,18 +344,19 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } } - private removeGroupFromSelected(groupToRemove: IdentityGroupModel) { - const indexToRemove = this.selectedGroups.findIndex((selectedGroup: IdentityGroupModel) => { - return selectedGroup.id === groupToRemove.id && selectedGroup.name === groupToRemove.name; + private removeGroupFromSelected({ id, name }: IdentityGroupModel): void { + const indexToRemove = this.selectedGroups.findIndex(group => { + return group.id === id && group.name === name; }); + if (indexToRemove !== -1) { this.selectedGroups.splice(indexToRemove, 1); } } - private removeGroupFromValidation(groupToRemove: IdentityGroupModel) { - const indexToRemove = this.invalidGroups.findIndex((invalidGroup) => { - return invalidGroup.name === groupToRemove.name && invalidGroup.id === groupToRemove.id; + private removeGroupFromValidation({ id, name }: IdentityGroupModel): void { + const indexToRemove = this.invalidGroups.findIndex(group => { + return group.id === id && group.name === name; }); if (indexToRemove !== -1) { @@ -378,9 +364,9 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } } - private resetSearchGroups() { + private resetSearchGroups(): void { this.searchGroups = []; - this.searchGroupsSubject.next(this.searchGroups); + this.searchGroups$.next(this.searchGroups); } isPreselectedGroupInvalid(preselectedGroup: IdentityGroupModel, validatedGroup: IdentityGroupModel): boolean { @@ -392,7 +378,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } isSingleMode(): boolean { - return this.mode === GroupCloudComponent.MODE_SINGLE; + return this.mode === 'single'; } private isSingleSelectionReadonly(): boolean { @@ -408,7 +394,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } isMultipleMode(): boolean { - return this.mode === GroupCloudComponent.MODE_MULTIPLE; + return this.mode === 'multiple'; } getDisplayName(group: IdentityGroupModel): string { @@ -426,37 +412,39 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { return this.preSelectGroups && this.preSelectGroups.length > 0; } - private hasModeChanged(changes): boolean { - return changes && changes.mode && changes.mode.currentValue !== changes.mode.previousValue; + private hasModeChanged(changes: SimpleChanges): boolean { + return changes + && changes.mode + && changes.mode.currentValue !== changes.mode.previousValue; } - private isValidationChanged(changes): boolean { - return changes && changes.validate && changes.validate.currentValue !== changes.validate.previousValue; + private isValidationChanged(changes: SimpleChanges): boolean { + return changes + && changes.validate + && changes.validate.currentValue !== changes.validate.previousValue; } - private hasPreselectedGroupsChanged(changes): boolean { - return changes && changes.preSelectGroups && changes.preSelectGroups.currentValue !== changes.preSelectGroups.previousValue; + private hasPreselectedGroupsChanged(changes: SimpleChanges): boolean { + return changes + && changes.preSelectGroups + && changes.preSelectGroups.currentValue !== changes.preSelectGroups.previousValue; } - private hasPreselectedGroupsCleared(changes): boolean { - return changes && changes.preSelectGroups && changes.preSelectGroups.currentValue.length === 0; - } - - private createSearchParam(value: string): IdentityGroupSearchParam { - const queryParams: IdentityGroupSearchParam = { name: value.trim() }; - return queryParams; - } - - getSelectedGroups(): IdentityGroupModel[] { - return this.selectedGroups; + private hasPreselectedGroupsCleared(changes: SimpleChanges): boolean { + return changes + && changes.preSelectGroups + && changes.preSelectGroups.currentValue.length === 0; } private hasRoles(): boolean { return this.roles && this.roles.length > 0; } - private setTypingError() { - this.searchGroupsControl.setErrors({ searchTypingError: true, ...this.searchGroupsControl.errors }); + private setTypingError(): void { + this.searchGroupsControl.setErrors({ + searchTypingError: true, + ...this.searchGroupsControl.errors + }); } hasError(): boolean { @@ -487,8 +475,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { return this.searchGroupsControl.errors.minlength.requiredLength; } - ngOnDestroy() { - clearTimeout(this.currentTimeout); + ngOnDestroy(): void { this.onDestroy$.next(true); this.onDestroy$.complete(); } diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts index 761116c76e..43d67a154c 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts @@ -21,7 +21,8 @@ import { IdentityUserService, AlfrescoApiService, CoreModule, - setupTestBed + setupTestBed, + IdentityUserModel } from '@alfresco/adf-core'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { of } from 'rxjs'; @@ -49,33 +50,34 @@ describe('PeopleCloudComponent', () => { { id: mockUsers[2].id, username: mockUsers[2].username } ]; + function getElement(selector: string): T { + return fixture.nativeElement.querySelector(selector); + } + setupTestBed({ imports: [ CoreModule.forRoot(), ProcessServiceCloudTestingModule, PeopleCloudModule - ], - providers: [ - IdentityUserService ] }); beforeEach(() => { fixture = TestBed.createComponent(PeopleCloudComponent); component = fixture.componentInstance; + identityService = TestBed.get(IdentityUserService); alfrescoApiService = TestBed.get(AlfrescoApiService); - spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); - }); - it('should create PeopleCloudComponent', () => { - expect(component instanceof PeopleCloudComponent).toBe(true, 'should create PeopleCloudComponent'); + spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); }); it('should populate placeholder when title is present', async(() => { component.title = 'TITLE_KEY'; fixture.detectChanges(); - const matLabel: HTMLInputElement = fixture.nativeElement.querySelector('#adf-people-cloud-title-id'); + + const matLabel = getElement('#adf-people-cloud-title-id'); + fixture.whenStable().then(() => { fixture.detectChanges(); expect(matLabel.textContent).toEqual('TITLE_KEY'); @@ -84,7 +86,9 @@ describe('PeopleCloudComponent', () => { it('should not populate placeholder when title is not present', async(() => { fixture.detectChanges(); - const matLabel: HTMLInputElement = fixture.nativeElement.querySelector('#adf-people-cloud-title-id'); + + const matLabel = getElement('#adf-people-cloud-title-id'); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -93,7 +97,6 @@ describe('PeopleCloudComponent', () => { })); describe('Search user', () => { - beforeEach(async(() => { fixture.detectChanges(); element = fixture.nativeElement; @@ -101,13 +104,13 @@ describe('PeopleCloudComponent', () => { })); it('should list the users as dropdown options if the search term has results', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'first'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); - fixture.detectChanges(); + const input = getElement('input'); + input.focus(); + input.value = 'first'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3); @@ -178,11 +181,13 @@ describe('PeopleCloudComponent', () => { it('should hide result list if input is empty', (done) => { fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = ''; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = ''; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { expect(element.querySelector('mat-option')).toBeNull(); @@ -190,11 +195,12 @@ describe('PeopleCloudComponent', () => { }); }); - it('should selectedUser and changedUsers emit, update selected users when a user is selected', (done) => { + it('should update selected users when a user is selected', (done) => { const user = { username: 'username' }; fixture.detectChanges(); const selectEmitSpy = spyOn(component.selectUser, 'emit'); const changedUsersSpy = spyOn(component.changedUsers, 'emit'); + component.onSelect(user); fixture.detectChanges(); @@ -206,17 +212,58 @@ describe('PeopleCloudComponent', () => { }); }); + it('should replace the user in single-selection mode', () => { + component.mode = 'single'; + + const user1: IdentityUserModel = { id: '1', username: 'user1', email: 'user1@mail.com' }; + const user2: IdentityUserModel = { id: '2', username: 'user2', email: 'user2@mail.com' }; + + component.onSelect(user1); + expect(component.getSelectedUsers()).toEqual([user1]); + + component.onSelect(user2); + expect(component.getSelectedUsers()).toEqual([user2]); + }); + + it('should allow multiple users in multi-selection mode', () => { + component.mode = 'multiple'; + + const user1: IdentityUserModel = { id: '1', username: 'user1', email: 'user1@mail.com' }; + const user2: IdentityUserModel = { id: '2', username: 'user2', email: 'user2@mail.com' }; + + component.onSelect(user1); + component.onSelect(user2); + + expect(component.getSelectedUsers()).toEqual([user1, user2]); + }); + + it('should allow only unique users in multi-selection mode', () => { + component.mode = 'multiple'; + + const user1: IdentityUserModel = { id: '1', username: 'user1', email: 'user1@mail.com' }; + const user2: IdentityUserModel = { id: '2', username: 'user2', email: 'user2@mail.com' }; + + component.onSelect(user1); + component.onSelect(user2); + component.onSelect(user1); + component.onSelect(user2); + + expect(component.getSelectedUsers()).toEqual([user1, user2]); + }); + it('should show an error message if the search result empty', (done) => { findUsersByNameSpy.and.returnValue(of([])); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'ZZZ'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'ZZZ'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { - inputHTMLElement.blur(); + input.blur(); fixture.detectChanges(); const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]'); expect(errorMessage).not.toBeNull(); @@ -227,7 +274,6 @@ describe('PeopleCloudComponent', () => { }); describe('when application name defined', () => { - let checkUserHasAccessSpy: jasmine.Spy; let checkUserHasAnyClientAppRoleSpy: jasmine.Spy; @@ -235,6 +281,7 @@ describe('PeopleCloudComponent', () => { findUsersByNameSpy = spyOn(identityService, 'findUsersByName').and.returnValue(of(mockUsers)); checkUserHasAccessSpy = spyOn(identityService, 'checkUserHasClientApp').and.returnValue(of(true)); checkUserHasAnyClientAppRoleSpy = spyOn(identityService, 'checkUserHasAnyClientAppRole').and.returnValue(of(true)); + component.preSelectUsers = []; component.appName = 'mock-app-name'; fixture.detectChanges(); @@ -244,8 +291,10 @@ describe('PeopleCloudComponent', () => { it('should fetch the client ID if appName specified', async (() => { const getClientIdByApplicationNameSpy = spyOn(identityService, 'getClientIdByApplicationName').and.callThrough(); component.appName = 'mock-app-name'; + const change = new SimpleChange(null, 'mock-app-name', false); component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -254,11 +303,12 @@ describe('PeopleCloudComponent', () => { })); it('should list users who have access to the app when appName is specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -270,11 +320,13 @@ describe('PeopleCloudComponent', () => { it('should not list users who do not have access to the app when appName is specified', (done) => { checkUserHasAccessSpy.and.returnValue(of(false)); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -285,11 +337,13 @@ describe('PeopleCloudComponent', () => { it('should list users if given roles mapped with client roles', (done) => { component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1']; - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -302,11 +356,13 @@ describe('PeopleCloudComponent', () => { it('should not list users if roles are not mapping with client roles', (done) => { checkUserHasAnyClientAppRoleSpy.and.returnValue(of(false)); component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1']; - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -317,11 +373,12 @@ describe('PeopleCloudComponent', () => { }); it('should not call client role mapping sevice if roles not specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -331,11 +388,12 @@ describe('PeopleCloudComponent', () => { }); it('should validate access to the app when appName is specified', (done) => { - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -347,11 +405,13 @@ describe('PeopleCloudComponent', () => { it('should not validate access to the app when appName is not specified', (done) => { component.appName = ''; fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -364,15 +424,18 @@ describe('PeopleCloudComponent', () => { checkUserHasAccessSpy.and.returnValue(of(false)); findUsersByNameSpy.and.returnValue(of([])); fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'ZZZ'; - inputHTMLElement.dispatchEvent(new Event('keyup')); - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'ZZZ'; + input.dispatchEvent(new Event('keyup')); + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { - inputHTMLElement.blur(); + input.blur(); fixture.detectChanges(); + const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]'); expect(errorMessage).not.toBeNull(); expect(errorMessage.textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND'); @@ -402,7 +465,6 @@ describe('PeopleCloudComponent', () => { }); describe('When roles defined', () => { - let checkUserHasRoleSpy: jasmine.Spy; beforeEach(async(() => { @@ -415,10 +477,12 @@ describe('PeopleCloudComponent', () => { it('should filter users if users has any specified role', (done) => { fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -431,10 +495,12 @@ describe('PeopleCloudComponent', () => { it('should not filter users if user does not have any specified role', (done) => { fixture.detectChanges(); checkUserHasRoleSpy.and.returnValue(of(false)); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -447,10 +513,12 @@ describe('PeopleCloudComponent', () => { it('should not call checkUserHasRole service when roles are not specified', (done) => { component.roles = []; fixture.detectChanges(); - const inputHTMLElement: HTMLInputElement = element.querySelector('input'); - inputHTMLElement.focus(); - inputHTMLElement.value = 'M'; - inputHTMLElement.dispatchEvent(new Event('input')); + + const input = getElement('input'); + input.focus(); + input.value = 'M'; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -461,13 +529,13 @@ describe('PeopleCloudComponent', () => { }); describe('Single Mode with Pre-selected users', () => { - const changes = new SimpleChange(null, mockPreselectedUsers, false); beforeEach(async(() => { component.mode = 'single'; component.preSelectUsers = mockPreselectedUsers; component.ngOnChanges({ 'preSelectUsers': changes }); + fixture.detectChanges(); element = fixture.nativeElement; })); @@ -484,53 +552,85 @@ describe('PeopleCloudComponent', () => { }); describe('Multiple Mode with Pre-selected Users', () => { + beforeEach(() => { + component.mode = 'multiple'; + }); + + it('should render multiple preselected users', (done) => { + fixture.detectChanges(); + + const changes = new SimpleChange(null, mockPreselectedUsers, false); + + component.preSelectUsers = mockPreselectedUsers; + component.ngOnChanges({ 'preSelectUsers': changes }); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); + expect(chips.length).toEqual(2); + expect(chips[0].attributes['data-automation-id']).toEqual(`adf-people-cloud-chip-${mockPreselectedUsers[0].username}`); + expect(chips[1].attributes['data-automation-id']).toEqual(`adf-people-cloud-chip-${mockPreselectedUsers[1].username}`); + + done(); + }); + }); it('Should not show remove icon for pre-selected users if readonly property set to true', (done) => { fixture.detectChanges(); + component.preSelectUsers = [ { id: mockUsers[0].id, username: mockUsers[0].username, readonly: true }, { id: mockUsers[1].id, username: mockUsers[1].username, readonly: true } ]; - const change = new SimpleChange(null, component.preSelectUsers, false); - component.mode = 'multiple'; - component.ngOnChanges({ 'preSelectUsers': change }); - fixture.detectChanges(); + const change = new SimpleChange(null, component.preSelectUsers, false); + component.ngOnChanges({ 'preSelectUsers': change }); + + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); + const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip'); - const removeIcon = fixture.nativeElement.querySelector('[data-automation-id="adf-people-cloud-chip-remove-icon-first-name-1 last-name-1"]'); + const removeIcon = getElement('[data-automation-id="adf-people-cloud-chip-remove-icon-first-name-1 last-name-1"]'); + expect(chipList.length).toBe(2); expect(component.preSelectUsers[0].readonly).toBeTruthy(); expect(component.preSelectUsers[1].readonly).toBeTruthy(); expect(removeIcon).toBeNull(); + done(); }); }); it('Should be able to remove preselected users if readonly property set to false', (done) => { - fixture.detectChanges(); component.preSelectUsers = [ { id: mockUsers[0].id, username: mockUsers[0].username, readonly: false }, { id: mockUsers[1].id, username: mockUsers[1].username, readonly: false } ]; - const change = new SimpleChange(null, component.preSelectUsers, false); - component.mode = 'multiple'; - component.ngOnChanges({ 'preSelectUsers': change }); - const removeUserSpy = spyOn(component.removeUser, 'emit'); - fixture.detectChanges(); + const change = new SimpleChange(null, component.preSelectUsers, false); + component.ngOnChanges({ 'preSelectUsers': change }); + + const removeUserSpy = spyOn(component.removeUser, 'emit'); + + fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const removeIcon = fixture.nativeElement.querySelector(`[data-automation-id="adf-people-cloud-chip-remove-icon-${mockPreselectedUsers[0].username}"]`); + const removeIcon = getElement(`[data-automation-id="adf-people-cloud-chip-remove-icon-${mockPreselectedUsers[0].username}"]`); + expect(chips.length).toBe(2); expect(component.preSelectUsers[0].readonly).toBe(false, 'Removable'); expect(component.preSelectUsers[1].readonly).toBe(false, 'Removable'); + removeIcon.click(); fixture.detectChanges(); + expect(removeUserSpy).toHaveBeenCalled(); expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(1); + done(); }); }); @@ -545,8 +645,10 @@ describe('PeopleCloudComponent', () => { component.ngOnChanges({ 'preSelectUsers': change }); fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const chipList = fixture.nativeElement.querySelector('mat-chip-list'); + const chipList = getElement('mat-chip-list'); + expect(chips).toBeDefined(); expect(chipList).toBeDefined(); expect(chips.length).toBe(1); @@ -558,9 +660,12 @@ describe('PeopleCloudComponent', () => { component.readOnly = true; component.preSelectUsers = mockPreselectedUsers; component.ngOnChanges({ 'preSelectUsers': change }); + fixture.detectChanges(); + const chips = fixture.debugElement.queryAll(By.css('mat-chip')); - const chipList = fixture.nativeElement.querySelector('mat-chip-list'); + const chipList = getElement('mat-chip-list'); + expect(chips).toBeDefined(); expect(chipList).toBeDefined(); expect(chips.length).toBe(2); @@ -588,7 +693,26 @@ describe('PeopleCloudComponent', () => { component.mode = 'single'; component.validate = true; component.preSelectUsers = [mockPreselectedUsers[0], mockPreselectedUsers[1]]; - component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) }); + component.ngOnChanges({ + 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) + }); + }); + + it('should skip warnings if validation disabled', () => { + spyOn(identityService, 'findUserById').and.returnValue(Promise.resolve([])); + spyOn(component, 'compare').and.returnValue(false); + + let warnings = 0; + + component.warning.subscribe(() => warnings++); + component.mode = 'single'; + component.validate = false; + component.preSelectUsers = [mockPreselectedUsers[0], mockPreselectedUsers[1]]; + component.ngOnChanges({ + 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) + }); + + expect(warnings).toBe(0); }); it('should check validation for all the users and emit warning - multiple mode', (done) => { @@ -604,7 +728,8 @@ describe('PeopleCloudComponent', () => { { id: mockPreselectedUsers[1].id, username: mockPreselectedUsers[1].username - }] + } + ] }; component.warning.subscribe(warning => { @@ -615,7 +740,9 @@ describe('PeopleCloudComponent', () => { component.mode = 'multiple'; component.validate = true; component.preSelectUsers = [mockPreselectedUsers[0], mockPreselectedUsers[1]]; - component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) }); + component.ngOnChanges({ + 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) + }); }); }); diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts index e97dbdf4c6..8912830194 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts @@ -27,7 +27,7 @@ import { OnChanges, OnDestroy, ChangeDetectionStrategy, - ViewChild, ElementRef + ViewChild, ElementRef, SimpleChange } from '@angular/core'; import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators'; @@ -38,6 +38,7 @@ import { LogService } from '@alfresco/adf-core'; import { trigger, state, style, transition, animate } from '@angular/animations'; +import { ComponentSelectionMode } from '../../types'; @Component({ selector: 'adf-cloud-people', @@ -59,16 +60,13 @@ import { trigger, state, style, transition, animate } from '@angular/animations' export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { - static MODE_SINGLE = 'single'; - static MODE_MULTIPLE = 'multiple'; - /** Name of the application. If specified, this shows the users who have access to the app. */ @Input() appName: string; /** User selection mode (single/multiple). */ @Input() - mode: string = PeopleCloudComponent.MODE_SINGLE; + mode: ComponentSelectionMode = 'single'; /** Role names of the users to be listed. */ @Input() @@ -79,7 +77,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { * Otherwise, no check will be done. */ @Input() - validate: Boolean = false; + validate: boolean = false; /** Show the info in readonly mode */ @@ -96,7 +94,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { /** FormControl to search the user */ @Input() - searchUserCtrl: FormControl = new FormControl({ value: '', disabled: false }); + searchUserCtrl = new FormControl({ value: '', disabled: false }); /** Placeholder translation key */ @@ -123,18 +121,16 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { private userInput: ElementRef; private _searchUsers: IdentityUserModel[] = []; - private searchUsersSubject: BehaviorSubject; private onDestroy$ = new Subject(); selectedUsers: IdentityUserModel[] = []; invalidUsers: IdentityUserModel[] = []; - searchUsers$: Observable; + searchUsers$ = new BehaviorSubject(this._searchUsers); _subscriptAnimationState: string = 'enter'; clientId: string; isFocused: boolean; - currentTimeout: any; validateUsersMessage: string; searchedValue = ''; @@ -144,19 +140,17 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { private identityUserService: IdentityUserService, private logService: LogService) {} - ngOnInit() { - if (this.searchUsersSubject === undefined) { - this.searchUsersSubject = new BehaviorSubject(this._searchUsers); - this.searchUsers$ = this.searchUsersSubject.asObservable(); - } - + ngOnInit(): void { this.loadClientId(); this.initSearch(); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges): void { - if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) { + if (this.valueChanged(changes.preSelectUsers) + || this.valueChanged(changes.mode) + || this.valueChanged(changes.validate) + ) { if (this.hasPreSelectUsers()) { this.loadPreSelectUsers(); } else if (this.hasPreselectedUsersCleared(changes)) { @@ -175,14 +169,14 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { } } - private async loadClientId() { + private async loadClientId(): Promise { this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise(); if (this.clientId) { this.searchUserCtrl.enable(); } } - private initSearch() { + private initSearch(): void { this.searchUserCtrl.valueChanges.pipe( debounceTime(500), distinctUntilChanged(), @@ -207,15 +201,13 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { this.resetSearchUsers(); return users; }), - filter((user: any) => { - return !this.isUserAlreadySelected(user); - }), - mergeMap((user: any) => { + filter(user => !this.isUserAlreadySelected(user)), + mergeMap(user => { if (this.appName) { return this.checkUserHasAccess(user.id).pipe( - mergeMap((hasRole) => { - return hasRole ? of(user) : of(); - }) + mergeMap( + hasRole => hasRole ? of(user) : of() + ) ); } else if (this.hasRoles()) { return this.filterUsersByRoles(user); @@ -224,19 +216,18 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { } }), takeUntil(this.onDestroy$) - ).subscribe((user: any) => { + ).subscribe(user => { this._searchUsers.push(user); - this.searchUsersSubject.next(this._searchUsers); + this.searchUsers$.next(this._searchUsers); }); } - ngOnDestroy() { - clearTimeout(this.currentTimeout); + ngOnDestroy(): void { this.onDestroy$.next(true); this.onDestroy$.complete(); } - private isAppNameChanged(change): boolean { + private isAppNameChanged(change: SimpleChange): boolean { return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0; } @@ -274,7 +265,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { return false; } - private async loadPreSelectUsers() { + private async loadPreSelectUsers(): Promise { this.selectedUsers = []; if (this.isSingleMode()) { @@ -286,6 +277,15 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { if (this.isValidationEnabled()) { this.isLoading = true; await this.validatePreselectUsers(); + this.isLoading = false; + } + } + + private getPreselectedUsers(): IdentityUserModel[] { + if (this.isSingleMode()) { + return [this.preSelectUsers[0]]; + } else { + return this.removeDuplicatedUsers(this.preSelectUsers); } } @@ -293,31 +293,24 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { this.invalidUsers = []; const validUsers: IdentityUserModel[] = []; - let preselectedUsersToValidate: IdentityUserModel[] = []; - - if (this.isSingleMode()) { - preselectedUsersToValidate = [this.preSelectUsers[0]]; - } else { - preselectedUsersToValidate = this.removeDuplicatedUsers(this.preSelectUsers); - } - - await Promise.all(preselectedUsersToValidate.map(async (preselectedUser: IdentityUserModel) => { + for (const user of this.getPreselectedUsers()) { try { - const userValidationResult: IdentityUserModel = await this.searchUser(preselectedUser); - if (this.compare(preselectedUser, userValidationResult)) { - userValidationResult.readonly = preselectedUser.readonly; - validUsers.push(userValidationResult); + const validationResult = await this.searchUser(user); + + if (this.compare(user, validationResult)) { + validationResult.readonly = user.readonly; + validUsers.push(validationResult); } else { - this.invalidUsers.push(preselectedUser); + this.invalidUsers.push(user); } } catch (error) { - this.invalidUsers.push(preselectedUser); + this.invalidUsers.push(user); this.logService.error(error); } - })); + } + this.checkPreselectValidationErrors(); this.selectedUsers = validUsers.concat(this.invalidUsers); - this.isLoading = false; } compare(preselectedUser: IdentityUserModel, identityUser: IdentityUserModel): boolean { @@ -332,16 +325,20 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { return false; } - async searchUser(user: IdentityUserModel) { - let key: string = ''; - + private getSearchKey(user: IdentityUserModel): string { if (user.id) { - key = 'id'; + return 'id'; } else if (user.email) { - key = 'email'; + return 'email'; } else if (user.username) { - key = 'username'; + return 'username'; + } else { + return null; } + } + + async searchUser(user: IdentityUserModel): Promise { + const key = this.getSearchKey(user); switch (key) { case 'id': @@ -351,19 +348,18 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { case 'email': return (await this.identityUserService.findUserByEmail(user[key]).toPromise())[0]; default: - return of([]); + return null; } } removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] { return users.filter((user, index, self) => - index === self.findIndex((auxUser) => { - return user.id === auxUser.id && user.username === auxUser.username && user.email === auxUser.email; - })); + index === self.findIndex(auxUser => + user.id === auxUser.id && user.username === auxUser.username && user.email === auxUser.email + )); } - public checkPreselectValidationErrors() { - + checkPreselectValidationErrors(): void { this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers); if (this.invalidUsers.length > 0) { @@ -376,8 +372,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { }); } - onSelect(user: IdentityUserModel) { + onSelect(user: IdentityUserModel): void { this.selectUser.emit(user); + if (this.isMultipleMode()) { if (!this.isUserAlreadySelected(user)) { this.selectedUsers.push(user); @@ -394,7 +391,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { this.resetSearchUsers(); } - onRemove(userToRemove: IdentityUserModel) { + onRemove(userToRemove: IdentityUserModel): void { this.removeUser.emit(userToRemove); this.removeUserFromSelected(userToRemove); this.changedUsers.emit(this.selectedUsers); @@ -406,18 +403,23 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { } } - private removeUserFromSelected(userToRemove: IdentityUserModel) { - const indexToRemove = this.selectedUsers.findIndex((selectedUser: IdentityUserModel) => { - return selectedUser.id === userToRemove.id && selectedUser.username === userToRemove.username && selectedUser.email === userToRemove.email; + private removeUserFromSelected({ id, username, email }: IdentityUserModel): void { + const indexToRemove = this.selectedUsers.findIndex(user => { + return user.id === id + && user.username === username + && user.email === email; }); + if (indexToRemove !== -1) { this.selectedUsers.splice(indexToRemove, 1); } } - private removeUserFromValidation(userToRemove: IdentityUserModel) { - const indexToRemove = this.invalidUsers.findIndex((invalidUser) => { - return invalidUser.username === userToRemove.username && invalidUser.id === userToRemove.id && invalidUser.email === userToRemove.email; + private removeUserFromValidation({ id, username, email }: IdentityUserModel): void { + const indexToRemove = this.invalidUsers.findIndex(user => { + return user.id === id + && user.username === username + && user.email === email; }); if (indexToRemove !== -1) { @@ -425,10 +427,10 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { } } - generateInvalidUsersMessage() { + generateInvalidUsersMessage(): void { this.validateUsersMessage = ''; - this.invalidUsers.forEach((invalidUser: IdentityUserModel, index) => { + this.invalidUsers.forEach((invalidUser, index) => { if (index === this.invalidUsers.length - 1) { this.validateUsersMessage += `${invalidUser.username} `; } else { @@ -437,12 +439,16 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { }); } - setTypingError() { - this.searchUserCtrl.setErrors({ searchTypingError: true, ...this.searchUserCtrl.errors }); + setTypingError(): void { + this.searchUserCtrl.setErrors({ + searchTypingError: true, + ...this.searchUserCtrl.errors + }); } hasPreselectError(): boolean { - return this.invalidUsers && this.invalidUsers.length > 0; + return this.invalidUsers + && this.invalidUsers.length > 0; } getDisplayName(user): string { @@ -450,40 +456,39 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy { } isMultipleMode(): boolean { - return this.mode === PeopleCloudComponent.MODE_MULTIPLE; + return this.mode === 'multiple'; } isSingleMode(): boolean { - return this.mode === PeopleCloudComponent.MODE_SINGLE; + return this.mode === 'single'; } private isSingleSelectionReadonly(): boolean { - return this.isSingleMode() && this.selectedUsers.length === 1 && this.selectedUsers[0].readonly === true; + return this.isSingleMode() + && this.selectedUsers.length === 1 + && this.selectedUsers[0].readonly === true; } private hasPreSelectUsers(): boolean { - return this.preSelectUsers && this.preSelectUsers.length > 0; + return this.preSelectUsers + && this.preSelectUsers.length > 0; } - private hasModeChanged(changes): boolean { - return changes && changes.mode && changes.mode.currentValue !== changes.mode.previousValue; + private valueChanged(change: SimpleChange): boolean { + return change + && change.currentValue !== change.previousValue; } - private isValidationChanged(changes): boolean { - return changes && changes.validate && changes.validate.currentValue !== changes.validate.previousValue; + private hasPreselectedUsersCleared(changes: SimpleChanges): boolean { + return changes + && changes.preSelectUsers + && changes.preSelectUsers.currentValue + && changes.preSelectUsers.currentValue.length === 0; } - private hasPreselectedUsersChanged(changes): boolean { - return changes && changes.preSelectUsers && changes.preSelectUsers.currentValue !== changes.preSelectUsers.previousValue; - } - - private hasPreselectedUsersCleared(changes): boolean { - return changes && changes.preSelectUsers && changes.preSelectUsers.currentValue && changes.preSelectUsers.currentValue.length === 0; - } - - private resetSearchUsers() { + private resetSearchUsers(): void { this._searchUsers = []; - this.searchUsersSubject.next(this._searchUsers); + this.searchUsers$.next(this._searchUsers); } getSelectedUsers(): IdentityUserModel[] { diff --git a/lib/process-services-cloud/src/lib/types.ts b/lib/process-services-cloud/src/lib/types.ts new file mode 100644 index 0000000000..e3418a6d71 --- /dev/null +++ b/lib/process-services-cloud/src/lib/types.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type ComponentSelectionMode = 'single' | 'multiple'; diff --git a/lib/process-services-cloud/src/public-api.ts b/lib/process-services-cloud/src/public-api.ts index 6eb702599e..1bcf8a5abe 100644 --- a/lib/process-services-cloud/src/public-api.ts +++ b/lib/process-services-cloud/src/public-api.ts @@ -23,3 +23,4 @@ export * from './lib/group/public-api'; export * from './lib/people/public-api'; export * from './lib/form/public-api'; export * from './lib/services/public-api'; +export * from './lib/types';