[AAE-1372] Refactor People/Group cloud component (#5355)

* [AAE-1372] Fix read-only preselected users can be deleted

* [AAE-1372] Fix people/group cloud component readonly mode

* [AAE-1372] Refactor People/Group Cloud components

* [AAE-1372] Refactor People/Group Cloud components

* [AAE-1372] Clear invalid user in single mode after replacing with a valid user

* [AAE-1372] Add progress bar while validation loading. When user gets removed remove from validation

* [AAE-1372] Fix lint errors

* [AAE-1372] Fix single selection e2e

* [AAE-1372] Fix unit tests - people/group cloud components

* [AAE-1372] Fix e2e, set People/Group formControls invalid when has preselect errors

* [AAE-1372] Fix invalid form control bug
This commit is contained in:
arditdomi 2020-01-21 13:37:57 +00:00 committed by Maurizio Vitale
parent 91abe87ccc
commit 3c3aa7599a
17 changed files with 1005 additions and 987 deletions

View File

@ -309,10 +309,11 @@
"APP_FILTER_MODE": "Filter by application name", "APP_FILTER_MODE": "Filter by application name",
"ROLE_FILTER_MODE": "Filter by role", "ROLE_FILTER_MODE": "Filter by role",
"PRESELECT_VALIDATION": "Preselect validation", "PRESELECT_VALIDATION": "Preselect validation",
"ALL_SELECTED_USERS": "All Selected Users", "ALL_PRESELECTED_USERS": "All Pre-selected Users",
"ALL_SELECTED_GROUPS": "All Selected Groups", "ALL_PRESELECTED_GROUPS": "All Pre-selected Groups",
"INVALID_USERS": "Invalid Users", "INVALID_USERS": "Invalid Users",
"INVALID_GROUPS": "Invalid Groups" "INVALID_GROUPS": "Invalid Groups",
"READONLY_MODE": "Readonly Mode"
}, },
"SETTINGS_CLOUD": { "SETTINGS_CLOUD": {
"MULTISELECTION": "Multiselection", "MULTISELECTION": "Multiselection",

View File

@ -32,21 +32,23 @@
</mat-form-field> </mat-form-field>
<mat-checkbox class="app-preselect-value" (change)="onChangePeopleValidation($event)">{{ <mat-checkbox class="app-preselect-value" (change)="onChangePeopleValidation($event)">{{
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox> 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox>
<mat-checkbox data-automation-id="app-people-readonly" value="{{ peopleReadonly }}" (change)="onChangePeopleReadonly($event)">{{
'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }}</mat-checkbox>
</div> </div>
<div> <div>
<adf-cloud-people <adf-cloud-people
[preSelectUsers]="preSelectUsers" [preSelectUsers]="preSelectUsers"
[readOnly]="peopleReadonly"
[validate]="peoplePreselectValidation" [validate]="peoplePreselectValidation"
[appName]="peopleAppName" [appName]="peopleAppName"
[roles]="peopleRoles" [roles]="peopleRoles"
[title]="'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'" [title]="'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'"
[mode]="peopleMode" [mode]="peopleMode"
(selectUser)="onSelectUser($event)"
(warning)="onUsersWarning($event)"></adf-cloud-people> (warning)="onUsersWarning($event)"></adf-cloud-people>
</div> </div>
<div class="app-people-list" *ngIf="canShowPeopleList()"> <div class="app-people-list" *ngIf="canShowPeopleList()">
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_USERS' | translate }}</h4> <h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_USERS' | translate }}</h4>
<mat-list role="list"> <mat-list role="list">
<mat-list-item *ngFor="let item of preSelectUsers" role="listitem"> <mat-list-item *ngFor="let item of preSelectUsers" role="listitem">
<mat-icon mat-list-icon>person</mat-icon> <mat-icon mat-list-icon>person</mat-icon>
@ -99,23 +101,24 @@
<mat-label>Preselect: {{ DEFAULT_GROUP_PLACEHOLDER }}</mat-label> <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)" data-automation-id="app-group-preselect-input" />
</mat-form-field> </mat-form-field>
<mat-checkbox class="app-group-preselect-validation" (change)="onChangeGroupValidation($event)">{{ <mat-checkbox class="app-preselect-value" (change)="onChangeGroupValidation($event)">{{
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox> '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>
<div> <div>
<adf-cloud-group <adf-cloud-group
[mode]="groupMode" [mode]="groupMode"
[readOnly]="groupReadonly"
[validate]="groupPreselectValidation" [validate]="groupPreselectValidation"
[roles]="groupRoles" [roles]="groupRoles"
[appName]="groupAppName" [appName]="groupAppName"
[preSelectGroups]="preSelectGroup" [preSelectGroups]="preSelectGroup"
(selectGroup)="onSelectGroup($event)"
(removeGroup)="onRemoveGroup($event)"
(warning)="onGroupsWarning($event)"></adf-cloud-group> (warning)="onGroupsWarning($event)"></adf-cloud-group>
</div> </div>
<div class="app-group-list" *ngIf="canShowGroupList()"> <div class="app-group-list" *ngIf="canShowGroupList()">
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_GROUPS' | translate }}</h4> <h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_GROUPS' | translate }}</h4>
<mat-list role="list"> <mat-list role="list">
<mat-list-item *ngFor="let item of preSelectGroup" role="listitem"> <mat-list-item *ngFor="let item of preSelectGroup" role="listitem">
<mat-icon mat-list-icon>group</mat-icon> <mat-icon mat-list-icon>group</mat-icon>

View File

@ -34,17 +34,18 @@ export class PeopleGroupCloudDemoComponent {
peopleMode: string = PeopleCloudComponent.MODE_SINGLE; peopleMode: string = PeopleCloudComponent.MODE_SINGLE;
preSelectUsers: IdentityUserModel[] = []; preSelectUsers: IdentityUserModel[] = [];
invalidUsers: IdentityGroupModel[] = []; invalidUsers: IdentityUserModel[] = [];
peopleRoles: string[] = []; peopleRoles: string[] = [];
peopleAppName: string; peopleAppName: string;
peopleFilterMode: string = this.DEFAULT_FILTER_MODE; peopleFilterMode: string = this.DEFAULT_FILTER_MODE;
peoplePreselectValidation: Boolean = false; peoplePreselectValidation: Boolean = false;
groupPreselectValidation = false; groupPreselectValidation = false;
peopleReadonly = false;
groupReadonly = false;
groupMode: string = GroupCloudComponent.MODE_SINGLE; groupMode: string = GroupCloudComponent.MODE_SINGLE;
preSelectGroup: IdentityGroupModel[] = []; preSelectGroup: IdentityGroupModel[] = [];
invalidGroups: IdentityGroupModel[] = []; invalidGroups: IdentityGroupModel[] = [];
selectedGroupList: IdentityGroupModel[] = [];
groupRoles: string[]; groupRoles: string[];
groupAppName: string; groupAppName: string;
groupFilterMode: string = this.DEFAULT_FILTER_MODE; groupFilterMode: string = this.DEFAULT_FILTER_MODE;
@ -75,12 +76,18 @@ export class PeopleGroupCloudDemoComponent {
onChangePeopleMode(event: MatRadioChange) { onChangePeopleMode(event: MatRadioChange) {
this.peopleMode = event.value; this.peopleMode = event.value;
this.preSelectUsers = [...this.preSelectUsers]; }
onChangePeopleReadonly(event: MatCheckboxChange) {
this.peopleReadonly = event.checked;
}
onChangeGroupReadonly(event: MatCheckboxChange) {
this.groupReadonly = event.checked;
} }
onChangeGroupsMode(event: MatRadioChange) { onChangeGroupsMode(event: MatRadioChange) {
this.groupMode = event.value; this.groupMode = event.value;
this.preSelectGroup = [...this.preSelectGroup];
} }
onChangePeopleFilterMode(event: MatRadioChange) { onChangePeopleFilterMode(event: MatRadioChange) {
@ -119,18 +126,10 @@ export class PeopleGroupCloudDemoComponent {
onChangePeopleValidation(event: MatCheckboxChange) { onChangePeopleValidation(event: MatCheckboxChange) {
this.peoplePreselectValidation = event.checked; this.peoplePreselectValidation = event.checked;
this.preSelectUsers = [...this.preSelectUsers];
if (!this.peoplePreselectValidation) {
this.invalidUsers = [];
}
} }
onChangeGroupValidation(event: MatCheckboxChange) { onChangeGroupValidation(event: MatCheckboxChange) {
this.groupPreselectValidation = event.checked; this.groupPreselectValidation = event.checked;
this.preSelectGroup = [...this.preSelectGroup];
if (!this.groupPreselectValidation) {
this.invalidGroups = [];
}
} }
onGroupsWarning(warning: any) { onGroupsWarning(warning: any) {
@ -167,22 +166,6 @@ export class PeopleGroupCloudDemoComponent {
return this.groupMode === GroupCloudComponent.MODE_MULTIPLE; return this.groupMode === GroupCloudComponent.MODE_MULTIPLE;
} }
onRemoveGroup(group: IdentityGroupModel) {
this.preSelectGroup = this.preSelectGroup.filter((value: any) => value.id !== group.id);
}
onSelectUser(user: IdentityUserModel) {
if (this.peopleMode === PeopleCloudComponent.MODE_MULTIPLE) {
this.preSelectUsers.push(user);
}
}
onSelectGroup(group: IdentityGroupModel) {
if (this.groupMode === GroupCloudComponent.MODE_MULTIPLE) {
this.preSelectGroup.push(group);
}
}
get peopleSingleMode() { get peopleSingleMode() {
return PeopleCloudComponent.MODE_SINGLE; return PeopleCloudComponent.MODE_SINGLE;
} }

View File

@ -26,9 +26,10 @@ Searches Groups.
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| appName | `string` | | Name of the application. If specified this shows the users who have access to the app. | | appName | `string` | | Name of the application. If specified this shows the groups who have access to the app. |
| readOnly | `boolean` | false | readOnly mode (true/false). |
| mode | `string` | | User selection mode (single/multiple). | | mode | `string` | | User selection mode (single/multiple). |
| preSelectGroups | [`IdentityGroupModel`](../../../lib/core/models/identity-group.model.ts)`[]` | \[] | Array of users to be pre-selected. This pre-selects all users in multi selection mode and only the first user of the array in single selection mode. | | preSelectGroups | [`IdentityGroupModel`](../../../lib/core/models/identity-group.model.ts)`[]` | \[] | 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. |
| roles | `string[]` | \[] | Role names of the groups to be listed. | | roles | `string[]` | \[] | Role names of the groups to be listed. |
| searchGroupsControl | `FormControl` | new FormControl() | FormControl to search the group | | searchGroupsControl | `FormControl` | new FormControl() | FormControl to search the group |
| title | `string` | | Title of the field | | title | `string` | | Title of the field |
@ -95,6 +96,20 @@ export class MyComponent {
### Read-only ### Read-only
You can use `readonly` property to set the component in `readonly` mode. Readonly mode will disable any interaction with the component.
```html
<adf-cloud-group
[appName]="'simple-app'"
[mode]="'multiple'"
[preSelectGroups]="groups"
[readOnly]="true">
</adf-cloud-group>
```
If you want to manage each group seperately you can set their readonly property at your preference.
You need to have component's readonly property set to false. Component's readonly mode overwrites groups level.
You can use `readonly` property to make preselected groups read-only in `multiple` mode. You can use `readonly` property to make preselected groups read-only in `multiple` mode.
Usage example: Usage example:

View File

@ -25,6 +25,7 @@ Allows one or more users to be selected (with auto-suggestion) based on the inpu
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| appName | `string` | | Name of the application. If specified, this shows the users who have access to the app. | | appName | `string` | | Name of the application. If specified, this shows the users who have access to the app. |
| readOnly | `boolean` | false | readOnly mode (true/false). |
| mode | `string` | | User selection mode (single/multiple). | | mode | `string` | | User selection mode (single/multiple). |
| preSelectUsers | [`IdentityUserModel`](../../../lib/core/models/identity-user.model.ts)`[]` | | Array of users to be pre-selected. All users in the array are pre-selected in multi selection mode, but only the first user is pre-selected in single selection mode. Mandatory properties are: id, email, username | | preSelectUsers | [`IdentityUserModel`](../../../lib/core/models/identity-user.model.ts)`[]` | | Array of users to be pre-selected. All users in the array are pre-selected in multi selection mode, but only the first user is pre-selected in single selection mode. Mandatory properties are: id, email, username |
| roles | `string[]` | | Role names of the users to be listed. | | roles | `string[]` | | Role names of the users to be listed. |
@ -44,7 +45,19 @@ Allows one or more users to be selected (with auto-suggestion) based on the inpu
### Read-only ### Read-only
You can use `readonly` property to make preselected users read-only in `multiple` mode. You can use `readonly` property to set the component in `readonly` mode. Readonly mode will disable any interaction with the component.
```html
<adf-cloud-people
[appName]="'simple-app'"
[mode]="'multiple'"
[preSelectUsers]="preSelectUsers"
[readOnly]="true">
</adf-cloud-people>
```
If you want to manage each user seperately you can set their readonly property at your preference.
You need to have component's readonly property set to false. Component's readonly mode overwrites users level.
```ts ```ts
const preSelectUsers = [ const preSelectUsers = [
@ -55,7 +68,7 @@ const preSelectUsers = [
``` ```
```html ```html
<adf-cloud-people <adf-cloud-people
[mode]="'multiple'", [mode]="'multiple'"
[preSelectUsers]="preSelectUsers"> [preSelectUsers]="preSelectUsers">
</adf-cloud-people> </adf-cloud-people>
``` ```

View File

@ -193,21 +193,22 @@ describe('People Groups Cloud Component', () => {
await peopleGroupCloudComponentPage.clickPeopleCloudSingleSelection(); await peopleGroupCloudComponentPage.clickPeopleCloudSingleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudSingleSelectionIsSelected(); await peopleGroupCloudComponentPage.checkPeopleCloudSingleSelectionIsSelected();
await peopleGroupCloudComponentPage.enterPeoplePreselect('[{"id":"12345","username":"someUsername","email":"someEmail"}]');
await expect(await peopleCloudComponent.checkSelectedPeople('someUsername'));
await peopleGroupCloudComponentPage.clickPreselectValidation(); await peopleGroupCloudComponentPage.clickPreselectValidation();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true'); await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
await peopleGroupCloudComponentPage.enterPeoplePreselect('[{"id":"12345","username":"someUsername","email":"someEmail"}]');
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe('');
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true'); await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"id":"${noRoleUser.idIdentityService}"}]`); await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"id":"${noRoleUser.idIdentityService}"}]`);
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${noRoleUser.firstName} ${noRoleUser.lastName}`); await expect(await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`));
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"}]`); await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"}]`);
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${apsUser.firstName} ${apsUser.lastName}`); await expect(await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`));
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${testUser.username}"}]`); await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${testUser.username}"}]`);
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${testUser.firstName} ${testUser.lastName}`); await expect(await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`));
}); });
it('[C309676] Should fetch the preselect users based on the Validate flag set to True in Multiple mode selection', async () => { it('[C309676] Should fetch the preselect users based on the Validate flag set to True in Multiple mode selection', async () => {
@ -254,18 +255,6 @@ describe('People Groups Cloud Component', () => {
}); });
it('[C309678] Should not fetch the preselect users when mandatory parameters Id, Email and username are missing', async () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await peopleGroupCloudComponentPage.clickPreselectValidation();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"firstName":"${apsUser.firstName}","lastName":"${apsUser.lastName},"` +
`{"firstName":"${testUser.firstName}","lastName":"${testUser.lastName}",{"firstName":"${noRoleUser.firstName}","lastName":"${noRoleUser.lastName}"]`);
await browser.sleep(200);
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe('');
});
}); });
}); });

View File

@ -90,7 +90,7 @@ describe('People Groups Cloud Component', () => {
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`); await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.selectAssigneeFromList(`${testUser.firstName} ${testUser.lastName}`); await peopleCloudComponent.selectAssigneeFromList(`${testUser.firstName} ${testUser.lastName}`);
await browser.sleep(100); await browser.sleep(100);
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${testUser.firstName} ${testUser.lastName}`); await expect(await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`));
}); });
it('[C305041] Should filter the People Multiple Selection with the Application name filter', async () => { it('[C305041] Should filter the People Multiple Selection with the Application name filter', async () => {
@ -118,7 +118,7 @@ describe('People Groups Cloud Component', () => {
await groupCloudComponentPage.searchGroups(hrGroup.name); await groupCloudComponentPage.searchGroups(hrGroup.name);
await groupCloudComponentPage.checkGroupIsDisplayed(hrGroup.name); await groupCloudComponentPage.checkGroupIsDisplayed(hrGroup.name);
await groupCloudComponentPage.selectGroupFromList(hrGroup.name); await groupCloudComponentPage.selectGroupFromList(hrGroup.name);
await expect(await groupCloudComponentPage.getGroupsFieldContent()).toBe(hrGroup.name); await expect(await groupCloudComponentPage.checkSelectedGroup(hrGroup.name));
}); });
it('[C305041] Should filter the Groups Multiple Selection with the Application name filter', async () => { it('[C305041] Should filter the Groups Multiple Selection with the Application name filter', async () => {

View File

@ -106,7 +106,7 @@ describe('Start Task', () => {
it('[C297675] Should create a task unassigned when assignee field is empty in Start Task form', async () => { it('[C297675] Should create a task unassigned when assignee field is empty in Start Task form', async () => {
await tasksCloudDemoPage.openNewTaskForm(); await tasksCloudDemoPage.openNewTaskForm();
await startTask.checkFormIsDisplayed(); await startTask.checkFormIsDisplayed();
await peopleCloudComponent.clearAssignee(); await peopleCloudComponent.clearAssigneeFromChip(testUser.username);
await startTask.addName(unassignedTaskName); await startTask.addName(unassignedTaskName);
await startTask.clickStartButton(); await startTask.clickStartButton();
await tasksCloudDemoPage.editTaskFilterCloudComponent(); await tasksCloudDemoPage.editTaskFilterCloudComponent();
@ -125,7 +125,7 @@ describe('Start Task', () => {
it('[C291956] Should be able to create a new standalone task without assignee', async () => { it('[C291956] Should be able to create a new standalone task without assignee', async () => {
await tasksCloudDemoPage.openNewTaskForm(); await tasksCloudDemoPage.openNewTaskForm();
await startTask.checkFormIsDisplayed(); await startTask.checkFormIsDisplayed();
await peopleCloudComponent.clearAssignee(); await peopleCloudComponent.clearAssigneeFromChip(testUser.username);
await startTask.addName(unassignedTaskName); await startTask.addName(unassignedTaskName);
await startTask.checkStartButtonIsEnabled(); await startTask.checkStartButtonIsEnabled();
await startTask.clickStartButton(); await startTask.clickStartButton();
@ -203,7 +203,7 @@ describe('Start Task', () => {
it('[C291953] Assignee field should display the logged user as default', async () => { it('[C291953] Assignee field should display the logged user as default', async () => {
await tasksCloudDemoPage.openNewTaskForm(); await tasksCloudDemoPage.openNewTaskForm();
await startTask.checkFormIsDisplayed(); await startTask.checkFormIsDisplayed();
await expect(await peopleCloudComponent.getAssignee()).toContain(testUser.firstName, 'does not contain Admin'); await expect(await peopleCloudComponent.checkSelectedPeople(testUser.firstName));
await startTask.clickCancelButton(); await startTask.clickCancelButton();
}); });
@ -212,7 +212,7 @@ describe('Start Task', () => {
await startTask.checkFormIsDisplayed(); await startTask.checkFormIsDisplayed();
await startTask.addName(reassignTaskName); await startTask.addName(reassignTaskName);
await expect(await peopleCloudComponent.getAssignee()).toBe(`${testUser.firstName} ${testUser.lastName}`); await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.searchAssignee(apsUser.username); await peopleCloudComponent.searchAssignee(apsUser.username);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`); await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.selectAssigneeFromList(`${apsUser.firstName} ${apsUser.lastName}`); await peopleCloudComponent.selectAssigneeFromList(`${apsUser.firstName} ${apsUser.lastName}`);

View File

@ -1,61 +1,71 @@
<div class="adf-cloud-group"> <form>
<mat-form-field> <mat-form-field class="adf-cloud-group">
<mat-label id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label> <mat-label
<mat-chip-list #groupChipList *ngIf="isMultipleMode()" [disabled]="isDisabled" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list"> id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
<mat-chip <mat-chip-list #groupChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
*ngFor="let group of selectedGroups$ | async" <mat-chip
[removable]="!(group.readonly)" *ngFor="let group of selectedGroups"
(removed)="onRemove(group)" [removable]="!(group.readonly)"
matTooltip="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}" [attr.data-automation-id]="'adf-cloud-group-chip-' + group.name"
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name"> (removed)="onRemove(group)"
{{group.name}} matTooltip="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
<mat-icon {{group.name}}
*ngIf="!(group.readonly || readOnly)" <mat-icon
matChipRemove [attr.data-automation-id]="'adf-cloud-group-chip-remove-icon-' + group.name"> *ngIf="!(group.readonly || readOnly)"
cancel matChipRemove [attr.data-automation-id]="'adf-cloud-group-chip-remove-icon-' + group.name">
</mat-icon> cancel
</mat-chip> </mat-icon>
<input matInput </mat-chip>
[formControl]="searchGroupsControl" <input matInput
class="adf-group-input" [formControl]="searchGroupsControl"
id="group-name" [matAutocomplete]="auto"
[attr.disabled]="isDisabled" [matChipInputFor]="groupChipList"
data-automation-id="adf-cloud-group-search-input" (focus)="setFocus(true)"
(focus)="setFocus(true)" (blur)="setFocus(false)"
(blur)="setFocus(false)" class="adf-group-input"
[matAutocomplete]="auto" id="group-name"
[matChipInputFor]="groupChipList" #groupInput> data-automation-id="adf-cloud-group-search-input" #groupInput>
</mat-chip-list> </mat-chip-list>
<input *ngIf="!isMultipleMode()" <mat-autocomplete
matInput autoActiveFirstOption
class="adf-group-input" #auto="matAutocomplete"
data-automation-id="adf-cloud-group-search-input" class="adf-cloud-group-list"
(focus)="setFocus(true)" (optionSelected)="onSelect($event.option.value)"
(blur)="setFocus(false)" [displayWith]="getDisplayName"
[formControl]="searchGroupsControl" data-automation-id="adf-cloud-group-autocomplete">
[matAutocomplete]="auto"> <mat-option *ngFor="let group of searchGroups$ | async; let i = index" [value]="group"
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
<div class="adf-cloud-group-row" id="adf-group-{{i}}" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="20px">
<button class="adf-group-short-name" mat-fab>{{group | groupNameInitial }}</button>
<span>{{group.name}}</span>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-progress-bar
*ngIf="isLoading"
mode="indeterminate">
</mat-progress-bar>
<mat-autocomplete <mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
#auto="matAutocomplete" <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
class="adf-cloud-group-list" {{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : validateGroupsMessage } }}</mat-error>
(optionSelected)="onSelect($event.option.value)" <mat-error *ngIf="searchGroupsControl.hasError('pattern')">
[displayWith]="getDisplayName" <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
data-automation-id="adf-cloud-group-autocomplete"> {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</mat-error>
<mat-option *ngFor="let group of searchGroups$ | async; let i = index" [value]="group" [attr.data-automation-id]="'adf-cloud-group-chip-' + group.name"> <mat-error *ngIf="searchGroupsControl.hasError('maxlength')">
<div class="adf-cloud-group-row" id="adf-group-{{i}}" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="20px"> <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
<button class="adf-group-short-name" mat-fab>{{group | groupNameInitial }}</button> {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}
<span>{{group.name}}</span> </mat-error>
</div> <mat-error *ngIf="searchGroupsControl.hasError('minlength')">
</mat-option> <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
</mat-autocomplete> {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
</mat-form-field> <mat-error *ngIf="searchGroupsControl.hasError('required')">
<div class="adf-cloud-group-error" *ngIf="hasErrorMessage()"> <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
<div fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState"> {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
<div class="adf-cloud-group-error-message"> <mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-groups-typing-error">
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }} <mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
</div> {{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}</mat-error>
<mat-icon class="adf-cloud-group-error-icon">warning</mat-icon> </form>
</div>
</div>
</div>

View File

@ -6,7 +6,14 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
.adf { .adf {
&-cloud-group-list {
margin: 5px 0;
padding: 10px 0;
}
&-cloud-group { &-cloud-group {
width: 100%;
.mat-form-field { .mat-form-field {
width: 100%; width: 100%;
} }

View File

@ -72,7 +72,7 @@ describe('GroupCloudComponent', () => {
component.title = 'TITLE_KEY'; component.title = 'TITLE_KEY';
fixture.detectChanges(); fixture.detectChanges();
const matLabel: HTMLInputElement = <HTMLInputElement> element.querySelector('#adf-group-cloud-title-id'); const matLabel: HTMLInputElement = <HTMLInputElement> element.querySelector('#adf-group-cloud-title-id');
fixture.whenStable().then( () => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(matLabel.textContent).toEqual('TITLE_KEY'); expect(matLabel.textContent).toEqual('TITLE_KEY');
}); });
@ -86,14 +86,14 @@ describe('GroupCloudComponent', () => {
findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups)); findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups));
})); }));
it('should list the group if the typed result match', (done) => { it('should list the groups as dropdown options if the search term has results', (done) => {
findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups));
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input'); const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus(); inputHTMLElement.focus();
inputHTMLElement.value = 'Mock'; inputHTMLElement.value = 'Mock';
inputHTMLElement.dispatchEvent(new Event('keyup')); inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input')); inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(5); expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(5);
@ -116,13 +116,19 @@ describe('GroupCloudComponent', () => {
}); });
}); });
it('should emit selectedGroup if option is valid', (done) => { it('should selectedGroup and groupsChanged emit, update selected groups when a group is selected', (done) => {
const group = { name: 'groupname' };
fixture.detectChanges(); fixture.detectChanges();
spyOn(component, 'hasGroupIdOrName').and.returnValue(true);
const selectEmitSpy = spyOn(component.selectGroup, 'emit'); const selectEmitSpy = spyOn(component.selectGroup, 'emit');
component.onSelect({ name: 'groupname' }); const changedGroupsSpy = spyOn(component.changedGroups, 'emit');
component.onSelect(group);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(selectEmitSpy).toHaveBeenCalled(); expect(selectEmitSpy).toHaveBeenCalledWith(group);
expect(changedGroupsSpy).toHaveBeenCalledWith([group]);
expect(component.getSelectedGroups()[0]).toEqual(group);
done(); done();
}); });
}); });
@ -139,9 +145,9 @@ describe('GroupCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
inputHTMLElement.blur(); inputHTMLElement.blur();
fixture.detectChanges(); fixture.detectChanges();
const errorMessage = element.querySelector('.adf-cloud-group-error-message'); const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]');
expect(errorMessage).not.toBeNull(); expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toContain(' ADF_CLOUD_GROUPS.ERROR.NOT_FOUND '); expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND');
done(); done();
}); });
}); });
@ -162,13 +168,13 @@ describe('GroupCloudComponent', () => {
element = fixture.nativeElement; element = fixture.nativeElement;
})); }));
it('should fetch the client ID if appName specified', async(() => { it('should fetch the client ID if appName specified', async (() => {
const getClientIdByApplicationNameSpy = spyOn(identityGroupService, 'getClientIdByApplicationName').and.callThrough(); const getClientIdByApplicationNameSpy = spyOn(identityGroupService, 'getClientIdByApplicationName').and.callThrough();
component.appName = 'mock-app-name'; component.appName = 'mock-app-name';
const change = new SimpleChange(null, 'mock-app-name', false); const change = new SimpleChange(null, 'mock-app-name', false);
component.ngOnChanges({ 'appName': change }); component.ngOnChanges({ 'appName': change });
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then( () => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled(); expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
}); });
@ -294,89 +300,31 @@ describe('GroupCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
inputHTMLElement.blur(); inputHTMLElement.blur();
fixture.detectChanges(); fixture.detectChanges();
const errorMessage = element.querySelector('.adf-cloud-group-error-message'); const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]');
expect(errorMessage).not.toBeNull(); expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toContain(' ADF_CLOUD_GROUPS.ERROR.NOT_FOUND '); expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND');
done(); done();
}); });
}); });
}); });
describe('Single Mode and Pre-selected groups', () => { describe('No preselected groups', () => {
beforeEach(async () => {
fixture.detectChanges();
});
beforeEach(async(() => { it('should not pre-select any group when preSelectGroups is empty - single mode', () => {
component.mode = 'single'; component.mode = 'single';
component.preSelectGroups = <any> mockIdentityGroups;
fixture.detectChanges(); fixture.detectChanges();
element = fixture.nativeElement; const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
})); expect(chips.length).toEqual(0);
it('should not show chip list when mode=single', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeNull();
done();
});
}); });
it('should not pre-select any group when preSelectGroups is empty and mode=single', (done) => { it('should not pre-select any group when preSelectGroups is empty - multiple mode', () => {
component.preSelectGroups = [];
fixture.detectChanges();
fixture.whenStable().then(() => {
const selectedGroup = component.searchGroupsControl.value;
expect(selectedGroup).toBeNull();
done();
});
});
});
describe('Multiple Mode and Pre-selected groups', () => {
const change = new SimpleChange(null, mockIdentityGroups, false);
beforeEach(async(() => {
component.mode = 'multiple';
component.preSelectGroups = <any> mockIdentityGroups;
component.ngOnChanges({ 'preSelectGroups': change });
fixture.detectChanges();
element = fixture.nativeElement;
}));
it('should show chip list when mode=multiple', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeDefined();
done();
});
});
it('should pre-select all preSelectGroups when mode=multiple', (done) => {
component.mode = 'multiple'; component.mode = 'multiple';
fixture.detectChanges(); fixture.detectChanges();
component.ngOnChanges({ 'preSelectGroups': change }); const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
fixture.detectChanges(); expect(chips.length).toEqual(0);
fixture.whenStable().then(() => {
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(chips.length).toBe(5);
done();
});
});
it('should emit removeGroup when a selected group is removed', (done) => {
const removeSpy = spyOn(component.removeGroup, 'emit');
component.mode = 'multiple';
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
removeIcon.nativeElement.click();
fixture.detectChanges();
expect(removeSpy).toHaveBeenCalled();
done();
});
}); });
}); });
@ -439,57 +387,114 @@ describe('GroupCloudComponent', () => {
}); });
}); });
describe('Single Mode with pre-selected groups', () => {
const changes = new SimpleChange(null, mockIdentityGroups, false);
beforeEach(async(() => {
component.mode = 'single';
component.preSelectGroups = <any> mockIdentityGroups;
component.ngOnChanges({ 'preSelectGroups': changes });
fixture.detectChanges();
element = fixture.nativeElement;
}));
it('should show only one mat chip with the first preSelectedGroup', () => {
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(chips.length).toEqual(1);
expect(chips[0].attributes['data-automation-id']).toEqual(`adf-cloud-group-chip-${mockIdentityGroups[0].name}`);
});
});
describe('Multiple Mode with pre-selected groups', () => {
const change = new SimpleChange(null, mockIdentityGroups, false);
beforeEach(async(() => {
component.mode = 'multiple';
component.preSelectGroups = <any> mockIdentityGroups;
component.ngOnChanges({ 'preSelectGroups': change });
fixture.detectChanges();
element = fixture.nativeElement;
}));
it('should pre-select all preSelectGroups', () => {
component.mode = 'multiple';
fixture.detectChanges();
component.ngOnChanges({ 'preSelectGroups': change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(chips.length).toBe(5);
});
it('should removeGroup and changedGroups emit when a selected group is removed', (done) => {
const removeGroupEmitterSpy = spyOn(component.removeGroup, 'emit');
const changedGroupsEmitterSpy = spyOn(component.changedGroups, 'emit');
const groupToRemove = mockIdentityGroups[0];
component.mode = 'multiple';
fixture.detectChanges();
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
removeIcon.nativeElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(removeGroupEmitterSpy).toHaveBeenCalledWith(groupToRemove);
expect(changedGroupsEmitterSpy).toHaveBeenCalledWith([mockIdentityGroups[1], mockIdentityGroups[2], mockIdentityGroups[3], mockIdentityGroups[4]]);
expect(component.getSelectedGroups().indexOf({
id: groupToRemove.id,
name: groupToRemove.name,
path: groupToRemove.path
})).toEqual(-1);
done();
});
});
});
describe('Multiple Mode with read-only', () => { describe('Multiple Mode with read-only', () => {
it('Should not show remove icon for pre-selected groups if readonly property set to true', (done) => { it('Should not show remove icon for pre-selected groups if readonly property set to true', (done) => {
fixture.detectChanges(); fixture.detectChanges();
const preselectedGroups = [ component.preSelectGroups = [
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: true }, { id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: true },
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: true } { id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: true }
]; ];
component.preSelectGroups = preselectedGroups; const change = new SimpleChange(null, component.preSelectGroups, false);
const change = new SimpleChange(null, preselectedGroups, false);
component.mode = 'multiple'; component.mode = 'multiple';
component.ngOnChanges({ 'preSelectGroups': change }); component.ngOnChanges({ 'preSelectGroups': change });
fixture.detectChanges(); 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"]');
expect(chipList.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBeTruthy();
expect(component.preSelectGroups[1].readonly).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); 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"]');
expect(chipList.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBeTruthy();
expect(component.preSelectGroups[1].readonly).toBeTruthy();
expect(removeIcon).toBeNull(); expect(removeIcon).toBeNull();
expect(component.preSelectGroups.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBe(true, 'Not removable');
expect(component.preSelectGroups[1].readonly).toBe(true, 'Not removable');
expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(2);
done(); done();
}); });
}); });
it('Should be able to remove preselected groups if readonly property set to false', (done) => { it('Should be able to remove preselected groups if readonly property set to false', (done) => {
fixture.detectChanges(); fixture.detectChanges();
const preselectedGroups = [ component.preSelectGroups = [
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: false }, { id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: false },
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: false } { id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: false }
]; ];
component.preSelectGroups = preselectedGroups; const change = new SimpleChange(null, component.preSelectGroups, false);
const change = new SimpleChange(null, preselectedGroups, false);
component.mode = 'multiple'; component.mode = 'multiple';
const removeGroupSpy = spyOn(component.removeGroup, 'emit');
component.ngOnChanges({ 'preSelectGroups': change }); component.ngOnChanges({ 'preSelectGroups': change });
const removeGroupSpy = spyOn(component.removeGroup, 'emit');
fixture.detectChanges(); 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"]');
expect(chipList.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable');
expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable');
removeIcon.click();
fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); 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"]');
expect(chips.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable');
expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable');
removeIcon.click(); removeIcon.click();
fixture.detectChanges(); fixture.detectChanges();
expect(removeGroupSpy).toHaveBeenCalled(); expect(removeGroupSpy).toHaveBeenCalled();
@ -497,65 +502,101 @@ describe('GroupCloudComponent', () => {
done(); done();
}); });
}); });
});
describe('Multiple Mode and Pre-selected groups with validate flag', () => { describe('Component readonly mode', () => {
const change = new SimpleChange(null, mockIdentityGroups, false);
beforeEach(async(() => { it('should chip list be disabled and show one single chip - single mode', () => {
component.mode = 'multiple'; component.mode = 'single';
component.validate = true; component.readOnly = true;
component.preSelectGroups = <any> mockIdentityGroups; component.preSelectGroups = <any> mockIdentityGroups;
element = fixture.nativeElement; component.ngOnChanges({ 'preSelectGroups': change });
alfrescoApiService = TestBed.get(AlfrescoApiService);
fixture.detectChanges();
}));
it('should emit warning if are invalid groups', (done) => { fixture.detectChanges();
findGroupsByNameSpy.and.returnValue(Promise.resolve([])); const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const warnMessage = { message: 'INVALID_PRESELECTED_GROUPS', groups: [{ name: 'invalidGroupOne' }, { name: 'invalidGroupTwo' }] }; const chipList = fixture.nativeElement.querySelector('mat-chip-list');
component.validate = true; expect(chips).toBeDefined();
component.preSelectGroups = <any> [{ name: 'invalidGroupOne' }, { name: 'invalidGroupTwo' }]; expect(chipList).toBeDefined();
fixture.detectChanges(); expect(chips.length).toBe(1);
component.loadSinglePreselectGroup(); expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
component.warning.subscribe((response) => { });
expect(response).toEqual(warnMessage);
expect(response.message).toEqual(warnMessage.message); it('should chip list be disabled and show all the chips - multiple mode', () => {
expect(response.groups).toEqual(warnMessage.groups); component.mode = 'multiple';
expect(response.groups[0].name).toEqual('invalidGroupOne'); component.readOnly = true;
done(); 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');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(5);
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
}); });
}); });
it('should filter group by name if validate true', (done) => { describe('Preselected groups and validation enabled', () => {
findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups));
component.mode = 'multiple'; it('should check validation only for the first group and emit warning when group is invalid - single mode', (done) => {
component.validate = true; spyOn(identityGroupService, 'findGroupsByName').and.returnValue(Promise.resolve([]));
component.preSelectGroups = <any> [{ name: mockIdentityGroups[1].name }, { name: mockIdentityGroups[2].name }]; spyOn(component, 'hasGroupIdOrName').and.returnValue(false);
fixture.detectChanges();
fixture.whenStable().then(() => { const expectedWarning = {
component.filterPreselectGroups().then((result) => { message: 'INVALID_PRESELECTED_GROUPS',
expect(findGroupsByNameSpy).toHaveBeenCalled(); groups: [{
expect(component.groupExists(result[0])).toEqual(true); id: mockIdentityGroups[0].id,
expect(component.groupExists(result[1])).toEqual(true); name: mockIdentityGroups[0].name,
path: mockIdentityGroups[0].path,
subGroups: []
}]
};
component.warning.subscribe(warning => {
expect(warning).toEqual(expectedWarning);
done(); done();
}); });
component.mode = 'single';
component.validate = true;
component.preSelectGroups = <any> [mockIdentityGroups[0], mockIdentityGroups[1]];
component.ngOnChanges({ 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) });
});
it('should check validation for all the groups and emit warning - multiple mode', (done) => {
spyOn(identityGroupService, 'findGroupsByName').and.returnValue(Promise.resolve(undefined));
const expectedWarning = {
message: 'INVALID_PRESELECTED_GROUPS',
groups: [
{
id: mockIdentityGroups[0].id,
name: mockIdentityGroups[0].name,
path: mockIdentityGroups[0].path,
subGroups: []
},
{
id: mockIdentityGroups[1].id,
name: mockIdentityGroups[1].name,
path: mockIdentityGroups[1].path,
subGroups: []
}]
};
component.warning.subscribe(warning => {
expect(warning).toEqual(expectedWarning);
done();
});
component.mode = 'multiple';
component.validate = true;
component.preSelectGroups = <any> [mockIdentityGroups[0], mockIdentityGroups[1]];
component.ngOnChanges({ 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) });
}); });
}); });
it('should not preselect any group if name is invalid and validation enable', (done) => { it('should removeDuplicatedGroups return only unique groups', () => {
findGroupsByNameSpy.and.returnValue(of([])); const duplicatedGroups = [{ name: mockIdentityGroups[0].name }, { name: mockIdentityGroups[0].name }];
component.mode = 'single'; expect(component.removeDuplicatedGroups(duplicatedGroups)).toEqual([{ name: mockIdentityGroups[0].name }]);
component.validate = true;
component.preSelectGroups = <any> [{ name: 'invalid group' }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectGroups().then((result) => {
fixture.detectChanges();
expect(findGroupsByNameSpy).toHaveBeenCalled();
expect(result.length).toEqual(0);
done();
});
});
}); });
}); });
}); });

View File

@ -35,7 +35,12 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
import { debounceTime } from 'rxjs/internal/operators/debounceTime'; import { debounceTime } from 'rxjs/internal/operators/debounceTime';
import { distinctUntilChanged, switchMap, mergeMap, filter, tap, map, takeUntil } from 'rxjs/operators'; import { distinctUntilChanged, switchMap, mergeMap, filter, tap, map, takeUntil } from 'rxjs/operators';
import { IdentityGroupModel, IdentityGroupSearchParam, IdentityGroupService, LogService } from '@alfresco/adf-core'; import {
IdentityGroupModel,
IdentityGroupSearchParam,
IdentityGroupService,
LogService
} from '@alfresco/adf-core';
@Component({ @Component({
selector: 'adf-cloud-group', selector: 'adf-cloud-group',
@ -88,7 +93,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
/** FormControl to search the group */ /** FormControl to search the group */
@Input() @Input()
searchGroupsControl: FormControl = new FormControl(); searchGroupsControl: FormControl = new FormControl({ value: '', disabled: false });
/** Role names of the groups to be listed. */ /** Role names of the groups to be listed. */
@Input() @Input()
@ -113,61 +118,59 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('groupInput') @ViewChild('groupInput')
private groupInput: ElementRef<HTMLInputElement>; private groupInput: ElementRef<HTMLInputElement>;
private selectedGroups: IdentityGroupModel[] = [];
private searchGroups: IdentityGroupModel[] = []; private searchGroups: IdentityGroupModel[] = [];
private searchGroupsSubject: BehaviorSubject<IdentityGroupModel[]>;
private onDestroy$ = new Subject<boolean>();
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>([]); selectedGroups: IdentityGroupModel[] = [];
invalidGroups: IdentityGroupModel[] = [];
selectedGroups$ = new BehaviorSubject<IdentityGroupModel[]>([]);
searchGroups$: Observable<IdentityGroupModel[]>;
_subscriptAnimationState = 'enter'; _subscriptAnimationState = 'enter';
clientId: string; clientId: string;
searchedValue = '';
isFocused: boolean; isFocused: boolean;
isDisabled: boolean;
private onDestroy$ = new Subject<boolean>();
currentTimeout: any; currentTimeout: any;
invalidGroups: IdentityGroupModel[] = []; validateGroupsMessage: string;
searchedValue = '';
isLoading = false;
constructor( constructor(
private identityGroupService: IdentityGroupService, private identityGroupService: IdentityGroupService,
private logService: LogService private logService: LogService) {}
) { }
ngOnInit() { ngOnInit() {
if (this.searchGroupsSubject === undefined) {
this.searchGroupsSubject = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
this.searchGroups$ = this.searchGroupsSubject.asObservable();
}
this.loadClientId();
this.initSearch(); this.initSearch();
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (this.isPreselectedGroupsChanged(changes)) { if (this.hasPreselectedGroupsChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
if (this.isValidationEnabled()) { if (this.hasPreSelectGroups()) {
this.loadPreSelectGroups(); this.loadPreSelectGroups();
} else { } else if (this.hasPreselectedGroupsCleared(changes)) {
this.loadNoValidationPreselectGroups(); this.selectedGroups = [];
this.invalidGroups = [];
}
if (!this.isValidationEnabled()) {
this.invalidGroups = [];
} }
} }
if (this.isAppNameChanged(changes.appName)) { if (changes.appName && this.isAppNameChanged(changes.appName)) {
this.disableSearch();
this.loadClientId(); this.loadClientId();
} else { this.initSearch();
this.enableSearch();
} }
} }
private isPreselectedGroupsChanged(changes: SimpleChanges): boolean {
return changes.preSelectGroups
&& changes.preSelectGroups.previousValue !== changes.preSelectGroups.currentValue
&& this.hasPreSelectGroups();
}
private isAppNameChanged(change: SimpleChange): boolean { 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;
} }
@ -175,23 +178,23 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
private async loadClientId() { private async loadClientId() {
this.clientId = await this.identityGroupService.getClientIdByApplicationName(this.appName).toPromise(); this.clientId = await this.identityGroupService.getClientIdByApplicationName(this.appName).toPromise();
if (this.clientId) { if (this.clientId) {
this.enableSearch(); this.searchGroupsControl.enable();
} }
} }
initSearch() { initSearch() {
this.searchGroupsControl.valueChanges.pipe( this.searchGroupsControl.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
filter((value) => { filter((value) => {
return typeof value === 'string'; return typeof value === 'string';
}), }),
tap((value) => { tap((value) => {
this.searchedValue = value; this.searchedValue = value;
if (value) { if (value) {
this.setError(); this.setTypingError();
} }
}), }),
debounceTime(500),
distinctUntilChanged(),
tap(() => { tap(() => {
this.resetSearchGroups(); this.resetSearchGroups();
}), }),
@ -221,7 +224,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
takeUntil(this.onDestroy$) takeUntil(this.onDestroy$)
).subscribe((searchedGroup: any) => { ).subscribe((searchedGroup: any) => {
this.searchGroups.push(searchedGroup); this.searchGroups.push(searchedGroup);
this.searchGroups$.next(this.searchGroups); this.searchGroupsSubject.next(this.searchGroups);
}); });
} }
@ -236,7 +239,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
private isGroupAlreadySelected(group: IdentityGroupModel): boolean { private isGroupAlreadySelected(group: IdentityGroupModel): boolean {
if (this.selectedGroups && this.selectedGroups.length > 0 && this.isMultipleMode()) { if (this.selectedGroups && this.selectedGroups.length > 0 && this.isMultipleMode()) {
const result = this.selectedGroups.find((selectedGroup: IdentityGroupModel) => { const result = this.selectedGroups.find((selectedGroup: IdentityGroupModel) => {
return selectedGroup.id === group.id; return selectedGroup.name === group.name;
}); });
return !!result; return !!result;
@ -244,115 +247,74 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
return false; return false;
} }
async searchGroup(groupName: any): Promise<IdentityGroupModel> { async searchGroup(groupName: string): Promise<IdentityGroupModel> {
return (await this.identityGroupService.findGroupsByName(this.createSearchParam(groupName)).toPromise())[0]; return (await this.identityGroupService.findGroupsByName({ name: groupName }).toPromise())[0];
} }
async filterPreselectGroups() { async validatePreselectGroups(): Promise<any> {
const promiseBatch = this.preSelectGroups.map(async (group: IdentityGroupModel) => { this.invalidGroups = [];
let result: any;
let preselectedGroupsToValidate: IdentityGroupModel[] = [];
if (this.isSingleMode()) {
preselectedGroupsToValidate = [this.preSelectGroups[0]];
} else {
preselectedGroupsToValidate = this.removeDuplicatedGroups(this.preSelectGroups);
}
await Promise.all(preselectedGroupsToValidate.map(async (group: IdentityGroupModel) => {
try { try {
result = await this.searchGroup(group.name); const validationResult = await this.searchGroup(group.name);
if (!this.hasGroupIdOrName(validationResult)) {
this.invalidGroups.push(group);
}
} catch (error) { } catch (error) {
result = []; this.invalidGroups.push(group);
this.logService.error(error); this.logService.error(error);
} }
const isGroupValid: boolean = this.groupExists(result); }));
return isGroupValid ? result : null; this.checkPreselectValidationErrors();
}); this.isLoading = false;
return Promise.all(promiseBatch);
}
public groupExists(result: IdentityGroupModel): boolean {
return result
&& (result.id !== undefined
|| result.name !== undefined);
}
private isValidGroup(filteredGroups: IdentityGroupModel[], group: IdentityGroupModel): IdentityGroupModel {
return filteredGroups.find((filteredGroup: IdentityGroupModel) => {
return filteredGroup &&
(filteredGroup.id === group.id ||
filteredGroup.name === group.name);
});
}
async validatePreselectGroups(): Promise<IdentityGroupModel[]> {
let filteredPreselectGroups: IdentityGroupModel[];
let validGroups: IdentityGroupModel[] = [];
try {
filteredPreselectGroups = await this.filterPreselectGroups();
} catch (error) {
validGroups = [];
this.logService.error(error);
}
await this.preSelectGroups.map((group: IdentityGroupModel) => {
const validGroup = this.isValidGroup(filteredPreselectGroups, group);
if (validGroup) {
validGroups.push(validGroup);
} else {
this.invalidGroups.push(group);
}
});
validGroups = this.removeDuplicatedGroups(validGroups);
return validGroups;
}
public async loadSinglePreselectGroup() {
const groups = await this.validatePreselectGroups();
if (groups && groups.length > 0) {
this.checkPreselectValidationErrors();
this.searchGroupsControl.setValue(groups[0]);
} else {
this.checkPreselectValidationErrors();
}
}
public async loadMultiplePreselectGroups() {
const groups = await this.validatePreselectGroups();
if (groups && groups.length > 0) {
this.checkPreselectValidationErrors();
this.selectedGroups = [...groups];
this.selectedGroups$.next(this.selectedGroups);
} else {
this.checkPreselectValidationErrors();
}
} }
public checkPreselectValidationErrors() { public checkPreselectValidationErrors() {
this.invalidGroups = this.removeDuplicatedGroups(this.invalidGroups);
if (this.invalidGroups.length > 0) { if (this.invalidGroups.length > 0) {
this.warning.emit({ this.generateInvalidGroupsMessage();
message: 'INVALID_PRESELECTED_GROUPS',
groups: this.invalidGroups
});
} }
this.warning.emit({
message: 'INVALID_PRESELECTED_GROUPS',
groups: this.invalidGroups
});
} }
private loadPreSelectGroups() { generateInvalidGroupsMessage() {
if (!this.isMultipleMode()) { this.validateGroupsMessage = '';
this.loadSinglePreselectGroup();
} else {
this.loadMultiplePreselectGroups();
}
}
loadNoValidationPreselectGroups() { this.invalidGroups.forEach((invalidGroup: IdentityGroupModel, index) => {
this.selectedGroups = [...this.removeDuplicatedGroups([...this.preSelectGroups])]; if (index === this.invalidGroups.length - 1) {
if (this.isMultipleMode()) { this.validateGroupsMessage += `${invalidGroup.name} `;
this.selectedGroups$.next(this.selectedGroups); } else {
} else { this.validateGroupsMessage += `${invalidGroup.name}, `;
if (this.currentTimeout) {
clearTimeout(this.currentTimeout);
} }
});
}
this.currentTimeout = setTimeout(() => { private async loadPreSelectGroups() {
this.searchGroupsControl.setValue(this.selectedGroups[0]); this.selectedGroups = [];
this.onSelect(this.selectedGroups[0]);
}, 0); if (this.isSingleMode()) {
this.selectedGroups = [this.preSelectGroups[0]];
} else {
this.selectedGroups = this.removeDuplicatedGroups(this.preSelectGroups);
}
if (this.isValidationEnabled()) {
this.isLoading = true;
await this.validatePreselectGroups();
} }
} }
@ -368,33 +330,67 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
if (this.isMultipleMode()) { if (this.isMultipleMode()) {
if (!this.isGroupAlreadySelected(group)) { if (!this.isGroupAlreadySelected(group)) {
this.selectedGroups.push(group); this.selectedGroups.push(group);
this.selectedGroups$.next(this.selectedGroups);
this.searchGroups$.next([]);
} }
this.groupInput.nativeElement.value = '';
this.searchGroupsControl.setValue('');
} else { } else {
this.invalidGroups = [];
this.selectedGroups = [group]; this.selectedGroups = [group];
} }
this.changedGroups.emit(this.selectedGroups);
this.clearError(); this.groupInput.nativeElement.value = '';
this.searchGroupsControl.setValue('');
this.changedGroups.emit(this.selectedGroups);
this.resetSearchGroups(); this.resetSearchGroups();
} }
onRemove(removedGroup: IdentityGroupModel) { onRemove(groupToRemove: IdentityGroupModel) {
this.removeGroup.emit(removedGroup); this.removeGroup.emit(groupToRemove);
const indexToRemove = this.selectedGroups.findIndex((group: IdentityGroupModel) => { const indexToRemove = this.selectedGroups.findIndex((group: IdentityGroupModel) => {
return group.id === removedGroup.id; return group.id === groupToRemove.id;
}); });
this.selectedGroups.splice(indexToRemove, 1); this.selectedGroups.splice(indexToRemove, 1);
this.selectedGroups$.next(this.selectedGroups);
this.changedGroups.emit(this.selectedGroups); this.changedGroups.emit(this.selectedGroups);
this.searchGroupsControl.markAsDirty();
if (this.isValidationEnabled()) {
this.removeGroupFromValidation(groupToRemove.name);
this.checkPreselectValidationErrors();
}
}
private removeGroupFromValidation(groupName: string) {
const indexToRemove = this.invalidGroups.findIndex((invalidGroup) => {
return invalidGroup.name === groupName;
});
if (indexToRemove !== -1) {
this.invalidGroups.splice(indexToRemove, 1);
}
} }
private resetSearchGroups() { private resetSearchGroups() {
this.searchGroups = []; this.searchGroups = [];
this.searchGroups$.next([]); this.searchGroupsSubject.next(this.searchGroups);
}
hasGroupIdOrName(group: IdentityGroupModel): boolean {
return group && (group.id !== undefined || group.name !== undefined);
}
isSingleMode(): boolean {
return this.mode === GroupCloudComponent.MODE_SINGLE;
}
private isSingleSelectionReadonly(): boolean {
return this.isSingleMode() && this.selectedGroups.length === 1 && this.selectedGroups[0].readonly === true;
}
hasPreselectError(): boolean {
return this.invalidGroups && this.invalidGroups.length > 0;
}
isReadonly(): boolean {
return this.readOnly || this.isSingleSelectionReadonly();
} }
isMultipleMode(): boolean { isMultipleMode(): boolean {
@ -405,58 +401,76 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
return group ? group.name : ''; return group ? group.name : '';
} }
private removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] { removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] {
return groups.filter((group, index, self) => return groups.filter((group, index, self) =>
index === self.findIndex((auxGroup) => { index === self.findIndex((auxGroup) => {
return group.id === auxGroup.id && group.name === auxGroup.name; return group.id === auxGroup.id && group.name === auxGroup.name;
})); }));
} }
private hasPreSelectGroups(): boolean { private hasPreSelectGroups(): boolean {
return this.preSelectGroups && this.preSelectGroups.length > 0; return this.preSelectGroups && this.preSelectGroups.length > 0;
} }
private hasModeChanged(changes): 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 hasPreselectedGroupsChanged(changes): 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 { private createSearchParam(value: string): IdentityGroupSearchParam {
const queryParams: IdentityGroupSearchParam = { name: value }; const queryParams: IdentityGroupSearchParam = { name: value };
return queryParams; return queryParams;
} }
getSelectedGroups(): IdentityGroupModel[] {
return this.selectedGroups;
}
private hasRoles(): boolean { private hasRoles(): boolean {
return this.roles && this.roles.length > 0; return this.roles && this.roles.length > 0;
} }
private disableSearch() { private setTypingError() {
this.searchGroupsControl.disable(); this.searchGroupsControl.setErrors({ searchTypingError: true, ...this.searchGroupsControl.errors });
this.isDisabled = true;
}
private enableSearch() {
this.searchGroupsControl.enable();
this.isDisabled = false;
}
private setError() {
this.searchGroupsControl.setErrors({ invalid: true });
}
private clearError() {
this.searchGroupsControl.setErrors(null);
} }
hasError(): boolean { hasError(): boolean {
return this.searchGroupsControl && this.searchGroupsControl.errors && (this.searchGroupsControl.errors.invalid || this.searchGroupsControl.errors.required); return !!this.searchGroupsControl.errors;
}
isValidationLoading(): boolean {
return this.isValidationEnabled() && this.isLoading;
} }
setFocus(isFocused: boolean) { setFocus(isFocused: boolean) {
this.isFocused = isFocused; this.isFocused = isFocused;
} }
isValidationEnabled() { isValidationEnabled(): boolean {
return this.validate === true; return this.validate === true;
} }
hasErrorMessage(): boolean { getValidationPattern(): string {
return !this.isFocused && this.hasError(); return this.searchGroupsControl.errors.pattern.requiredPattern;
}
getValidationMaxLength(): string {
return this.searchGroupsControl.errors.maxlength.requiredLength;
}
getValidationMinLength(): string {
return this.searchGroupsControl.errors.minlength.requiredLength;
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -181,6 +181,20 @@
"NOT_FOUND": "No group found with the name {{groupName}}" "NOT_FOUND": "No group found with the name {{groupName}}"
} }
}, },
"ADF_CLOUD_USERS": {
"ERROR": {
"NOT_FOUND": "No user found with the name {{userName}}"
}
},
"ADF_CLOUD_PEOPLE_GROUPS": {
"ERROR": {
"INVALID_PATTERN": "Not mathcing pattern {{pattern}}",
"INVALID_MIN_LENGTH": "Minimum length {{requiredLength}}",
"INVALID_MAX_LENGTH": "Maximum length {{requiredLength}}",
"REQUIRED": "Field is required"
}
},
"ADF_CLOUD_TASK_HEADER": { "ADF_CLOUD_TASK_HEADER": {
"BUTTON": { "BUTTON": {
"CLAIM": "Claim", "CLAIM": "Claim",

View File

@ -1,47 +1,35 @@
<form> <form>
<mat-form-field class="adf-people-cloud"> <mat-form-field class="adf-people-cloud">
<mat-label id="adf-people-cloud-title-id">{{ title | translate }}</mat-label> <mat-label id="adf-people-cloud-title-id">{{ title | translate }}</mat-label>
<mat-chip-list #userChipList *ngIf="isMultipleMode(); else singleSelection"> <mat-chip-list #userMultipleChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-people-chip-list">
<mat-chip <mat-chip
*ngFor="let user of selectedUsers$ | async" *ngFor="let user of selectedUsers"
[removable]="!(user.readonly)" [removable]="!(user.readonly)"
[attr.data-automation-id]="'adf-people-cloud-chip-' + user.username" [attr.data-automation-id]="'adf-people-cloud-chip-' + user.username"
matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}" (removed)="onRemove(user)"
(removed)="onRemove(user)"> matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
{{user | fullName}} {{user | fullName}}
<mat-icon <mat-icon
matChipRemove matChipRemove
*ngIf="!(user.readonly || readOnly)" *ngIf="!(user.readonly || readOnly)"
[attr.data-automation-id]="'adf-people-cloud-chip-remove-icon-' + user.username"> [attr.data-automation-id]="'adf-people-cloud-chip-remove-icon-' + user.username">
cancel cancel
</mat-icon> </mat-icon>
</mat-chip> </mat-chip>
<input <input matInput
#userInput [formControl]="searchUserCtrl"
matInput [matAutocomplete]="auto"
[formControl]="searchUserCtrl" [matChipInputFor]="userMultipleChipList"
[matAutocomplete]="auto" (focus)="setFocus(true)"
[matChipInputFor]="userChipList" (blur)="setFocus(false)"
class="adf-cloud-input" class="adf-cloud-input"
(focus)="setFocus(true)" data-automation-id="adf-people-cloud-search-input" #userInput>
(blur)="setFocus(false)"
data-automation-id="adf-people-cloud-search-input">
</mat-chip-list> </mat-chip-list>
<ng-template #singleSelection>
<input matInput
(focus)="setFocus(true)"
(blur)="setFocus(false)"
class="adf-cloud-input"
data-automation-id="adf-people-cloud-search-input"
type="text"
[formControl]="searchUserCtrl"
[matAutocomplete]="auto">
</ng-template>
<mat-autocomplete autoActiveFirstOption class="adf-people-cloud-list" <mat-autocomplete autoActiveFirstOption class="adf-people-cloud-list"
#auto="matAutocomplete" #auto="matAutocomplete"
(optionSelected)="onSelect($event.option.value)" (optionSelected)="onSelect($event.option.value)"
[displayWith]="getDisplayName"> [displayWith]="getDisplayName">
<mat-option *ngFor="let user of searchUsers$ | async; let i = index" [value]="user"> <mat-option *ngFor="let user of searchUsers$ | async; let i = index" [value]="user">
<div class="adf-people-cloud-row" id="adf-people-cloud-user-{{i}}"> <div class="adf-people-cloud-row" id="adf-people-cloud-user-{{i}}">
<div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div> <div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div>
@ -50,10 +38,28 @@
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>
<div class="adf-start-task-cloud-error"> <mat-progress-bar
<div *ngIf="hasErrorMessage()" fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState"> *ngIf="isLoading"
<div class="adf-start-task-cloud-error-message">{{ 'ADF_CLOUD_START_TASK.ERROR.MESSAGE' | translate }}</div> mode="indeterminate">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon> </mat-progress-bar>
</div>
</div> <mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : validateUsersMessage } }}</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('pattern')">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('minlength')">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('required')">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-users-typing-error">
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : searchedValue } }}</mat-error>
</form> </form>

View File

@ -17,7 +17,12 @@
import { PeopleCloudComponent } from './people-cloud.component'; import { PeopleCloudComponent } from './people-cloud.component';
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { IdentityUserService, AlfrescoApiService, CoreModule, setupTestBed } from '@alfresco/adf-core'; import {
IdentityUserService,
AlfrescoApiService,
CoreModule,
setupTestBed
} from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockUsers } from '../mock/user-cloud.mock'; import { mockUsers } from '../mock/user-cloud.mock';
@ -32,7 +37,6 @@ describe('PeopleCloudComponent', () => {
let identityService: IdentityUserService; let identityService: IdentityUserService;
let alfrescoApiService: AlfrescoApiService; let alfrescoApiService: AlfrescoApiService;
let findUsersByNameSpy: jasmine.Spy; let findUsersByNameSpy: jasmine.Spy;
let findUserByUsernameSpy: jasmine.Spy;
const mock = { const mock = {
oauth2Auth: { oauth2Auth: {
@ -62,7 +66,6 @@ describe('PeopleCloudComponent', () => {
identityService = TestBed.get(IdentityUserService); identityService = TestBed.get(IdentityUserService);
alfrescoApiService = TestBed.get(AlfrescoApiService); alfrescoApiService = TestBed.get(AlfrescoApiService);
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock);
findUserByUsernameSpy = spyOn(identityService, 'findUserByUsername').and.returnValue(Promise.resolve([]));
}); });
it('should create PeopleCloudComponent', () => { it('should create PeopleCloudComponent', () => {
@ -73,7 +76,7 @@ describe('PeopleCloudComponent', () => {
component.title = 'TITLE_KEY'; component.title = 'TITLE_KEY';
fixture.detectChanges(); fixture.detectChanges();
const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id'); const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
fixture.whenStable().then( () => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(matLabel.textContent).toEqual('TITLE_KEY'); expect(matLabel.textContent).toEqual('TITLE_KEY');
}); });
@ -83,7 +86,7 @@ describe('PeopleCloudComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id'); const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then( () => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(matLabel.textContent).toEqual(''); expect(matLabel.textContent).toEqual('');
}); });
@ -97,14 +100,14 @@ describe('PeopleCloudComponent', () => {
findUsersByNameSpy = spyOn(identityService, 'findUsersByName').and.returnValue(of(mockUsers)); findUsersByNameSpy = spyOn(identityService, 'findUsersByName').and.returnValue(of(mockUsers));
})); }));
it('should list the users if the typed result match', (done) => { it('should list the users as dropdown options if the search term has results', (done) => {
findUsersByNameSpy.and.returnValue(of(mockUsers));
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input'); const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus(); inputHTMLElement.focus();
inputHTMLElement.value = 'first'; inputHTMLElement.value = 'first';
inputHTMLElement.dispatchEvent(new Event('keyup')); inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input')); inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3); expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3);
@ -127,13 +130,19 @@ describe('PeopleCloudComponent', () => {
}); });
}); });
it('should emit selectedUser if option is valid', (done) => { it('should selectedUser and changedUsers emit, update selected users when a user is selected', (done) => {
const user = { username: 'username' };
fixture.detectChanges(); fixture.detectChanges();
spyOn(component, 'hasUserDetails').and.returnValue(true);
const selectEmitSpy = spyOn(component.selectUser, 'emit'); const selectEmitSpy = spyOn(component.selectUser, 'emit');
component.onSelect({ username: 'username' }); const changedUsersSpy = spyOn(component.changedUsers, 'emit');
component.onSelect(user);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(selectEmitSpy).toHaveBeenCalled(); expect(selectEmitSpy).toHaveBeenCalledWith(user);
expect(changedUsersSpy).toHaveBeenCalledWith([user]);
expect(component.getSelectedUsers()).toEqual([user]);
done(); done();
}); });
}); });
@ -150,9 +159,9 @@ describe('PeopleCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
inputHTMLElement.blur(); inputHTMLElement.blur();
fixture.detectChanges(); fixture.detectChanges();
const errorMessage = element.querySelector('.adf-start-task-cloud-error-message'); const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]');
expect(errorMessage).not.toBeNull(); expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toContain('ADF_CLOUD_START_TASK.ERROR.MESSAGE'); expect(errorMessage.textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND');
done(); done();
}); });
}); });
@ -173,6 +182,18 @@ describe('PeopleCloudComponent', () => {
element = fixture.nativeElement; element = fixture.nativeElement;
})); }));
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();
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
});
}));
it('should list users who have access to the app when appName is specified', (done) => { it('should list users who have access to the app when appName is specified', (done) => {
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input'); const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
inputHTMLElement.focus(); inputHTMLElement.focus();
@ -293,14 +314,34 @@ describe('PeopleCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
inputHTMLElement.blur(); inputHTMLElement.blur();
fixture.detectChanges(); fixture.detectChanges();
const errorMessage = element.querySelector('.adf-start-task-cloud-error-message'); const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]');
expect(errorMessage).not.toBeNull(); expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toContain('ADF_CLOUD_START_TASK.ERROR.MESSAGE'); expect(errorMessage.textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND');
done(); done();
}); });
}); });
}); });
describe('No preselected users', () => {
beforeEach(async () => {
fixture.detectChanges();
});
it('should not pre-select any user when preSelectUsers is empty - single mode', () => {
component.mode = 'single';
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(chips.length).toEqual(0);
});
it('should not pre-select any users when preSelectUsers 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', () => { describe('When roles defined', () => {
let checkUserHasRoleSpy: jasmine.Spy; let checkUserHasRoleSpy: jasmine.Spy;
@ -360,311 +401,170 @@ describe('PeopleCloudComponent', () => {
}); });
}); });
describe('Single Mode and Pre-selected users with no validate flag', () => { describe('Single Mode with Pre-selected users', () => {
const changes = new SimpleChange(null, mockPreselectedUsers, false);
beforeEach(async(() => { beforeEach(async(() => {
component.mode = 'single'; component.mode = 'single';
component.preSelectUsers = <any> mockPreselectedUsers; component.preSelectUsers = <any> mockPreselectedUsers;
component.ngOnChanges({ 'preSelectUsers': changes });
fixture.detectChanges(); fixture.detectChanges();
element = fixture.nativeElement; element = fixture.nativeElement;
})); }));
it('should not show chip list when mode=single', (done) => { it('should show only one mat chip with the first preSelectedUser', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeNull();
done();
});
});
it('should not pre-select any user when preSelectUsers is empty and mode=single', (done) => {
component.preSelectUsers = [];
fixture.detectChanges();
fixture.whenStable().then(() => {
const selectedUser = component.searchUserCtrl.value;
expect(selectedUser).toBeNull();
done();
});
});
});
describe('Single Mode and Pre-selected users with validate flag', () => {
beforeEach(async(() => {
component.mode = 'single';
component.validate = true;
component.preSelectUsers = <any> mockPreselectedUsers;
fixture.detectChanges();
element = fixture.nativeElement;
}));
it('should not show chip list when mode=single', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeNull();
done();
});
});
});
describe('Multiple Mode and Pre-selected users with no validate flag', () => {
const change = new SimpleChange(null, mockPreselectedUsers, false);
beforeEach(async(() => {
component.mode = 'multiple';
component.preSelectUsers = <any> mockPreselectedUsers;
fixture.detectChanges();
element = fixture.nativeElement;
alfrescoApiService = TestBed.get(AlfrescoApiService);
}));
it('should show chip list when mode=multiple', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeDefined();
done();
});
});
it('should pre-select all preSelectUsers when mode=multiple validation disabled', (done) => {
component.mode = 'multiple';
spyOn(component, 'filterPreselectUsers').and.returnValue(Promise.resolve(mockPreselectedUsers));
component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
component.selectedUsers$.subscribe((selectedUsers) => { const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(selectedUsers).toBeDefined(); expect(chips.length).toEqual(1);
expect(selectedUsers.length).toEqual(2); expect(chips[0].attributes['data-automation-id']).toEqual(`adf-people-cloud-chip-${mockPreselectedUsers[0].username}`);
expect(selectedUsers[0].id).toEqual('fake-id-2'); done();
done();
});
}); });
}); });
}); });
describe('Multiple Mode with read-only mode', () => { describe('Multiple Mode with Pre-selected Users', () => {
it('Should not show remove icon for pre-selected users if readonly property set to true', (done) => { it('Should not show remove icon for pre-selected users if readonly property set to true', (done) => {
component.mode = 'multiple'; fixture.detectChanges();
const removeUserSpy = spyOn(component.removeUser, 'emit');
component.preSelectUsers = [ component.preSelectUsers = [
{ id: mockUsers[0].id, username: mockUsers[0].username, readonly: true }, { id: mockUsers[0].id, username: mockUsers[0].username, readonly: true },
{ id: mockUsers[1].id, username: mockUsers[1].username, readonly: true } { id: mockUsers[1].id, username: mockUsers[1].username, readonly: true }
]; ];
fixture.detectChanges(); const change = new SimpleChange(null, component.preSelectUsers, false);
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"]');
expect(chipList.length).toBe(2);
expect(component.preSelectUsers[0].readonly).toBeTruthy();
expect(component.preSelectUsers[1].readonly).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(removeIcon).toBeNull();
fixture.detectChanges();
expect(removeUserSpy).not.toHaveBeenCalled();
expect(component.preSelectUsers.length).toBe(2);
expect(component.preSelectUsers[0].readonly).toBe(true, 'Not removable');
expect(component.preSelectUsers[1].readonly).toBe(true, 'not removable');
done();
});
});
});
describe('Multiple Mode and Pre-selected users with validate flag', () => {
const change = new SimpleChange(null, mockPreselectedUsers, false);
beforeEach(async(() => {
component.mode = 'multiple'; component.mode = 'multiple';
component.validate = true;
component.preSelectUsers = <any> mockPreselectedUsers;
element = fixture.nativeElement;
alfrescoApiService = TestBed.get(AlfrescoApiService);
fixture.detectChanges();
}));
it('should show chip list when mode=multiple', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const chip = element.querySelector('mat-chip-list');
expect(chip).toBeDefined();
done();
});
});
it('should pre-select all preSelectUsers when mode=multiple', (done) => {
spyOn(component, 'searchUser').and.returnValue(Promise.resolve(mockPreselectedUsers));
component.mode = 'multiple';
fixture.detectChanges();
component.ngOnChanges({ 'preSelectUsers': change }); component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges(); 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"]');
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();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip')); 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}"]`);
expect(chips.length).toBe(2); 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(); done();
}); });
}); });
it('should emit removeUser when a selected user is removed if mode=multiple', (done) => { describe('Component readonly mode', () => {
spyOn(component.removeUser, 'emit'); const change = new SimpleChange(null, mockPreselectedUsers, false);
component.mode = 'multiple';
fixture.detectChanges(); it('should chip list be disabled and show one single chip - single mode', () => {
fixture.whenStable().then(() => { component.mode = 'single';
component.readOnly = true;
component.preSelectUsers = <any> mockPreselectedUsers;
component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges(); fixture.detectChanges();
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon')); const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
removeIcon.nativeElement.click(); const chipList = fixture.nativeElement.querySelector('mat-chip-list');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(1);
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
});
it('should chip list be disabled and show mat chips for all the preselected users - multiple mode', () => {
component.mode = 'multiple';
component.readOnly = true;
component.preSelectUsers = <any> mockPreselectedUsers;
component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges(); fixture.detectChanges();
expect(component.removeUser.emit).toHaveBeenCalled(); const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
done(); const chipList = fixture.nativeElement.querySelector('mat-chip-list');
}); expect(chips).toBeDefined();
}); expect(chipList).toBeDefined();
expect(chips.length).toBe(2);
it('should emit warning if are invalid users', (done) => { expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
findUserByUsernameSpy.and.returnValue(Promise.resolve([]));
const warnMessage = { message: 'INVALID_PRESELECTED_USERS', users: [{ username: 'invalidUsername' }] };
component.validate = true;
component.preSelectUsers = <any> [{ username: 'invalidUsername' }];
fixture.detectChanges();
component.loadSinglePreselectUser();
component.warning.subscribe((response) => {
expect(response).toEqual(warnMessage);
expect(response.message).toEqual(warnMessage.message);
expect(response.users).toEqual(warnMessage.users);
expect(response.users[0].username).toEqual('invalidUsername');
done();
});
});
it('should filter user by id if validate true', async(() => {
const findByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
component.mode = 'multiple';
component.validate = true;
fixture.detectChanges();
component.preSelectUsers = <any> [{ id: mockUsers[0].id }, { id: mockUsers[1].id }];
component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges();
component.filterPreselectUsers().then((result: any) => {
fixture.detectChanges();
expect(findByIdSpy).toHaveBeenCalled();
expect(component.userExists(result[0])).toEqual(true);
expect(result[1].id).toBe(mockUsers[0].id);
});
}));
it('should filter user by username if validate true', (done) => {
findUserByUsernameSpy.and.returnValue(of(mockUsers));
component.mode = 'multiple';
component.validate = true;
component.preSelectUsers = <any> [{ username: mockUsers[1].username }, { username: mockUsers[2].username }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.filterPreselectUsers().then((result) => {
expect(findUserByUsernameSpy).toHaveBeenCalled();
expect(component.userExists(result[0])).toEqual(true);
expect(component.userExists(result[1])).toEqual(true);
done();
});
});
});
it('should filter user by email if validate true', (done) => {
const findUserByEmailSpy = spyOn(identityService, 'findUserByEmail').and.returnValue(of(mockUsers));
fixture.detectChanges();
component.mode = 'multiple';
component.validate = true;
component.preSelectUsers = <any> [{ email: mockUsers[1].email }, { email: mockUsers[2].email }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.filterPreselectUsers().then((result) => {
expect(findUserByEmailSpy).toHaveBeenCalled();
expect(component.userExists(result[0])).toEqual(true);
expect(component.userExists(result[1])).toEqual(true);
done();
});
});
});
it('should search user by id on single selection mode', (done) => {
const findUserByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
component.mode = 'single';
component.validate = true;
fixture.detectChanges();
component.preSelectUsers = <any> [{ id: mockUsers[0].id }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectUsers().then((result) => {
expect(findUserByIdSpy).toHaveBeenCalled();
expect(result.length).toEqual(1);
done();
});
});
});
it('should not preselect any user if email is invalid and validation enable', (done) => {
const findUserByEmailSpy = spyOn(identityService, 'findUserByEmail').and.returnValue(of([]));
component.mode = 'single';
component.validate = true;
component.preSelectUsers = <any> [{ email: 'invalid email' }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectUsers().then((result) => {
expect(findUserByEmailSpy).toHaveBeenCalled();
expect(result.length).toEqual(0);
done();
});
});
});
it('should not preselect any user if id is invalid and validation enable', (done) => {
const findUserByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of([]));
component.mode = 'single';
component.validate = true;
component.preSelectUsers = <any> [{ id: 'invalid id' }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectUsers().then((result) => {
expect(findUserByIdSpy).toHaveBeenCalled();
expect(result.length).toEqual(0);
done();
});
});
});
it('should not preselect any user if username is invalid and validation enable', (done) => {
findUserByUsernameSpy.and.returnValue(of([]));
component.mode = 'single';
component.validate = true;
component.preSelectUsers = <any> [{ username: 'invalid user' }];
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectUsers().then((result) => {
fixture.detectChanges();
expect(findUserByUsernameSpy).toHaveBeenCalled();
expect(result.length).toEqual(0);
done();
});
});
});
it('should remove duplicated preselcted users when a user is duplicated', () => {
spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
component.mode = 'multiple';
component.validate = true;
component.preSelectUsers = <any> [{ id: mockUsers[0].id }, { id: mockUsers[0].id }];
component.ngOnChanges({ 'preSelectUsers': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
component.validatePreselectUsers().then((result) => {
expect(result.length).toEqual(1);
});
}); });
}); });
}); });
describe('Preselected users and validation enabled', () => {
it('should check validation only for the first user and emit warning when user is invalid - single mode', (done) => {
spyOn(identityService, 'findUserById').and.returnValue(Promise.resolve([]));
spyOn(component, 'hasUserDetails').and.returnValue(false);
const expectedWarning = {
message: 'INVALID_PRESELECTED_USERS',
users: [{
id: mockPreselectedUsers[0].id,
username: mockPreselectedUsers[0].username
}]
};
component.warning.subscribe(warning => {
expect(warning).toEqual(expectedWarning);
done();
});
component.mode = 'single';
component.validate = true;
component.preSelectUsers = <any> [mockPreselectedUsers[0], mockPreselectedUsers[1]];
component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) });
});
it('should check validation for all the users and emit warning - multiple mode', (done) => {
spyOn(identityService, 'findUserById').and.returnValue(Promise.resolve(undefined));
const expectedWarning = {
message: 'INVALID_PRESELECTED_USERS',
users: [
{
id: mockPreselectedUsers[0].id,
username: mockPreselectedUsers[0].username
},
{
id: mockPreselectedUsers[1].id,
username: mockPreselectedUsers[1].username
}]
};
component.warning.subscribe(warning => {
expect(warning).toEqual(expectedWarning);
done();
});
component.mode = 'multiple';
component.validate = true;
component.preSelectUsers = <any> [mockPreselectedUsers[0], mockPreselectedUsers[1]];
component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) });
});
});
it('should removeDuplicateUsers return only unique users', () => {
const duplicatedUsers = [{ id: mockUsers[0].id }, { id: mockUsers[0].id }];
expect(component.removeDuplicatedUsers(duplicatedUsers)).toEqual([{ id: mockUsers[0].id }]);
});
}); });

View File

@ -16,10 +16,27 @@
*/ */
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input, ViewChild, ElementRef, SimpleChanges, OnChanges, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; import {
Component,
OnInit,
Output,
EventEmitter,
ViewEncapsulation,
Input,
SimpleChanges,
OnChanges,
OnDestroy,
ChangeDetectionStrategy,
ViewChild, ElementRef
} from '@angular/core';
import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators'; import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators';
import { FullNamePipe, IdentityUserModel, IdentityUserService, LogService } from '@alfresco/adf-core'; import {
FullNamePipe,
IdentityUserModel,
IdentityUserService,
LogService
} from '@alfresco/adf-core';
import { trigger, state, style, transition, animate } from '@angular/animations'; import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({ @Component({
@ -79,7 +96,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
/** FormControl to search the user */ /** FormControl to search the user */
@Input() @Input()
searchUserCtrl: FormControl = new FormControl(); searchUserCtrl: FormControl = new FormControl({ value: '', disabled: false });
/** Placeholder translation key /** Placeholder translation key
*/ */
@ -106,166 +123,63 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
private userInput: ElementRef<HTMLInputElement>; private userInput: ElementRef<HTMLInputElement>;
private _searchUsers: IdentityUserModel[] = []; private _searchUsers: IdentityUserModel[] = [];
private selectedUsersSubject: BehaviorSubject<IdentityUserModel[]>;
private searchUsersSubject: BehaviorSubject<IdentityUserModel[]>; private searchUsersSubject: BehaviorSubject<IdentityUserModel[]>;
private onDestroy$ = new Subject<boolean>(); private onDestroy$ = new Subject<boolean>();
selectedUsers: IdentityUserModel[] = []; selectedUsers: IdentityUserModel[] = [];
selectedUsers$: Observable<IdentityUserModel[]>; invalidUsers: IdentityUserModel[] = [];
searchUsers$: Observable<IdentityUserModel[]>; searchUsers$: Observable<IdentityUserModel[]>;
_subscriptAnimationState: string = 'enter'; _subscriptAnimationState: string = 'enter';
clientId: string; clientId: string;
isFocused: boolean; isFocused: boolean;
invalidUsers: IdentityUserModel[] = [];
currentTimeout: any; currentTimeout: any;
validateUsersMessage: string;
searchedValue = '';
constructor(private identityUserService: IdentityUserService, private logService: LogService) { isLoading = false;
}
constructor(
private identityUserService: IdentityUserService,
private logService: LogService) {}
ngOnInit() { ngOnInit() {
if (this.hasPreSelectUsers()) {
this.selectedUsers = [...this.preSelectUsers];
}
this.initSubjects();
this.initSearch();
if (this.appName) {
this.disableSearch();
this.loadClientId();
}
}
ngOnChanges(changes: SimpleChanges) {
this.initSubjects();
if (this.isPreselectedUserChanged(changes)) {
if (this.isValidationEnabled()) {
this.loadPreSelectUsers();
} else {
this.loadNoValidationPreselectUsers();
}
}
if (changes.appName && this.isAppNameChanged(changes.appName)) {
this.disableSearch();
this.loadClientId();
} else {
this.enableSearch();
}
}
ngOnDestroy() {
clearTimeout(this.currentTimeout);
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
initSubjects() {
if (this.selectedUsersSubject === undefined) {
this.selectedUsersSubject = new BehaviorSubject<IdentityUserModel[]>(this.preSelectUsers);
this.selectedUsers$ = this.selectedUsersSubject.asObservable();
}
if (this.searchUsersSubject === undefined) { if (this.searchUsersSubject === undefined) {
this.searchUsersSubject = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers); this.searchUsersSubject = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
this.searchUsers$ = this.searchUsersSubject.asObservable(); this.searchUsers$ = this.searchUsersSubject.asObservable();
} }
this.loadClientId();
this.initSearch();
} }
private isAppNameChanged(change) { ngOnChanges(changes: SimpleChanges) {
return change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
}
isPreselectedUserChanged(changes: SimpleChanges) { if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
return changes.preSelectUsers if (this.hasPreSelectUsers()) {
&& changes.preSelectUsers.previousValue !== changes.preSelectUsers.currentValue this.loadPreSelectUsers();
&& this.hasPreSelectUsers(); } else if (this.hasPreselectedUsersCleared(changes)) {
} this.selectedUsers = [];
this.invalidUsers = [];
isValidationEnabled() {
return this.validate === true;
}
async validatePreselectUsers(): Promise<any> {
let filteredPreselectUsers: IdentityUserModel[];
let validUsers: IdentityUserModel[] = [];
try {
filteredPreselectUsers = await this.filterPreselectUsers();
} catch (error) {
validUsers = [];
this.logService.error(error);
}
await this.preSelectUsers.map((user: IdentityUserModel) => {
const validUser = this.isValidUser(filteredPreselectUsers, user);
if (validUser) {
validUsers.push(validUser);
} else {
this.invalidUsers.push(user);
} }
});
validUsers = this.removeDuplicatedUsers(validUsers);
return validUsers;
}
private removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] { if (!this.isValidationEnabled()) {
return users.filter((user, index, self) => this.invalidUsers = [];
index === self.findIndex((auxUser) => {
return user.id === auxUser.id && user.username === auxUser.username;
}));
}
async filterPreselectUsers() {
const promiseBatch = this.preSelectUsers.map(async (user: IdentityUserModel) => {
let result: any;
try {
result = await this.searchUser(user);
} catch (error) {
result = [];
this.logService.error(error);
} }
const isUserValid: boolean = this.userExists(result);
return isUserValid ? result : null;
});
return Promise.all(promiseBatch);
}
async searchUser(user: IdentityUserModel) {
let key: string = '';
if (user.id) {
key = 'id';
} else if (user.email) {
key = 'email';
} else if (user.username) {
key = 'username';
} }
switch (key) { if (changes.appName && this.isAppNameChanged(changes.appName)) {
case 'id': return this.identityUserService.findUserById(user[key]).toPromise(); this.loadClientId();
case 'username': return (await this.identityUserService.findUserByUsername(user[key]).toPromise())[0]; this.initSearch();
case 'email': return (await this.identityUserService.findUserByEmail(user[key]).toPromise())[0];
default: return of([]);
} }
} }
private isValidUser(filteredUsers: IdentityUserModel[], user: IdentityUserModel) { private async loadClientId() {
return filteredUsers.find((filteredUser: IdentityUserModel) => { this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
return filteredUser && if (this.clientId) {
(filteredUser.id === user.id || this.searchUserCtrl.enable();
filteredUser.username === user.username || }
filteredUser.email === user.email);
});
}
public userExists(result: IdentityUserModel): boolean {
return result
&& (result.id !== undefined
|| result.username !== undefined
|| result.email !== undefined);
} }
private initSearch() { private initSearch() {
@ -276,12 +190,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return typeof value === 'string'; return typeof value === 'string';
}), }),
tap((value) => { tap((value) => {
this.searchedValue = value;
if (value) { if (value) {
this.setError(); this.setTypingError();
} else {
if (!this.isMultipleMode()) {
this.removeUser.emit();
}
} }
}), }),
tap(() => { tap(() => {
@ -296,7 +207,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}), }),
mergeMap((user: any) => { mergeMap((user: any) => {
if (this.appName) { if (this.appName) {
return this.checkUserHasAccess(user.id).pipe( return this.checkUserHasAccess(user.id).pipe(
mergeMap((hasRole) => { mergeMap((hasRole) => {
return hasRole ? of(user) : of(); return hasRole ? of(user) : of();
@ -315,6 +225,20 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}); });
} }
ngOnDestroy() {
clearTimeout(this.currentTimeout);
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
private isAppNameChanged(change): boolean {
return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
}
isValidationEnabled(): boolean {
return this.validate === true;
}
private checkUserHasAccess(userId: string): Observable<boolean> { private checkUserHasAccess(userId: string): Observable<boolean> {
if (this.hasRoles()) { if (this.hasRoles()) {
return this.identityUserService.checkUserHasAnyClientAppRole(userId, this.clientId, this.roles); return this.identityUserService.checkUserHasAnyClientAppRole(userId, this.clientId, this.roles);
@ -345,79 +269,105 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return false; return false;
} }
private loadPreSelectUsers() { private async loadPreSelectUsers() {
if (!this.isMultipleMode()) { this.selectedUsers = [];
this.loadSinglePreselectUser();
if (this.isSingleMode()) {
this.selectedUsers = [this.preSelectUsers[0]];
} else { } else {
this.loadMultiplePreselectUsers(); this.selectedUsers = this.removeDuplicatedUsers(this.preSelectUsers);
}
if (this.isValidationEnabled()) {
this.isLoading = true;
await this.validatePreselectUsers();
} }
} }
async loadNoValidationPreselectUsers() { async validatePreselectUsers(): Promise<any> {
this.selectedUsers = [...this.removeDuplicatedUsers(this.preSelectUsers)]; this.invalidUsers = [];
const validUsers: IdentityUserModel[] = [];
if (this.isMultipleMode()) { let preselectedUsersToValidate: IdentityUserModel[] = [];
this.selectedUsersSubject.next(this.selectedUsers);
if (this.isSingleMode()) {
preselectedUsersToValidate = [this.preSelectUsers[0]];
} else { } else {
preselectedUsersToValidate = this.removeDuplicatedUsers(this.preSelectUsers);
if (this.currentTimeout) {
clearTimeout(this.currentTimeout);
}
this.currentTimeout = setTimeout(() => {
this.searchUserCtrl.setValue(this.selectedUsers[0]);
this.onSelect(this.selectedUsers[0]);
}, 0);
} }
}
public async loadSinglePreselectUser() { await Promise.all(preselectedUsersToValidate.map(async (user: IdentityUserModel) => {
const users = await this.validatePreselectUsers(); try {
if (users && users.length > 0) { const validationResult = await this.searchUser(user);
this.checkPreselectValidationErrors(); if (!this.hasUserDetails(validationResult)) {
this.searchUserCtrl.setValue(users[0]); this.invalidUsers.push(user);
} else { } else {
this.checkPreselectValidationErrors(); validUsers.push(validationResult);
}
}
public async loadMultiplePreselectUsers() {
const users = await this.validatePreselectUsers();
if (users && users.length > 0) {
this.checkPreselectValidationErrors();
this.selectedUsers = [...this.alignPreselectedReadonlyUsersAfterValidation(users)];
this.selectedUsersSubject.next(this.selectedUsers);
} else {
this.checkPreselectValidationErrors();
}
}
private alignPreselectedReadonlyUsersAfterValidation(users: IdentityUserModel[]) {
this.preSelectUsers.forEach((preSelectedUser, index) => {
if (users[index]) {
if ((preSelectedUser.id === users[index].id) || (preSelectedUser.username === users[index].username)) {
users[index].readonly = preSelectedUser.readonly;
} }
} catch (error) {
this.invalidUsers.push(user);
this.logService.error(error);
} }
}));
this.checkPreselectValidationErrors();
this.alignUsersAfterValidation(validUsers);
this.isLoading = false;
}
private alignUsersAfterValidation(validatedUsers: IdentityUserModel[]) {
this.selectedUsers.forEach((selectedUser, index) => {
validatedUsers.forEach(validatedUser => {
if ((selectedUser.id === validatedUser.id) || (selectedUser.username === validatedUser.username)
|| (selectedUser.email === validatedUser.email)) {
validatedUser.readonly = selectedUser.readonly;
this.selectedUsers[index] = validatedUser;
}
});
}); });
return users; }
async searchUser(user: IdentityUserModel) {
let key: string = '';
if (user.id) {
key = 'id';
} else if (user.email) {
key = 'email';
} else if (user.username) {
key = 'username';
}
switch (key) {
case 'id':
return this.identityUserService.findUserById(user[key]).toPromise();
case 'username':
return (await this.identityUserService.findUserByUsername(user[key]).toPromise())[0];
case 'email':
return (await this.identityUserService.findUserByEmail(user[key]).toPromise())[0];
default:
return of([]);
}
}
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;
}));
} }
public checkPreselectValidationErrors() { public checkPreselectValidationErrors() {
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
if (this.invalidUsers.length > 0) { if (this.invalidUsers.length > 0) {
this.warning.emit({ this.generateInvalidUsersMessage();
message: 'INVALID_PRESELECTED_USERS',
users: this.invalidUsers
});
} }
}
private async loadClientId() { this.warning.emit({
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise(); message: 'INVALID_PRESELECTED_USERS',
users: this.invalidUsers
if (this.clientId) { });
this.enableSearch();
}
} }
onSelect(user: IdentityUserModel) { onSelect(user: IdentityUserModel) {
@ -425,26 +375,66 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
if (this.isMultipleMode()) { if (this.isMultipleMode()) {
if (!this.isUserAlreadySelected(user)) { if (!this.isUserAlreadySelected(user)) {
this.selectedUsers.push(user); this.selectedUsers.push(user);
this.selectedUsersSubject.next(this.selectedUsers);
} }
this.userInput.nativeElement.value = '';
this.searchUserCtrl.setValue('');
} else { } else {
this.invalidUsers = [];
this.selectedUsers = [user]; this.selectedUsers = [user];
} }
this.userInput.nativeElement.value = '';
this.searchUserCtrl.setValue('');
this.changedUsers.emit(this.selectedUsers); this.changedUsers.emit(this.selectedUsers);
this.clearError();
this.resetSearchUsers(); this.resetSearchUsers();
} }
onRemove(user: IdentityUserModel) { onRemove(userToRemove: IdentityUserModel) {
this.removeUser.emit(user); this.removeUser.emit(userToRemove);
const indexToRemove = this.selectedUsers.findIndex((selectedUser) => { return selectedUser.id === user.id; }); const indexToRemove = this.selectedUsers.findIndex((selectedUser: IdentityUserModel) => {
return selectedUser.id === userToRemove.id;
});
this.selectedUsers.splice(indexToRemove, 1); this.selectedUsers.splice(indexToRemove, 1);
this.selectedUsersSubject.next(this.selectedUsers);
this.changedUsers.emit(this.selectedUsers); this.changedUsers.emit(this.selectedUsers);
this.searchUserCtrl.markAsDirty();
if (this.isValidationEnabled()) {
this.removeUserFromValidation(userToRemove.username);
this.checkPreselectValidationErrors();
}
}
private removeUserFromValidation(username: string) {
const indexToRemove = this.invalidUsers.findIndex((invalidUser) => {
return invalidUser.username === username;
});
if (indexToRemove !== -1) {
this.invalidUsers.splice(indexToRemove, 1);
}
}
hasUserDetails(user: IdentityUserModel): boolean {
return user && (user.id !== undefined || user.username !== undefined || user.email !== undefined);
}
generateInvalidUsersMessage() {
this.validateUsersMessage = '';
this.invalidUsers.forEach((invalidUser: IdentityUserModel, index) => {
if (index === this.invalidUsers.length - 1) {
this.validateUsersMessage += `${invalidUser.username} `;
} else {
this.validateUsersMessage += `${invalidUser.username}, `;
}
});
}
setTypingError() {
this.searchUserCtrl.setErrors({ searchTypingError: true, ...this.searchUserCtrl.errors });
}
hasPreselectError(): boolean {
return this.invalidUsers && this.invalidUsers.length > 0;
} }
getDisplayName(user): string { getDisplayName(user): string {
@ -455,21 +445,49 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return this.mode === PeopleCloudComponent.MODE_MULTIPLE; return this.mode === PeopleCloudComponent.MODE_MULTIPLE;
} }
isSingleMode(): boolean {
return this.mode === PeopleCloudComponent.MODE_SINGLE;
}
private isSingleSelectionReadonly(): boolean {
return this.isSingleMode() && this.selectedUsers.length === 1 && this.selectedUsers[0].readonly === true;
}
private hasPreSelectUsers(): boolean { 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 isValidationChanged(changes): boolean {
return changes && changes.validate && changes.validate.currentValue !== changes.validate.previousValue;
}
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.length === 0;
}
private resetSearchUsers() { private resetSearchUsers() {
this._searchUsers = []; this._searchUsers = [];
this.searchUsersSubject.next(this._searchUsers); this.searchUsersSubject.next(this._searchUsers);
} }
private setError() { getSelectedUsers(): IdentityUserModel[] {
this.searchUserCtrl.setErrors({ invalid: true }); return this.selectedUsers;
} }
private clearError() { isReadonly(): boolean {
this.searchUserCtrl.setErrors(null); return this.readOnly || this.isSingleSelectionReadonly();
}
isValidationLoading(): boolean {
return this.isValidationEnabled() && this.isLoading;
} }
setFocus(isFocused: boolean) { setFocus(isFocused: boolean) {
@ -480,16 +498,15 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return !!this.searchUserCtrl.errors; return !!this.searchUserCtrl.errors;
} }
hasErrorMessage(): boolean { getValidationPattern(): string {
return !this.isFocused && this.hasError(); return this.searchUserCtrl.errors.pattern.requiredPattern;
} }
private disableSearch() { getValidationMaxLength(): string {
this.searchUserCtrl.disable(); return this.searchUserCtrl.errors.maxlength.requiredLength;
} }
private enableSearch() { getValidationMinLength(): string {
this.searchUserCtrl.enable(); return this.searchUserCtrl.errors.minlength.requiredLength;
} }
} }

View File

@ -29,6 +29,11 @@ export class PeopleCloudComponentPage {
await this.peopleCloudSearch.sendKeys(protractor.Key.BACK_SPACE); await this.peopleCloudSearch.sendKeys(protractor.Key.BACK_SPACE);
} }
async clearAssigneeFromChip(username: string): Promise<void> {
const assigneeChipRemoveIcon = element(by.css(`[data-automation-id="adf-people-cloud-chip-remove-icon-${username}"]`));
await assigneeChipRemoveIcon.click();
}
async searchAssigneeAndSelect(name: string): Promise<void> { async searchAssigneeAndSelect(name: string): Promise<void> {
await BrowserActions.clearSendKeys(this.peopleCloudSearch, name); await BrowserActions.clearSendKeys(this.peopleCloudSearch, name);
await this.selectAssigneeFromList(name); await this.selectAssigneeFromList(name);