[ADF-5065] people and group picker tests, clean code (#5442)

* cleanup component types

* more readable tests

* clean code

* C291998: render preselected people

* clean code

* extra tests and clean code

* fix test after rebase
This commit is contained in:
Denys Vuika
2020-02-03 15:10:17 +00:00
committed by GitHub
parent d9e0847b18
commit bd3957dcee
9 changed files with 666 additions and 459 deletions

View File

@@ -6,9 +6,9 @@
<mat-card-content>
<br>
<mat-radio-group (change)="onChangePeopleMode($event)">
<mat-radio-button checked="true" class="app-people-single-mode" data-automation-id="app-people-single-mode" value="{{ peopleSingleMode }}">{{
<mat-radio-button checked="true" class="app-people-single-mode" data-automation-id="app-people-single-mode" value="single">{{
'PEOPLE_GROUPS_CLOUD.SINGLE' | translate }}</mat-radio-button>
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-people-multiple-mode" value="{{ peopleMultipleMode }}">{{
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-people-multiple-mode" value="multiple">{{
'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}</mat-radio-button>
</mat-radio-group>
<div class="app-people-control-options">
@@ -20,15 +20,15 @@
</mat-radio-group>
<mat-form-field *ngIf="!isPeopleAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput (input)="setPeopleRoles($event)" data-automation-id="app-people-roles-input" />
<input matInput (input)="setPeopleRoles($event.target?.value)" data-automation-id="app-people-roles-input" />
</mat-form-field>
<mat-form-field *ngIf="isPeopleAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }}</mat-label>
<input matInput (input)="setPeopleAppName($event)" data-automation-id="app-people-app-input" />
<input matInput (input)="setPeopleAppName($event.target?.value)" data-automation-id="app-people-app-input" />
</mat-form-field>
<mat-form-field class="app-preselect-value-full">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.PRESELECTED_VALUE' | translate }} {{ DEFAULT_PEOPLE_PLACEHOLDER }}</mat-label>
<input matInput (input)="setPeoplePreselectValue($event)" data-automation-id="app-people-preselect-input" />
<input matInput (input)="setPeoplePreselectValue($event.target?.value)" data-automation-id="app-people-preselect-input" />
</mat-form-field>
<mat-checkbox class="app-preselect-value" (change)="onChangePeopleValidation($event)">{{
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox>
@@ -47,7 +47,7 @@
(warning)="onUsersWarning($event)"></adf-cloud-people>
</div>
<div class="app-people-list" *ngIf="canShowPeopleList()">
<div class="app-people-list" *ngIf="peopleMode === 'multiple'">
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_USERS' | translate }}</h4>
<mat-list role="list">
<mat-list-item *ngFor="let item of preSelectUsers" role="listitem">
@@ -77,9 +77,9 @@
<mat-card-content>
<br>
<mat-radio-group (change)="onChangeGroupsMode($event)">
<mat-radio-button checked="true" class="app-people-single-mode" data-automation-id="app-group-single-mode" value="{{ groupSingleMode }}">{{
<mat-radio-button checked="true" class="app-people-single-mode" data-automation-id="app-group-single-mode" value="single">{{
'PEOPLE_GROUPS_CLOUD.SINGLE' | translate }}</mat-radio-button>
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-group-multiple-mode" value="{{ groupMultipleMode }}">{{
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-group-multiple-mode" value="multiple">{{
'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}</mat-radio-button>
</mat-radio-group>
<div class="app-groups-control-options">
@@ -91,20 +91,31 @@
</mat-radio-group>
<mat-form-field *ngIf="!isGroupAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput (input)="setGroupRoles($event)" data-automation-id="app-group-roles-input"/>
<input matInput
(input)="setGroupRoles($event.target?.value)"
data-automation-id="app-group-roles-input"/>
</mat-form-field>
<mat-form-field *ngIf="isGroupAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }}</mat-label>
<input matInput (input)="setGroupAppName($event)" data-automation-id="app-group-app-input"/>
<input matInput
(input)="setGroupAppName($event.target?.value)"
data-automation-id="app-group-app-input"/>
</mat-form-field>
<mat-form-field class="app-preselect-value-full">
<mat-label>Preselect: {{ DEFAULT_GROUP_PLACEHOLDER }}</mat-label>
<input matInput (input)="setGroupsPreselectValue($event)" data-automation-id="app-group-preselect-input" />
<input matInput
(input)="setGroupsPreselectValue($event.target?.value)"
data-automation-id="app-group-preselect-input" />
</mat-form-field>
<mat-checkbox class="app-preselect-value" (change)="onChangeGroupValidation($event)">{{
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox>
<mat-checkbox data-automation-id="app-group-readonly" value="{{ groupReadonly }}" (change)="onChangeGroupReadonly($event)">{{
'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }}</mat-checkbox>
<mat-checkbox class="app-preselect-value" (change)="onChangeGroupValidation($event)">
{{ 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}
</mat-checkbox>
<mat-checkbox
data-automation-id="app-group-readonly"
value="{{ groupReadonly }}"
(change)="onChangeGroupReadonly($event)">
{{ 'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }}
</mat-checkbox>
</div>
<div>
<adf-cloud-group
@@ -117,7 +128,7 @@
(warning)="onGroupsWarning($event)"></adf-cloud-group>
</div>
<div class="app-group-list" *ngIf="canShowGroupList()">
<div class="app-group-list" *ngIf="groupMode === 'multiple'">
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_GROUPS' | translate }}</h4>
<mat-list role="list">
<mat-list-item *ngFor="let item of preSelectGroup" role="listitem">

View File

@@ -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<T = any>(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;
}
}

View File

@@ -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}`));

View File

@@ -45,13 +45,15 @@ describe('GroupCloudComponent', () => {
}
};
function getElement<T = HTMLElement>(selector: string): T {
return <T> 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 = <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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'Mock';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = '';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'ZZZ';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'ZZZ';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <any> 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 = <any> 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 = <HTMLElement> 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 = <HTMLElement> 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 = <any> 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 = <any> [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)
});
});
});

View File

@@ -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<HTMLInputElement>;
private searchGroups: IdentityGroupModel[] = [];
private searchGroupsSubject: BehaviorSubject<IdentityGroupModel[]>;
private onDestroy$ = new Subject<boolean>();
selectedGroups: IdentityGroupModel[] = [];
invalidGroups: IdentityGroupModel[] = [];
searchGroups$: Observable<IdentityGroupModel[]>;
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(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<IdentityGroupModel[]>(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<void> {
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<IdentityGroupModel> {
return (await this.identityGroupService.findGroupsByName({ name: groupName }).toPromise())[0];
async searchGroup(name: string): Promise<IdentityGroupModel> {
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<any> {
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() {
this.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<void> {
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();
}

View File

@@ -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<T = HTMLElement>(selector: string): T {
return <T> 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 = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
const matLabel = getElement<HTMLInputElement>('#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 = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
const matLabel = getElement<HTMLInputElement>('#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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'first';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = '';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'ZZZ';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'ZZZ';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'M';
inputHTMLElement.dispatchEvent(new Event('input'));
const input = getElement<HTMLInputElement>('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 = <any> 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 = <any> 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 = <HTMLElement> 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 = <HTMLElement> 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 = <any> 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 = <any> [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 = <any> [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 = <any> [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)
});
});
});

View File

@@ -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<HTMLInputElement>;
private _searchUsers: IdentityUserModel[] = [];
private searchUsersSubject: BehaviorSubject<IdentityUserModel[]>;
private onDestroy$ = new Subject<boolean>();
selectedUsers: IdentityUserModel[] = [];
invalidUsers: IdentityUserModel[] = [];
searchUsers$: Observable<IdentityUserModel[]>;
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(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<IdentityUserModel[]>(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<void> {
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<void> {
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<IdentityUserModel> {
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[] {

View File

@@ -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';

View File

@@ -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';