mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-09-17 14:21:29 +00:00
[AAE-7765] Improved display mandatory form fields (#7531)
* [MNT-22765] Improved display mandatory form fields * [MNT-22765] added unit tests * [MNT-22765] fixed test with error icon on rest fail * Trigger travis * [MNT-22765] removed underscore from var name * [AAE-7765] removed underscore from unit test * [AAE-7765] fixed css lint * [AAE-7765] fixed e2e error message css class * [AAE-7765] fixed storybook e2e
This commit is contained in:
@@ -42,6 +42,7 @@ Searches Groups.
|
|||||||
| mode | [`ComponentSelectionMode`](../../../lib/process-services-cloud/src/lib/types.ts) | "single" | Group selection mode (single/multiple). |
|
| mode | [`ComponentSelectionMode`](../../../lib/process-services-cloud/src/lib/types.ts) | "single" | Group selection mode (single/multiple). |
|
||||||
| 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. |
|
| 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. |
|
||||||
| readOnly | `boolean` | false | Show the info in readonly mode |
|
| readOnly | `boolean` | false | Show the info in readonly mode |
|
||||||
|
| required | `boolean` | false | Mark this field as required |
|
||||||
| roles | `string[]` | \[] | Role names of the groups to be listed. |
|
| roles | `string[]` | \[] | Role names of the groups to be listed. |
|
||||||
| searchGroupsControl | `FormControl` | | FormControl to search the group |
|
| searchGroupsControl | `FormControl` | | FormControl to search the group |
|
||||||
| title | `string` | | Title of the field |
|
| title | `string` | | Title of the field |
|
||||||
|
@@ -30,6 +30,7 @@ Allows one or more users to be selected (with auto-suggestion) based on the inpu
|
|||||||
| mode | [`ComponentSelectionMode`](../../../lib/process-services-cloud/src/lib/types.ts) | "single" | User selection mode (single/multiple). |
|
| mode | [`ComponentSelectionMode`](../../../lib/process-services-cloud/src/lib/types.ts) | "single" | 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 |
|
||||||
| readOnly | `boolean` | false | Show the info in readonly mode |
|
| readOnly | `boolean` | false | Show the info in readonly mode |
|
||||||
|
| required | `boolean` | false | Mark this field as required |
|
||||||
| roles | `string[]` | | Role names of the users to be listed. |
|
| roles | `string[]` | | Role names of the users to be listed. |
|
||||||
| searchUserCtrl | `FormControl` | | FormControl to search the user |
|
| searchUserCtrl | `FormControl` | | FormControl to search the user |
|
||||||
| title | `string` | | Placeholder translation key |
|
| title | `string` | | Placeholder translation key |
|
||||||
|
@@ -40,11 +40,12 @@ test.describe('Groups component stories tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Invalid Preselected Groups', async ({ processServicesCloud, groupComponent }) => {
|
test('Invalid Preselected Groups', async ({ processServicesCloud, groupComponent }) => {
|
||||||
const expectedWarningMessage = 'warning No group found with the name invalid groups';
|
const expectedWarningMessage = 'No group found with the name invalid groups';
|
||||||
|
const expectedWarningIcon = 'error_outline';
|
||||||
|
|
||||||
await processServicesCloud.navigateTo({ componentName: 'group', story: 'invalid-preselected-groups' });
|
await processServicesCloud.navigateTo({ componentName: 'group', story: 'invalid-preselected-groups' });
|
||||||
|
|
||||||
await expect(groupComponent.error.content).toContainText(expectedWarningMessage);
|
await expect(groupComponent.error.content).toContainText(expectedWarningIcon + expectedWarningMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -39,11 +39,12 @@ test.describe('People component stories tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Invalid Preselected Users', async ({ processServicesCloud, peopleComponent }) => {
|
test('Invalid Preselected Users', async ({ processServicesCloud, peopleComponent }) => {
|
||||||
const expectedWarningMessage = 'warning No user found with the username invalid user';
|
const expectedWarningMessage = 'No user found with the username invalid user';
|
||||||
|
const expectedWarningIcon = 'error_outline';
|
||||||
|
|
||||||
await processServicesCloud.navigateTo({ componentName: 'people', story: 'invalid-preselected-users' });
|
await processServicesCloud.navigateTo({ componentName: 'people', story: 'invalid-preselected-users' });
|
||||||
|
|
||||||
await expect(peopleComponent.error.content).toContainText(expectedWarningMessage);
|
await expect(peopleComponent.error.content).toContainText(expectedWarningIcon + expectedWarningMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Excluded Users', async ({ processServicesCloud, peopleComponent }) => {
|
test('Excluded Users', async ({ processServicesCloud, peopleComponent }) => {
|
||||||
|
@@ -45,43 +45,39 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
const typeIntoInput = (targetInput: HTMLInputElement, message: string) => {
|
const typeIntoInput = (targetInput: HTMLInputElement, message: string) => {
|
||||||
expect(targetInput).not.toBeNull('Expected input to set to be valid and not null');
|
expect(targetInput).toBeTruthy('Expected input to set to be valid and not null');
|
||||||
targetInput.value = message;
|
targetInput.value = message;
|
||||||
targetInput.dispatchEvent(new Event('input'));
|
targetInput.dispatchEvent(new Event('input'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeIntoDate = (targetInput: DebugElement, date: { srcElement: { value: string } }) => {
|
const typeIntoDate = (targetInput: DebugElement, date: { srcElement: { value: string } }) => {
|
||||||
expect(targetInput).not.toBeNull('Expected input to set to be valid and not null');
|
expect(targetInput).toBeTruthy('Expected input to set to be valid and not null');
|
||||||
targetInput.triggerEventHandler('change', date);
|
targetInput.triggerEventHandler('change', date);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectElementToBeHidden = (targetElement: HTMLElement): void => {
|
const expectElementToBeHidden = (targetElement: HTMLElement): void => {
|
||||||
expect(targetElement).not.toBeNull();
|
expect(targetElement).toBeTruthy();
|
||||||
expect(targetElement).toBeDefined();
|
|
||||||
expect(targetElement.hidden).toBe(true, `${targetElement.id} should be hidden but it is not`);
|
expect(targetElement.hidden).toBe(true, `${targetElement.id} should be hidden but it is not`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectElementToBeVisible = (targetElement: HTMLElement): void => {
|
const expectElementToBeVisible = (targetElement: HTMLElement): void => {
|
||||||
expect(targetElement).not.toBeNull();
|
expect(targetElement).toBeTruthy();
|
||||||
expect(targetElement).toBeDefined();
|
|
||||||
expect(targetElement.hidden).toBe(false, `${targetElement.id} should be visibile but it is not`);
|
expect(targetElement.hidden).toBe(false, `${targetElement.id} should be visibile but it is not`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectInputElementValueIs = (targetElement: HTMLInputElement, value: string): void => {
|
const expectInputElementValueIs = (targetElement: HTMLInputElement, value: string): void => {
|
||||||
expect(targetElement).not.toBeNull();
|
expect(targetElement).toBeTruthy();
|
||||||
expect(targetElement).toBeDefined();
|
|
||||||
expect(targetElement.value).toBe(value, `invalid value for ${targetElement.name}`);
|
expect(targetElement.value).toBe(value, `invalid value for ${targetElement.name}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectElementToBeInvalid = (fieldId: string, fixture: ComponentFixture<FormRendererComponent>): void => {
|
const expectElementToBeInvalid = (fieldId: string, fixture: ComponentFixture<FormRendererComponent>): void => {
|
||||||
const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`);
|
const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`);
|
||||||
expect(invalidElementContainer).not.toBeNull();
|
expect(invalidElementContainer).toBeTruthy();
|
||||||
expect(invalidElementContainer).toBeDefined();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectElementToBeValid = (fieldId: string, fixture: ComponentFixture<FormRendererComponent>): void => {
|
const expectElementToBeValid = (fieldId: string, fixture: ComponentFixture<FormRendererComponent>): void => {
|
||||||
const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`);
|
const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`);
|
||||||
expect(invalidElementContainer).toBeNull();
|
expect(invalidElementContainer).toBeFalsy();
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Form Renderer Component', () => {
|
describe('Form Renderer Component', () => {
|
||||||
@@ -407,6 +403,12 @@ describe('Form Renderer Component', () => {
|
|||||||
|
|
||||||
const numberInputRequired: HTMLInputElement = fixture.nativeElement.querySelector('#Number0x8cbv');
|
const numberInputRequired: HTMLInputElement = fixture.nativeElement.querySelector('#Number0x8cbv');
|
||||||
expectElementToBeVisible(numberInputRequired);
|
expectElementToBeVisible(numberInputRequired);
|
||||||
|
expectElementToBeValid('Number0x8cbv', fixture);
|
||||||
|
|
||||||
|
numberInputRequired.dispatchEvent(new Event('blur'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
expectElementToBeInvalid('Number0x8cbv', fixture);
|
expectElementToBeInvalid('Number0x8cbv', fixture);
|
||||||
|
|
||||||
typeIntoInput(numberInputRequired, '5');
|
typeIntoInput(numberInputRequired, '5');
|
||||||
@@ -444,6 +446,7 @@ describe('Form Renderer Component', () => {
|
|||||||
expectElementToBeVisible(numberInputElement);
|
expectElementToBeVisible(numberInputElement);
|
||||||
expectElementToBeValid('Number0him2z', fixture);
|
expectElementToBeValid('Number0him2z', fixture);
|
||||||
|
|
||||||
|
numberInputElement.dispatchEvent(new Event('blur'));
|
||||||
typeIntoInput(numberInputElement, '9');
|
typeIntoInput(numberInputElement, '9');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<div class="adf-amount-widget__container adf-amount-widget {{field.className}}"
|
<div class="adf-amount-widget__container adf-amount-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid"
|
[class.adf-invalid]="!field.isValid && isTouched()"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label"
|
<label class="adf-label"
|
||||||
[attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
[attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true">
|
<mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true">
|
||||||
<span matPrefix class="adf-amount-widget__prefix-spacing">{{ currency }} </span>
|
<span matPrefix class="adf-amount-widget__prefix-spacing">{{ currency }} </span>
|
||||||
<input matInput
|
<input matInput
|
||||||
@@ -17,9 +17,10 @@
|
|||||||
[value]="field.value"
|
[value]="field.value"
|
||||||
[(ngModel)]="field.value"
|
[(ngModel)]="field.value"
|
||||||
(ngModelChange)="onFieldChanged(field)"
|
(ngModelChange)="onFieldChanged(field)"
|
||||||
[disabled]="field.readOnly">
|
[disabled]="field.readOnly"
|
||||||
|
(blur)="markAsTouched()">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()"
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()"
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -20,14 +20,16 @@ import { FormFieldModel } from './../core/form-field.model';
|
|||||||
import { AmountWidgetComponent, ADF_AMOUNT_SETTINGS } from './amount.widget';
|
import { AmountWidgetComponent, ADF_AMOUNT_SETTINGS } from './amount.widget';
|
||||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
import { FormBaseModule } from '../../../form-base.module';
|
import { FormBaseModule } from '../../../form-base.module';
|
||||||
import { FormModel } from '../core';
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { FormModel } from '../core/form.model';
|
||||||
|
|
||||||
describe('AmountWidgetComponent', () => {
|
describe('AmountWidgetComponent', () => {
|
||||||
|
|
||||||
let widget: AmountWidgetComponent;
|
let widget: AmountWidgetComponent;
|
||||||
let fixture: ComponentFixture<AmountWidgetComponent>;
|
let fixture: ComponentFixture<AmountWidgetComponent>;
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
setupTestBed({
|
setupTestBed({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -39,8 +41,8 @@ describe('AmountWidgetComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(AmountWidgetComponent);
|
fixture = TestBed.createComponent(AmountWidgetComponent);
|
||||||
|
|
||||||
widget = fixture.componentInstance;
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup currency from field', () => {
|
it('should setup currency from field', () => {
|
||||||
@@ -78,6 +80,38 @@ describe('AmountWidgetComponent', () => {
|
|||||||
widget.ngOnInit();
|
widget.ngOnInit();
|
||||||
expect(widget.placeholder).toBe('1234');
|
expect(widget.placeholder).toBe('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.AMOUNT,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const amount = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
amount.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AmountWidgetComponent - rendering', () => {
|
describe('AmountWidgetComponent - rendering', () => {
|
||||||
|
@@ -1,16 +1,19 @@
|
|||||||
<div [ngClass]="field.className"
|
<div [ngClass]="field.className"
|
||||||
[class.adf-invalid]="!field.isValid">
|
[class.adf-invalid]="!field.isValid && isTouched()">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
color="primary"
|
color="primary"
|
||||||
[required]="field.required"
|
[required]="isRequired()"
|
||||||
[disabled]="field.readOnly || readOnly"
|
[disabled]="field.readOnly || readOnly"
|
||||||
[(ngModel)]="field.value"
|
[(ngModel)]="field.value"
|
||||||
(ngModelChange)="onFieldChanged(field)"
|
(ngModelChange)="onFieldChanged(field)"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(click)="markAsTouched()"
|
||||||
matTooltipPosition="right"
|
matTooltipPosition="right"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
{{field.name | translate }}
|
{{field.name | translate }}
|
||||||
<span *ngIf="field.required" >*</span>
|
<span class="adf-asterisk" *ngIf="isRequired()" >*</span>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -21,9 +21,9 @@ import { FormFieldModel } from '../core/form-field.model';
|
|||||||
import { FormModel } from '../core/form.model';
|
import { FormModel } from '../core/form.model';
|
||||||
import { CheckboxWidgetComponent } from './checkbox.widget';
|
import { CheckboxWidgetComponent } from './checkbox.widget';
|
||||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
import { FormBaseModule } from 'core/form/form-base.module';
|
import { FormBaseModule } from '../../../form-base.module';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { TranslateLoaderService } from 'core/services';
|
import { TranslateLoaderService } from '../../../../services/translate-loader.service';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { CoreTestingModule } from '../../../../testing';
|
import { CoreTestingModule } from '../../../../testing';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
@@ -69,11 +69,27 @@ describe('CheckboxWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be marked as invalid when required', async () => {
|
it('should be marked as invalid when required after interaction', async () => {
|
||||||
|
const checkbox = element.querySelector('mat-checkbox');
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
checkbox.dispatchEvent(new Event('click'));
|
||||||
|
checkbox.dispatchEvent(new Event('click'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
expect(element.querySelector('.adf-invalid')).not.toBeNull();
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be checked if boolean true is passed', fakeAsync(() => {
|
it('should be checked if boolean true is passed', fakeAsync(() => {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="{{field.className}}" id="data-time-widget" [class.adf-invalid]="!field.isValid">
|
<div class="{{field.className}}" id="data-time-widget" [class.adf-invalid]="!field.isValid && isTouched()">
|
||||||
<mat-form-field class="adf-date-time-widget" [hideRequiredMarker]="true">
|
<mat-form-field class="adf-date-time-widget" [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[value]="field.value"
|
[value]="field.value"
|
||||||
@@ -9,13 +9,14 @@
|
|||||||
(change)="onDateChanged($any($event).srcElement.value)"
|
(change)="onDateChanged($any($event).srcElement.value)"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000"
|
matTooltipShowDelay="1000"
|
||||||
(focus)="datetimePicker.open()">
|
(focus)="datetimePicker.open()">
|
||||||
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker" [disabled]="field.readOnly"></mat-datetimepicker-toggle>
|
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker" [disabled]="field.readOnly"></mat-datetimepicker-toggle>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
<mat-datetimepicker #datetimePicker type="datetime" [touchUi]="true" [timeInterval]="5" [disabled]="field.readOnly"></mat-datetimepicker>
|
<mat-datetimepicker #datetimePicker type="datetime" [touchUi]="true" [timeInterval]="5" [disabled]="field.readOnly"></mat-datetimepicker>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@@ -24,6 +24,7 @@ import { setupTestBed } from '../../../../testing/setup-test-bed';
|
|||||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
|
|
||||||
describe('DateTimeWidgetComponent', () => {
|
describe('DateTimeWidgetComponent', () => {
|
||||||
|
|
||||||
@@ -106,6 +107,38 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.DATETIME,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const dateTimeInput = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
dateTimeInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('template check', () => {
|
describe('template check', () => {
|
||||||
|
|
||||||
it('should show visible date widget', async () => {
|
it('should show visible date widget', async () => {
|
||||||
|
@@ -1,17 +1,19 @@
|
|||||||
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="!field.isValid">
|
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="!field.isValid && isTouched()">
|
||||||
<mat-form-field class="adf-date-widget" [hideRequiredMarker]="true">
|
<mat-form-field class="adf-date-widget" [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
||||||
|
*ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[value]="field.value"
|
[value]="field.value"
|
||||||
[required]="isRequired()"
|
[required]="isRequired()"
|
||||||
[disabled]="field.readOnly"
|
[disabled]="field.readOnly"
|
||||||
(change)="onDateChanged($any($event).srcElement.value)"
|
(change)="onDateChanged($any($event).srcElement.value)"
|
||||||
[placeholder]="field.placeholder">
|
[placeholder]="field.placeholder"
|
||||||
|
(blur)="markAsTouched()">
|
||||||
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly" ></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly" ></mat-datepicker-toggle>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
<mat-datepicker #datePicker [touchUi]="true" [startAt]="field.value | adfMomentDate: field.dateDisplayFormat" [disabled]="field.readOnly"></mat-datepicker>
|
<mat-datepicker #datePicker [touchUi]="true" [startAt]="field.value | adfMomentDate: field.dateDisplayFormat" [disabled]="field.readOnly"></mat-datepicker>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@@ -23,6 +23,7 @@ import { DateWidgetComponent } from './date.widget';
|
|||||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
|
|
||||||
describe('DateWidgetComponent', () => {
|
describe('DateWidgetComponent', () => {
|
||||||
|
|
||||||
@@ -98,6 +99,38 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.DATE,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const dateInput = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
dateInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterix', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('template check', () => {
|
describe('template check', () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
<div class="adf-dropdown-widget {{field.className}}"
|
<div class="adf-dropdown-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select class="adf-select"
|
<mat-select class="adf-select"
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[(ngModel)]="field.value"
|
[(ngModel)]="field.value"
|
||||||
[disabled]="field.readOnly"
|
[disabled]="field.readOnly"
|
||||||
(ngModelChange)="onFieldChanged(field)">
|
(ngModelChange)="onFieldChanged(field)"
|
||||||
|
(blur)="markAsTouched()">
|
||||||
<mat-option *ngFor="let opt of field.options"
|
<mat-option *ngFor="let opt of field.options"
|
||||||
[value]="getOptionValue(opt, field.value)"
|
[value]="getOptionValue(opt, field.value)"
|
||||||
[id]="opt.id">{{opt.name}}
|
[id]="opt.id">{{opt.name}}
|
||||||
@@ -15,6 +16,6 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()"
|
<error-widget class="adf-dropdown-required-message" *ngIf="showRequiredMessage()"
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -130,22 +130,26 @@ describe('DropdownWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to display label with asterix', async () => {
|
it('should be able to display label with asterisk', async () => {
|
||||||
const label = 'MyLabel123';
|
|
||||||
widget.field.name = label;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
expect(element.querySelector('label').innerText).toBe(label + '*');
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be invalid if no default option', async () => {
|
it('should be invalid if no default option after interaction', async () => {
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
const dropdownSelect = element.querySelector('.adf-select');
|
||||||
|
dropdownSelect.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
expect(element.querySelector('.adf-invalid')).toBeDefined();
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
expect(element.querySelector('.adf-invalid')).not.toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be valid if default option', async () => {
|
it('should be valid if default option', async () => {
|
||||||
@@ -155,7 +159,7 @@ describe('DropdownWidgetComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
expect(element.querySelector('.adf-invalid')).toBeNull();
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -113,4 +113,7 @@ export class DropdownWidgetComponent extends WidgetComponent implements OnInit {
|
|||||||
return this.field.type === 'readonly';
|
return this.field.type === 'readonly';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showRequiredMessage(): boolean {
|
||||||
|
return (this.isInvalidFieldRequired() || this.field.value === 'empty') && this.isTouched();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="adf-dynamic-table-scrolling {{field.className}}"
|
<div class="adf-dynamic-table-scrolling {{field.className}}"
|
||||||
[class.adf-invalid]="!isValid()">
|
[class.adf-invalid]="!isValid()">
|
||||||
<div class="adf-label">{{content.name | translate }}<span *ngIf="isRequired()">*</span></div>
|
<div class="adf-label">{{content.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></div>
|
||||||
|
|
||||||
<div *ngIf="!editMode">
|
<div *ngIf="!editMode">
|
||||||
<div class="adf-table-container">
|
<div class="adf-table-container">
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
<div class="adf-error-text-container">
|
<div class="adf-error-container">
|
||||||
<div *ngIf="error?.isActive()" [@transitionMessages]="_subscriptAnimationState">
|
<div *ngIf="error?.isActive()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<div class="adf-error-text">{{error.message | translate:translateParameters}}</div>
|
<div class="adf-error-text">{{error.message | translate:translateParameters}}</div>
|
||||||
<mat-icon class="adf-error-icon">warning</mat-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="required" [@transitionMessages]="_subscriptAnimationState">
|
<div *ngIf="required" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<div class="adf-error-text">{{required}}</div>
|
<div class="adf-error-text">{{required}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
.adf-error-text {
|
.adf-error {
|
||||||
width: 85%;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,73 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SimpleChange, SimpleChanges } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
|
import { ErrorWidgetComponent } from './error.component';
|
||||||
|
import { CoreTestingModule } from '../../../../testing';
|
||||||
|
import { ErrorMessageModel } from '..';
|
||||||
|
|
||||||
|
describe('ErrorWidgetComponent', () => {
|
||||||
|
|
||||||
|
let widget: ErrorWidgetComponent;
|
||||||
|
let fixture: ComponentFixture<ErrorWidgetComponent>;
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
CoreTestingModule
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ErrorWidgetComponent);
|
||||||
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
|
});
|
||||||
|
const errorMessage: string = 'fake-error';
|
||||||
|
const errorMessageModel: ErrorMessageModel = new ErrorMessageModel({message: errorMessage});
|
||||||
|
const errorChanges: SimpleChanges = {
|
||||||
|
error: new SimpleChange(errorMessageModel, errorMessageModel, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should display proper error icon', async () => {
|
||||||
|
widget.ngOnChanges(errorChanges);
|
||||||
|
|
||||||
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const errorIcon = element.querySelector('.adf-error-icon').textContent;
|
||||||
|
expect(errorIcon).toEqual('error_outline');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set subscriptAnimationState value', () => {
|
||||||
|
widget.ngOnChanges(errorChanges);
|
||||||
|
|
||||||
|
expect(widget.subscriptAnimationState).toEqual('enter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check proper error message', async () => {
|
||||||
|
widget.ngOnChanges(errorChanges);
|
||||||
|
|
||||||
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const requiredErrorText = element.querySelector('.adf-error-text').textContent;
|
||||||
|
expect(requiredErrorText).toEqual(errorMessage);
|
||||||
|
});
|
||||||
|
});
|
@@ -59,7 +59,7 @@ export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
|
|||||||
|
|
||||||
translateParameters: any = null;
|
translateParameters: any = null;
|
||||||
|
|
||||||
_subscriptAnimationState: string = '';
|
subscriptAnimationState: string = '';
|
||||||
|
|
||||||
constructor(public formService: FormService) {
|
constructor(public formService: FormService) {
|
||||||
super(formService);
|
super(formService);
|
||||||
@@ -68,13 +68,13 @@ export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
|
|||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['required']) {
|
if (changes['required']) {
|
||||||
this.required = changes.required.currentValue;
|
this.required = changes.required.currentValue;
|
||||||
this._subscriptAnimationState = 'enter';
|
this.subscriptAnimationState = 'enter';
|
||||||
}
|
}
|
||||||
if (changes['error'] && changes['error'].currentValue) {
|
if (changes['error'] && changes['error'].currentValue) {
|
||||||
if (changes.error.currentValue.isActive()) {
|
if (changes.error.currentValue.isActive()) {
|
||||||
this.error = changes.error.currentValue;
|
this.error = changes.error.currentValue;
|
||||||
this.translateParameters = this.error.getAttributesAsJsonObj();
|
this.translateParameters = this.error.getAttributesAsJsonObj();
|
||||||
this._subscriptAnimationState = 'enter';
|
this.subscriptAnimationState = 'enter';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="adf-file-viewer-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
<div class="adf-file-viewer-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<adf-viewer [overlayMode]="false" [nodeId]="field.value" [showViewer]="field.value" [allowGoBack]="false"></adf-viewer>
|
<adf-viewer [overlayMode]="false" [nodeId]="field.value" [showViewer]="field.value" [allowGoBack]="false"></adf-viewer>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
|
@@ -5,8 +5,12 @@ ul > li > form-field > .adf-focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-form-field-label {
|
||||||
|
color: var(--theme-colors-mat-grey-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.adf {
|
.adf {
|
||||||
&-error-text-container {
|
&-error-container {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-top: -12px;
|
margin-top: -12px;
|
||||||
}
|
}
|
||||||
@@ -16,40 +20,35 @@ ul > li > form-field > .adf-focus {
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
font-size: var(--theme-caption-font-size);
|
font-size: var(--theme-caption-font-size);
|
||||||
line-height: 1.33;
|
line-height: 1.33;
|
||||||
float: left;
|
|
||||||
color: var(--theme-warn-color);
|
color: var(--theme-warn-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-error-icon {
|
&-error-icon {
|
||||||
float: right;
|
|
||||||
font-size: var(--theme-adf-icon-1-font-size);
|
font-size: var(--theme-adf-icon-1-font-size);
|
||||||
color: var(--theme-warn-color);
|
color: var(--theme-warn-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-label {
|
&-label {
|
||||||
color: rgb(186, 186, 186);
|
color: var(--theme-secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-asterisk {
|
||||||
|
padding-left: 2px;
|
||||||
|
color: var(--theme-warn-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-invalid {
|
&-invalid {
|
||||||
|
.mat-checkbox-layout {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.mat-form-field-underline {
|
.mat-form-field-underline {
|
||||||
background-color: #f44336 !important;
|
background-color: var(--theme-warn-color) !important;
|
||||||
}
|
|
||||||
|
|
||||||
.mat-checkbox {
|
|
||||||
color: var(--theme-warn-color);
|
|
||||||
|
|
||||||
.mat-checkbox-frame {
|
|
||||||
border-color: var(--theme-warn-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-select {
|
.mat-select {
|
||||||
&-value {
|
|
||||||
color: var(--theme-warn-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-arrow {
|
&-arrow {
|
||||||
color: var(--theme-warn-color);
|
color: var(--theme-secondary-text-color) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,20 +57,12 @@ ul > li > form-field > .adf-focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mat-form-field-prefix {
|
.mat-form-field-prefix {
|
||||||
color: var(--theme-warn-color);
|
color: var(--theme-secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-input {
|
.adf-input {
|
||||||
border-color: var(--theme-warn-color);
|
border-color: var(--theme-warn-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-label {
|
|
||||||
color: var(--theme-warn-color);
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
background-color: var(--theme-warn-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<div class="adf-group-widget {{field.className}}"
|
<div class="adf-group-widget {{field.className}}"
|
||||||
[class.is-dirty]="!!field.value"
|
[class.is-dirty]="!!field.value"
|
||||||
[class.adf-invalid]="!field.isValid"
|
[class.adf-invalid]="!field.isValid && isTouched()"
|
||||||
[class.adf-readonly]="field.readOnly"
|
[class.adf-readonly]="field.readOnly"
|
||||||
id="functional-group-div">
|
id="functional-group-div">
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
class="adf-input"
|
class="adf-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[formControl]="searchTerm"
|
[formControl]="searchTerm"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
[matAutocomplete]="auto">
|
[matAutocomplete]="auto">
|
||||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="updateOption($event.option.value)" [displayWith]="getDisplayName">
|
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="updateOption($event.option.value)" [displayWith]="getDisplayName">
|
||||||
<mat-option *ngFor="let item of groups$ | async; let i = index"
|
<mat-option *ngFor="let item of groups$ | async; let i = index"
|
||||||
@@ -25,5 +26,5 @@
|
|||||||
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -24,12 +24,14 @@ import { FunctionalGroupWidgetComponent } from './functional-group.widget';
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { CoreTestingModule, setupTestBed } from '../../../../testing';
|
import { CoreTestingModule, setupTestBed } from '../../../../testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
|
|
||||||
describe('FunctionalGroupWidgetComponent', () => {
|
describe('FunctionalGroupWidgetComponent', () => {
|
||||||
let fixture: ComponentFixture<FunctionalGroupWidgetComponent>;
|
let fixture: ComponentFixture<FunctionalGroupWidgetComponent>;
|
||||||
let component: FunctionalGroupWidgetComponent;
|
let component: FunctionalGroupWidgetComponent;
|
||||||
let formService: FormService;
|
let formService: FormService;
|
||||||
let getWorkflowGroupsSpy: jasmine.Spy;
|
let getWorkflowGroupsSpy: jasmine.Spy;
|
||||||
|
let element: HTMLElement;
|
||||||
const groups: GroupModel[] = [
|
const groups: GroupModel[] = [
|
||||||
{ id: '1', name: 'group 1' },
|
{ id: '1', name: 'group 1' },
|
||||||
{ id: '2', name: 'group 2' }
|
{ id: '2', name: 'group 2' }
|
||||||
@@ -49,6 +51,7 @@ describe('FunctionalGroupWidgetComponent', () => {
|
|||||||
fixture = TestBed.createComponent(FunctionalGroupWidgetComponent);
|
fixture = TestBed.createComponent(FunctionalGroupWidgetComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.field = new FormFieldModel(new FormModel());
|
component.field = new FormFieldModel(new FormModel());
|
||||||
|
element = fixture.nativeElement;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,4 +151,36 @@ describe('FunctionalGroupWidgetComponent', () => {
|
|||||||
await typeIntoInput('123');
|
await typeIntoInput('123');
|
||||||
expect(getWorkflowGroupsSpy).not.toHaveBeenCalled();
|
expect(getWorkflowGroupsSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.FUNCTIONAL_GROUP,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const functionalGroupInput = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
functionalGroupInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="adf-hyperlink-widget {{field.className}}">
|
<div class="adf-hyperlink-widget {{field.className}}">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<div [matTooltip]="field.tooltip" matTooltipPosition="above" matTooltipShowDelay="1000">
|
<div [matTooltip]="field.tooltip" matTooltipPosition="above" matTooltipShowDelay="1000">
|
||||||
<a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a>
|
<a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-multiline-text-widget {{field.className}}"
|
<div class="adf-multiline-text-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<mat-form-field floatPlaceholder="never" [hideRequiredMarker]="true">
|
<mat-form-field floatPlaceholder="never" [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<textarea matInput class="adf-input"
|
<textarea matInput class="adf-input"
|
||||||
[matTextareaAutosize]="true"
|
[matTextareaAutosize]="true"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
[disabled]="field.readOnly || readOnly"
|
[disabled]="field.readOnly || readOnly"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
</textarea>
|
</textarea>
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
<span>{{field?.value?.length || 0}}/{{field.maxLength}}</span>
|
<span>{{field?.value?.length || 0}}/{{field.maxLength}}</span>
|
||||||
</div>
|
</div>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget class="adf-multiline-required-message" *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget class="adf-multiline-required-message" *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}">
|
||||||
|
</error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -15,17 +15,67 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
import { MultilineTextWidgetComponentComponent } from './multiline-text.widget';
|
import { MultilineTextWidgetComponentComponent } from './multiline-text.widget';
|
||||||
|
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||||
|
import { FormFieldModel } from '../core/form-field.model';
|
||||||
|
import { FormModel } from '../core/form.model';
|
||||||
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
|
|
||||||
describe('MultilineTextWidgetComponentComponent', () => {
|
describe('MultilineTextWidgetComponentComponent', () => {
|
||||||
|
|
||||||
let widget: MultilineTextWidgetComponentComponent;
|
let widget: MultilineTextWidgetComponentComponent;
|
||||||
|
let fixture: ComponentFixture<MultilineTextWidgetComponentComponent>;
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CoreTestingModule
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
widget = new MultilineTextWidgetComponentComponent(null);
|
fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent);
|
||||||
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
expect(widget).toBeDefined();
|
expect(widget).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.MULTILINE_TEXT,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const multilineTextarea = fixture.nativeElement.querySelector('textarea');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
multilineTextarea.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-textfield adf-number-widget {{field.className}}"
|
<div class="adf-textfield adf-number-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<mat-form-field [hideRequiredMarker]="true">
|
<mat-form-field [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
class="adf-input"
|
class="adf-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -14,9 +14,10 @@
|
|||||||
[disabled]="field.readOnly"
|
[disabled]="field.readOnly"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
<div class="adf-people-widget {{field.className}}"
|
<div class="adf-people-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid"
|
[class.adf-invalid]="!field.isValid && isTouched()"
|
||||||
[class.adf-readonly]="field.readOnly"
|
[class.adf-readonly]="field.readOnly"
|
||||||
id="people-widget-content">
|
id="people-widget-content">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<input #inputValue
|
<input #inputValue
|
||||||
matInput
|
matInput
|
||||||
class="adf-input"
|
class="adf-input"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
[formControl]="searchTerm"
|
[formControl]="searchTerm"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
@@ -33,5 +34,5 @@
|
|||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -155,6 +155,38 @@ describe('PeopleWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.PEOPLE,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const peopleInput = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
peopleInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when template is ready', () => {
|
describe('when template is ready', () => {
|
||||||
|
|
||||||
const fakeUserResult = [
|
const fakeUserResult = [
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-radio-buttons-widget {{field.className}}"
|
<div class="adf-radio-buttons-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" [id]="field.id">
|
[class.adf-readonly]="field.readOnly" [id]="field.id">
|
||||||
<div class="adf-radio-button-container">
|
<div class="adf-radio-button-container">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<mat-radio-group class="adf-radio-group" [(ngModel)]="field.value" [disabled]="field.readOnly">
|
<mat-radio-group class="adf-radio-group" [(ngModel)]="field.value" [disabled]="field.readOnly">
|
||||||
<mat-radio-button
|
<mat-radio-button
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
@@ -19,5 +19,4 @@
|
|||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-textfield adf-text-widget {{field.className}}"
|
<div class="adf-textfield adf-text-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<mat-form-field [hideRequiredMarker]="true">
|
<mat-form-field [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
class="adf-input"
|
class="adf-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -14,9 +14,10 @@
|
|||||||
[textMask]="{mask: mask, isReversed: isMaskReversed}"
|
[textMask]="{mask: mask, isReversed: isMaskReversed}"
|
||||||
[placeholder]="placeholder"
|
[placeholder]="placeholder"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -93,33 +93,6 @@ describe('TextWidgetComponent', () => {
|
|||||||
expect(textWidgetLabel.innerText).toBe('text-name');
|
expect(textWidgetLabel.innerText).toBe('text-name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to set a Text Widget as required', async () => {
|
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
|
||||||
id: 'text-id',
|
|
||||||
name: 'text-name',
|
|
||||||
value: '',
|
|
||||||
type: FormFieldTypes.TEXT,
|
|
||||||
readOnly: false,
|
|
||||||
required: true
|
|
||||||
});
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
const textWidgetLabel = element.querySelector('label');
|
|
||||||
expect(textWidgetLabel.innerText).toBe('text-name*');
|
|
||||||
expect(widget.field.isValid).toBe(false);
|
|
||||||
|
|
||||||
enterValueInTextField(element.querySelector('#text-id'), 'TEXT');
|
|
||||||
|
|
||||||
await fixture.whenStable();
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(widget.field.isValid).toBe(true);
|
|
||||||
|
|
||||||
enterValueInTextField(element.querySelector('#text-id'), '');
|
|
||||||
await fixture.whenStable();
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(widget.field.isValid).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to set a placeholder for Text widget', async () => {
|
it('should be able to set a placeholder for Text widget', async () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||||
id: 'text-id',
|
id: 'text-id',
|
||||||
@@ -222,6 +195,42 @@ describe('TextWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||||
|
id: 'text-id',
|
||||||
|
name: 'text-name',
|
||||||
|
value: '',
|
||||||
|
type: FormFieldTypes.TEXT,
|
||||||
|
readOnly: false,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be marked as invalid after interaction', async () => {
|
||||||
|
const textInput = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
textInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('and no mask is configured on text element', () => {
|
describe('and no mask is configured on text element', () => {
|
||||||
|
|
||||||
let inputElement: HTMLInputElement;
|
let inputElement: HTMLInputElement;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-upload-folder-widget {{field.className}}"
|
<div class="adf-upload-folder-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid"
|
[class.adf-invalid]="!field.isValid"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<div class="adf-upload-widget-container">
|
<div class="adf-upload-widget-container">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,3 +14,48 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
|
import { CoreTestingModule } from '../../../../testing';
|
||||||
|
import { UploadFolderWidgetComponent } from './upload-folder.widget';
|
||||||
|
import { FormFieldModel } from '../core/form-field.model';
|
||||||
|
import { FormModel } from '../core/form.model';
|
||||||
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
|
|
||||||
|
describe('UploadFolderWidgetComponent', () => {
|
||||||
|
|
||||||
|
let widget: UploadFolderWidgetComponent;
|
||||||
|
let fixture: ComponentFixture<UploadFolderWidgetComponent>;
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
CoreTestingModule
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UploadFolderWidgetComponent);
|
||||||
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.UPLOAD,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-upload-widget {{field.className}}"
|
<div class="adf-upload-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid"
|
[class.adf-invalid]="!field.isValid"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<div class="adf-upload-widget-container">
|
<div class="adf-upload-widget-container">
|
||||||
<div>
|
<div>
|
||||||
<mat-list *ngIf="hasFile">
|
<mat-list *ngIf="hasFile">
|
||||||
|
@@ -56,6 +56,8 @@ export class WidgetComponent implements AfterViewInit {
|
|||||||
@Output()
|
@Output()
|
||||||
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
|
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
|
||||||
|
|
||||||
|
touched: boolean = false;
|
||||||
|
|
||||||
constructor(public formService?: FormService) {
|
constructor(public formService?: FormService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +78,10 @@ export class WidgetComponent implements AfterViewInit {
|
|||||||
return !!this.field.validationSummary;
|
return !!this.field.validationSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTouched(): boolean {
|
||||||
|
return this.touched;
|
||||||
|
}
|
||||||
|
|
||||||
hasValue(): boolean {
|
hasValue(): boolean {
|
||||||
return this.field &&
|
return this.field &&
|
||||||
this.field.value !== null &&
|
this.field.value !== null &&
|
||||||
@@ -83,7 +89,7 @@ export class WidgetComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isInvalidFieldRequired() {
|
isInvalidFieldRequired() {
|
||||||
return !this.field.isValid && !this.field.validationSummary && this.isRequired();
|
return !this.field.isValid && (!this.field.validationSummary || !this.field.value) && this.isRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@@ -101,4 +107,8 @@ export class WidgetComponent implements AfterViewInit {
|
|||||||
event(event: Event): void {
|
event(event: Event): void {
|
||||||
this.formService.formEvents.next(event);
|
this.formService.formEvents.next(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsTouched() {
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@
|
|||||||
"DOWNLOAD_FILE": "Download",
|
"DOWNLOAD_FILE": "Download",
|
||||||
"REMOVE_FILE": "Remove",
|
"REMOVE_FILE": "Remove",
|
||||||
"UPLOAD": "UPLOAD",
|
"UPLOAD": "UPLOAD",
|
||||||
"REQUIRED": "*Required",
|
"REQUIRED": "This is a required field",
|
||||||
"REST_API_FAILED": "The server `{{ hostname }}` is not reachable",
|
"REST_API_FAILED": "The server `{{ hostname }}` is not reachable",
|
||||||
"FILE_NAME": "File Name",
|
"FILE_NAME": "File Name",
|
||||||
"NO_FILE_ATTACHED": "No file attached",
|
"NO_FILE_ATTACHED": "No file attached",
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<div class="adf-attach-file-widget-container">
|
<div class="adf-attach-file-widget-container">
|
||||||
<div class="adf-attach-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
<div class="adf-attach-widget {{field.className}}"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name}}
|
<label class="adf-label" [attr.for]="field.id">{{field.name}}
|
||||||
<span *ngIf="isRequired()">*</span>
|
<span class="adf-asterisk" *ngIf="isRequired()">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="adf-attach-widget-container">
|
<div class="adf-attach-widget-container" (focusout)="markAsTouched()">
|
||||||
<div class="adf-attach-widget__menu-upload" *ngIf="isUploadButtonVisible()">
|
<div class="adf-attach-widget__menu-upload" *ngIf="isUploadButtonVisible()">
|
||||||
<button (click)="openSelectDialog()" mat-raised-button color="primary" [id]="field.id"
|
<button (click)="openSelectDialog()" mat-raised-button color="primary" [id]="field.id"
|
||||||
[matTooltip]="field.tooltip" matTooltipPosition="above" matTooltipShowDelay="1000">
|
[matTooltip]="field.tooltip" matTooltipPosition="above" matTooltipShowDelay="1000">
|
||||||
@@ -36,5 +36,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="!field.isValid && isTouched() && !isSelected()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -73,6 +73,7 @@
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
background: var(--theme-colors-mat-grey);
|
background: var(--theme-colors-mat-grey);
|
||||||
min-height: 27px;
|
min-height: 27px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.adf-label {
|
.adf-label {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
@@ -201,6 +201,24 @@ describe('AttachFileCloudWidgetComponent', () => {
|
|||||||
expect(contentNodeSelectorPanelService.customModels).toEqual([]);
|
expect(contentNodeSelectorPanelService.customModels).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.UPLOAD,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Upload widget with displayable ContentModel properties', () => {
|
describe('Upload widget with displayable ContentModel properties', () => {
|
||||||
|
|
||||||
it('should display CM Properties if the file contains value', async () => {
|
it('should display CM Properties if the file contains value', async () => {
|
||||||
|
@@ -255,6 +255,10 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i
|
|||||||
return alias && VALID_ALIAS.includes(alias);
|
return alias && VALID_ALIAS.includes(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSelected(): boolean {
|
||||||
|
return this.hasFile;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.contentNodeSelectorPanelService.customModels = [];
|
this.contentNodeSelectorPanelService.customModels = [];
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="adf-upload-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
<div class="adf-upload-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{ field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{ field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<div class="adf-cloud-upload-widget-container">
|
<div class="adf-cloud-upload-widget-container">
|
||||||
<div>
|
<div>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="!field.isValid">
|
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="!field.isValid && isTouched()">
|
||||||
<mat-form-field class="adf-date-widget">
|
<mat-form-field class="adf-date-widget" [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
||||||
|
*ngIf="isRequired()">*</span></label>
|
||||||
<input matInput
|
<input matInput
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[value]="field.value"
|
[value]="field.value"
|
||||||
@@ -9,12 +10,13 @@
|
|||||||
(change)="onDateChanged($any($event).srcElement.value)"
|
(change)="onDateChanged($any($event).srcElement.value)"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly" ></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly" ></mat-datepicker-toggle>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
<mat-datepicker #datePicker [touchUi]="true" [startAt]="field.value | adfMomentDate: field.dateDisplayFormat" [disabled]="field.readOnly"></mat-datepicker>
|
<mat-datepicker #datePicker [touchUi]="true" [startAt]="field.value | adfMomentDate: field.dateDisplayFormat" [disabled]="field.readOnly"></mat-datepicker>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-dropdown-widget {{field.className}}"
|
<div class="adf-dropdown-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="(!field.isValid && isTouched()) || isRestApiFailed" [class.adf-readonly]="field.readOnly">
|
||||||
<div class="adf-dropdown-widget-top-labels">
|
<div class="adf-dropdown-widget-top-labels">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span>
|
*ngIf="isRequired()">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
[compareWith]="compareDropdownValues"
|
[compareWith]="compareDropdownValues"
|
||||||
(ngModelChange)="selectionChangedForField(field)"
|
(ngModelChange)="selectionChangedForField(field)"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
|
[required]="isRequired()"
|
||||||
panelClass="adf-select-filter"
|
panelClass="adf-select-filter"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000"
|
matTooltipShowDelay="1000"
|
||||||
[multiple]="field.hasMultipleValues">
|
[multiple]="field.hasMultipleValues">
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()"
|
<error-widget class="adf-dropdown-required-message" *ngIf="showRequiredMessage()"
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
<error-widget class="adf-dropdown-failed-message" *ngIf="isRestApiFailed"
|
<error-widget class="adf-dropdown-failed-message" *ngIf="isRestApiFailed"
|
||||||
required="{{ 'FORM.FIELD.REST_API_FAILED' | translate: { hostname: restApiHostName } }}"></error-widget>
|
required="{{ 'FORM.FIELD.REST_API_FAILED' | translate: { hostname: restApiHostName } }}"></error-widget>
|
||||||
|
@@ -16,11 +16,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-dropdown-required-message .adf-error-text-container {
|
&-dropdown-required-message .adf-error-container {
|
||||||
margin-top: 1px !important;
|
margin-top: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-dropdown-failed-message .adf-error-text-container {
|
&-dropdown-failed-message .adf-error-container {
|
||||||
margin-top: 1px !important;
|
margin-top: 1px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { DropdownCloudWidgetComponent } from './dropdown-cloud.widget';
|
import { DropdownCloudWidgetComponent } from './dropdown-cloud.widget';
|
||||||
import { FormFieldModel, FormModel, FormService, setupTestBed, FormFieldEvent } from '@alfresco/adf-core';
|
import { FormFieldModel, FormModel, FormService, setupTestBed, FormFieldEvent, FormFieldTypes } from '@alfresco/adf-core';
|
||||||
import { FormCloudService } from '../../../services/form-cloud.service';
|
import { FormCloudService } from '../../../services/form-cloud.service';
|
||||||
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -160,6 +160,7 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
|
|
||||||
it('should show error message if the restUrl failed to fetch options', async () => {
|
it('should show error message if the restUrl failed to fetch options', async () => {
|
||||||
const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(throwError('Failed to fetch options'));
|
const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(throwError('Failed to fetch options'));
|
||||||
|
const errorIcon: string = 'error_outline';
|
||||||
widget.field.restUrl = 'https://fake-rest-url';
|
widget.field.restUrl = 'https://fake-rest-url';
|
||||||
widget.field.optionType = 'rest';
|
widget.field.optionType = 'rest';
|
||||||
widget.field.restIdProperty = 'name';
|
widget.field.restIdProperty = 'name';
|
||||||
@@ -173,11 +174,10 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-dropdown-failed-message'));
|
const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-dropdown-failed-message'));
|
||||||
|
|
||||||
expect(jsonDataSpy).toHaveBeenCalled();
|
expect(jsonDataSpy).toHaveBeenCalled();
|
||||||
expect(widget.isRestApiFailed).toBe(true);
|
expect(widget.isRestApiFailed).toBe(true);
|
||||||
expect(widget.field.options.length).toEqual(0);
|
expect(widget.field.options.length).toEqual(0);
|
||||||
expect(failedErrorMsgElement.nativeElement.innerText.trim()).toBe('FORM.FIELD.REST_API_FAILED');
|
expect(failedErrorMsgElement.nativeElement.textContent.trim()).toBe(errorIcon + 'FORM.FIELD.REST_API_FAILED');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should preselect dropdown widget value when Json (rest call) passed', async () => {
|
it('should preselect dropdown widget value when Json (rest call) passed', async () => {
|
||||||
@@ -234,6 +234,41 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.DROPDOWN,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be invalid if no default option after interaction', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
const dropdownSelect = element.querySelector('.adf-select');
|
||||||
|
dropdownSelect.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('filter', () => {
|
describe('filter', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -391,6 +426,7 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
|
|
||||||
it('should reset previous child options if the rest url failed for a linked dropdown', async () => {
|
it('should reset previous child options if the rest url failed for a linked dropdown', async () => {
|
||||||
const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(mockRestDropdownOptions));
|
const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(mockRestDropdownOptions));
|
||||||
|
const errorIcon: string = 'error_outline';
|
||||||
const mockParentDropdown = { id: 'parentDropdown', value: 'mock-value', validate: () => true };
|
const mockParentDropdown = { id: 'parentDropdown', value: 'mock-value', validate: () => true };
|
||||||
spyOn(widget.field.form, 'getFormFields').and.returnValue([mockParentDropdown]);
|
spyOn(widget.field.form, 'getFormFields').and.returnValue([mockParentDropdown]);
|
||||||
|
|
||||||
@@ -415,7 +451,7 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
|
|
||||||
expect(widget.isRestApiFailed).toBe(true);
|
expect(widget.isRestApiFailed).toBe(true);
|
||||||
expect(widget.field.options.length).toBe(0);
|
expect(widget.field.options.length).toBe(0);
|
||||||
expect(failedErrorMsgElement2.nativeElement.innerText.trim()).toBe('FORM.FIELD.REST_API_FAILED');
|
expect(failedErrorMsgElement2.nativeElement.textContent.trim()).toBe(errorIcon + 'FORM.FIELD.REST_API_FAILED');
|
||||||
|
|
||||||
jsonDataSpy.and.returnValue(of(mockSecondRestDropdownOptions));
|
jsonDataSpy.and.returnValue(of(mockSecondRestDropdownOptions));
|
||||||
selectParentOption('IT');
|
selectParentOption('IT');
|
||||||
|
@@ -310,4 +310,8 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
|||||||
return this.field?.restUrl;
|
return this.field?.restUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showRequiredMessage(): boolean {
|
||||||
|
return (this.isInvalidFieldRequired() || this.field.value === 'empty') && this.isTouched() && !this.isRestApiFailed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
<div class="adf-dropdown-widget {{field.className}}"
|
<div class="adf-dropdown-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<adf-cloud-group [mode]="mode"
|
<adf-cloud-group [mode]="mode"
|
||||||
[title]="title"
|
[title]="title"
|
||||||
[readOnly]="field.readOnly"
|
[readOnly]="field.readOnly"
|
||||||
[roles]="roles"
|
[roles]="roles"
|
||||||
[searchGroupsControl]="search"
|
[searchGroupsControl]="search"
|
||||||
|
[required]="isRequired()"
|
||||||
(changedGroups)="onChangedGroup($event)"
|
(changedGroups)="onChangedGroup($event)"
|
||||||
[preSelectGroups]="preSelectGroup"
|
[preSelectGroups]="preSelectGroup"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
[matTooltipPosition]="'above'"
|
[matTooltipPosition]="'above'"
|
||||||
[matTooltipShowDelay]="1000">
|
[matTooltipShowDelay]="1000">
|
||||||
</adf-cloud-group>
|
</adf-cloud-group>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired()"
|
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired() && isTouched()"
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -0,0 +1,83 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FormFieldModel, FormFieldTypes, FormModel, setupTestBed } from '@alfresco/adf-core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { GroupCloudWidgetComponent } from './group-cloud.widget';
|
||||||
|
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('GroupCloudWidgetComponent', () => {
|
||||||
|
let fixture: ComponentFixture<GroupCloudWidgetComponent>;
|
||||||
|
let widget: GroupCloudWidgetComponent;
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
ProcessServiceCloudTestingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
GroupCloudWidgetComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GroupCloudWidgetComponent);
|
||||||
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.GROUP,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be invalid if no default option after interaction', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
const cloudGroupInput = element.querySelector('adf-cloud-group');
|
||||||
|
cloudGroupInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="adf-dropdown-widget {{field.className}}"
|
<div class="adf-dropdown-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<adf-cloud-people
|
<adf-cloud-people
|
||||||
[preSelectUsers]="preSelectUsers"
|
[preSelectUsers]="preSelectUsers"
|
||||||
[validate]="true"
|
[validate]="true"
|
||||||
@@ -8,13 +8,17 @@
|
|||||||
[title]="title"
|
[title]="title"
|
||||||
[readOnly]="field.readOnly"
|
[readOnly]="field.readOnly"
|
||||||
[searchUserCtrl]="search"
|
[searchUserCtrl]="search"
|
||||||
|
[required]="isRequired()"
|
||||||
(changedUsers)="onChangedUser($event)"
|
(changedUsers)="onChangedUser($event)"
|
||||||
[roles]="roles"
|
[roles]="roles"
|
||||||
[mode]="mode"
|
[mode]="mode"
|
||||||
|
(blur)="markAsTouched()"
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
</adf-cloud-people>
|
</adf-cloud-people>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
|
<error-widget class="adf-dropdown-required-message" *ngIf="isInvalidFieldRequired() && isTouched()"
|
||||||
|
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -15,15 +15,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CoreTestingModule, FormFieldModel, FormModel, IdentityUserService, setupTestBed } from '@alfresco/adf-core';
|
import { FormFieldModel, FormFieldTypes, FormModel, IdentityUserService, setupTestBed } from '@alfresco/adf-core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { PeopleCloudWidgetComponent } from './people-cloud.widget';
|
import { PeopleCloudWidgetComponent } from './people-cloud.widget';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
||||||
|
|
||||||
describe('PeopleCloudWidgetComponent', () => {
|
describe('PeopleCloudWidgetComponent', () => {
|
||||||
let fixture: ComponentFixture<PeopleCloudWidgetComponent>;
|
let fixture: ComponentFixture<PeopleCloudWidgetComponent>;
|
||||||
let widget: PeopleCloudWidgetComponent;
|
let widget: PeopleCloudWidgetComponent;
|
||||||
|
let element: HTMLElement;
|
||||||
let identityUserService: IdentityUserService;
|
let identityUserService: IdentityUserService;
|
||||||
const currentUser = { id: 'id', username: 'user' };
|
const currentUser = { id: 'id', username: 'user' };
|
||||||
const fakeUser = { id: 'fake-id', username: 'fake' };
|
const fakeUser = { id: 'fake-id', username: 'fake' };
|
||||||
@@ -31,7 +33,7 @@ describe('PeopleCloudWidgetComponent', () => {
|
|||||||
setupTestBed({
|
setupTestBed({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
CoreTestingModule
|
ProcessServiceCloudTestingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PeopleCloudWidgetComponent
|
PeopleCloudWidgetComponent
|
||||||
@@ -45,6 +47,7 @@ describe('PeopleCloudWidgetComponent', () => {
|
|||||||
identityUserService = TestBed.inject(IdentityUserService);
|
identityUserService = TestBed.inject(IdentityUserService);
|
||||||
fixture = TestBed.createComponent(PeopleCloudWidgetComponent);
|
fixture = TestBed.createComponent(PeopleCloudWidgetComponent);
|
||||||
widget = fixture.componentInstance;
|
widget = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(fakeUser);
|
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(fakeUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,4 +64,39 @@ describe('PeopleCloudWidgetComponent', () => {
|
|||||||
expect(widget.preSelectUsers).toEqual([currentUser]);
|
expect(widget.preSelectUsers).toEqual([currentUser]);
|
||||||
expect(identityUserService.getCurrentUserInfo).not.toHaveBeenCalled();
|
expect(identityUserService.getCurrentUserInfo).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when is required', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||||
|
type: FormFieldTypes.PEOPLE,
|
||||||
|
required: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to display label with asterisk', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
|
expect(asterisk).toBeTruthy();
|
||||||
|
expect(asterisk.textContent).toEqual('*');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be invalid if no default option after interaction', async () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
|
const cloudPeopleInput = element.querySelector('adf-cloud-people');
|
||||||
|
cloudPeopleInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="adf-file-viewer-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
<div class="adf-file-viewer-widget {{field.className}}" [class.adf-invalid]="!field.isValid"
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<ng-template #properties [ngTemplateOutlet]="properties" let-properties="properties" [ngTemplateOutletContext]="{ properties: field.params?.propertiesViewerOptions }">
|
<ng-template #properties [ngTemplateOutlet]="properties" let-properties="properties" [ngTemplateOutletContext]="{ properties: field.params?.propertiesViewerOptions }">
|
||||||
<adf-properties-viewer-wrapper *ngIf="field.value" [nodeId]="field.value"
|
<adf-properties-viewer-wrapper *ngIf="field.value" [nodeId]="field.value"
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="adf-radio-buttons-widget-cloud {{field.className}}"
|
<div class="adf-radio-buttons-widget-cloud {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" [id]="field.id">
|
[class.adf-readonly]="field.readOnly" [id]="field.id">
|
||||||
<div class="adf-radio-button-container">
|
<div class="adf-radio-button-container">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span *ngIf="isRequired()">*</span></label>
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||||
<mat-radio-group class="adf-radio-group" [(ngModel)]="field.value" [disabled]="field.readOnly">
|
<mat-radio-group class="adf-radio-group" [(ngModel)]="field.value" [disabled]="field.readOnly">
|
||||||
<mat-radio-button
|
<mat-radio-button
|
||||||
[matTooltip]="field.tooltip"
|
[matTooltip]="field.tooltip"
|
||||||
@@ -19,5 +19,4 @@
|
|||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<form>
|
<form>
|
||||||
<mat-form-field class="adf-cloud-group">
|
<mat-form-field class="adf-cloud-group" [class.adf-invalid]="hasError() && isDirty()">
|
||||||
<mat-label *ngIf="!isReadonly()"
|
<mat-label *ngIf="!isReadonly()"
|
||||||
id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
|
id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
|
||||||
<mat-chip-list #groupChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-group-chip-list">
|
<mat-chip-list #groupChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-group-chip-list">
|
||||||
@@ -20,8 +20,9 @@
|
|||||||
[formControl]="searchGroupsControl"
|
[formControl]="searchGroupsControl"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
[matChipInputFor]="groupChipList"
|
[matChipInputFor]="groupChipList"
|
||||||
|
[required]="required"
|
||||||
(focus)="setFocus(true)"
|
(focus)="setFocus(true)"
|
||||||
(blur)="setFocus(false)"
|
(blur)="setFocus(false); markAsTouched()"
|
||||||
class="adf-group-input"
|
class="adf-group-input"
|
||||||
data-automation-id="adf-cloud-group-search-input" #groupInput>
|
data-automation-id="adf-cloud-group-search-input" #groupInput>
|
||||||
</mat-chip-list>
|
</mat-chip-list>
|
||||||
@@ -58,23 +59,32 @@
|
|||||||
mode="indeterminate">
|
mode="indeterminate">
|
||||||
</mat-progress-bar>
|
</mat-progress-bar>
|
||||||
|
|
||||||
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
|
<div class="adf-error-container">
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : validateGroupsMessage } }}</mat-error>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<mat-error *ngIf="searchGroupsControl.hasError('pattern')">
|
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : validateGroupsMessage } }}</div>
|
||||||
<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="searchGroupsControl.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>
|
||||||
<mat-error *ngIf="searchGroupsControl.hasError('minlength')">
|
<mat-error *ngIf="searchGroupsControl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</div>
|
||||||
<mat-error *ngIf="searchGroupsControl.hasError('required') || groupChipsCtrl.hasError('required')">
|
</mat-error>
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-groups-typing-error">
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}</div>
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
</mat-error>
|
||||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}</mat-error>
|
<mat-error *ngIf="searchGroupsControl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</div>
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="(searchGroupsControl.hasError('required') || groupChipsCtrl.hasError('required')) && isDirty()"
|
||||||
|
[@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </div>
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused"
|
||||||
|
data-automation-id="invalid-groups-typing-error" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}</div>
|
||||||
|
</mat-error>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -219,6 +219,26 @@ describe('GroupCloudComponent', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display proper error icon', (done) => {
|
||||||
|
findGroupsByNameSpy.and.returnValue(of([]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = getElement<HTMLInputElement>('input');
|
||||||
|
input.focus();
|
||||||
|
input.value = 'ZZZ';
|
||||||
|
input.dispatchEvent(new Event('keyup'));
|
||||||
|
input.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
input.blur();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const errorIcon = element.querySelector('.adf-error-icon').textContent;
|
||||||
|
expect(errorIcon).toEqual('error_outline');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when application name defined', () => {
|
describe('when application name defined', () => {
|
||||||
|
@@ -83,6 +83,11 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
readOnly = false;
|
readOnly = false;
|
||||||
|
|
||||||
|
/** Mark this field as required
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
required = false;
|
||||||
|
|
||||||
/** FormControl to list of group */
|
/** FormControl to list of group */
|
||||||
@Input()
|
@Input()
|
||||||
groupChipsCtrl = new FormControl({ value: '', disabled: false });
|
groupChipsCtrl = new FormControl({ value: '', disabled: false });
|
||||||
@@ -121,9 +126,10 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
invalidGroups: IdentityGroupModel[] = [];
|
invalidGroups: IdentityGroupModel[] = [];
|
||||||
|
|
||||||
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
|
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
|
||||||
_subscriptAnimationState = 'enter';
|
subscriptAnimationState: string = 'enter';
|
||||||
clientId: string;
|
clientId: string;
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
|
touched: boolean = false;
|
||||||
|
|
||||||
validateGroupsMessage: string;
|
validateGroupsMessage: string;
|
||||||
searchedValue = '';
|
searchedValue = '';
|
||||||
@@ -472,6 +478,22 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return this.isValidationEnabled() && this.validationLoading;
|
return this.isValidationEnabled() && this.validationLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsTouched(): void {
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTouched(): boolean {
|
||||||
|
return this.touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected(): boolean {
|
||||||
|
return this.selectedGroups && !!this.selectedGroups.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty(): boolean {
|
||||||
|
return this.isTouched() && !this.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
setFocus(isFocused: boolean) {
|
setFocus(isFocused: boolean) {
|
||||||
this.isFocused = isFocused;
|
this.isFocused = isFocused;
|
||||||
}
|
}
|
||||||
|
@@ -302,7 +302,7 @@
|
|||||||
"INVALID_PATTERN": "Must match the pattern {{pattern}}",
|
"INVALID_PATTERN": "Must match the pattern {{pattern}}",
|
||||||
"INVALID_MIN_LENGTH": "Must be at least {{requiredLength}} characters",
|
"INVALID_MIN_LENGTH": "Must be at least {{requiredLength}} characters",
|
||||||
"INVALID_MAX_LENGTH": "Length exceeded, {{requiredLength}} characters max",
|
"INVALID_MAX_LENGTH": "Length exceeded, {{requiredLength}} characters max",
|
||||||
"REQUIRED": "Field required"
|
"REQUIRED": "This is a required field"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ADF_CLOUD_TASK_HEADER": {
|
"ADF_CLOUD_TASK_HEADER": {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<form>
|
<form>
|
||||||
<mat-form-field [floatLabel]="'auto'" class="adf-people-cloud">
|
<mat-form-field [floatLabel]="'auto'" class="adf-people-cloud" [class.adf-invalid]="hasError() && isDirty()">
|
||||||
<mat-label *ngIf="!isReadonly()" id="adf-people-cloud-title-id">{{ title | translate }}</mat-label>
|
<mat-label *ngIf="!isReadonly()" id="adf-people-cloud-title-id">{{ title | translate }}</mat-label>
|
||||||
<mat-chip-list #userMultipleChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-people-chip-list">
|
<mat-chip-list #userMultipleChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-people-chip-list">
|
||||||
<mat-chip
|
<mat-chip
|
||||||
@@ -20,8 +20,9 @@
|
|||||||
[formControl]="searchUserCtrl"
|
[formControl]="searchUserCtrl"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
[matChipInputFor]="userMultipleChipList"
|
[matChipInputFor]="userMultipleChipList"
|
||||||
|
[required]="required"
|
||||||
(focus)="setFocus(true)"
|
(focus)="setFocus(true)"
|
||||||
(blur)="setFocus(false)"
|
(blur)="setFocus(false); markAsTouched()"
|
||||||
class="adf-cloud-input"
|
class="adf-cloud-input"
|
||||||
data-automation-id="adf-people-cloud-search-input" #userInput>
|
data-automation-id="adf-people-cloud-search-input" #userInput>
|
||||||
</mat-chip-list>
|
</mat-chip-list>
|
||||||
@@ -53,23 +54,32 @@
|
|||||||
mode="indeterminate">
|
mode="indeterminate">
|
||||||
</mat-progress-bar>
|
</mat-progress-bar>
|
||||||
|
|
||||||
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
|
<div class="adf-error-container">
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : validateUsersMessage } }}</mat-error>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<mat-error *ngIf="searchUserCtrl.hasError('pattern')">
|
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : validateUsersMessage } }}</div>
|
||||||
<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>
|
||||||
<mat-error *ngIf="searchUserCtrl.hasError('minlength')">
|
<mat-error *ngIf="searchUserCtrl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</div>
|
||||||
<mat-error *ngIf="searchUserCtrl.hasError('required') || userChipsCtrl.hasError('required')">
|
</mat-error>
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
<mat-error *ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-users-typing-error">
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}</div>
|
||||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
</mat-error>
|
||||||
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : searchedValue } }}</mat-error>
|
<mat-error *ngIf="searchUserCtrl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</div>
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="(searchUserCtrl.hasError('required') || userChipsCtrl.hasError('required')) && isDirty()"
|
||||||
|
[@transitionMessages]="subscriptAnimationState" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}</div>
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused"
|
||||||
|
[@transitionMessages]="subscriptAnimationState" data-automation-id="invalid-users-typing-error" class="adf-error">
|
||||||
|
<mat-icon class="adf-error-icon">error_outline</mat-icon>
|
||||||
|
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : searchedValue } }}</div>
|
||||||
|
</mat-error>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -327,6 +327,26 @@ describe('PeopleCloudComponent', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display proper error icon', (done) => {
|
||||||
|
findUsersByNameSpy.and.returnValue(of([]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = getElement<HTMLInputElement>('input');
|
||||||
|
input.focus();
|
||||||
|
input.value = 'ZZZ';
|
||||||
|
input.dispatchEvent(new Event('keyup'));
|
||||||
|
input.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
input.blur();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const errorIcon = element.querySelector('.adf-error-icon').textContent;
|
||||||
|
expect(errorIcon).toEqual('error_outline');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when application name defined', () => {
|
describe('when application name defined', () => {
|
||||||
|
@@ -84,6 +84,11 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
readOnly: boolean = false;
|
readOnly: boolean = false;
|
||||||
|
|
||||||
|
/** Mark this field as required
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
required = false;
|
||||||
|
|
||||||
/** Array of users to be pre-selected. All users in the
|
/** Array of users to be pre-selected. All users in the
|
||||||
* array are pre-selected in multi selection mode, but only the first user
|
* array are pre-selected in multi selection mode, but only the first user
|
||||||
* is pre-selected in single selection mode.
|
* is pre-selected in single selection mode.
|
||||||
@@ -137,9 +142,10 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
invalidUsers: IdentityUserModel[] = [];
|
invalidUsers: IdentityUserModel[] = [];
|
||||||
|
|
||||||
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
|
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
|
||||||
_subscriptAnimationState: string = 'enter';
|
subscriptAnimationState: string = 'enter';
|
||||||
clientId: string;
|
clientId: string;
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
|
touched: boolean = false;
|
||||||
|
|
||||||
validateUsersMessage: string;
|
validateUsersMessage: string;
|
||||||
searchedValue = '';
|
searchedValue = '';
|
||||||
@@ -535,6 +541,22 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return this.isValidationEnabled() && this.validationLoading;
|
return this.isValidationEnabled() && this.validationLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsTouched(): void {
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTouched(): boolean {
|
||||||
|
return this.touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected(): boolean {
|
||||||
|
return this.selectedUsers && !!this.selectedUsers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty(): boolean {
|
||||||
|
return this.isTouched() && !this.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
setFocus(isFocused: boolean) {
|
setFocus(isFocused: boolean) {
|
||||||
this.isFocused = isFocused;
|
this.isFocused = isFocused;
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,11 @@ import {
|
|||||||
AppConfigServiceMock,
|
AppConfigServiceMock,
|
||||||
TranslationService,
|
TranslationService,
|
||||||
TranslationMock,
|
TranslationMock,
|
||||||
CoreModule
|
CoreModule,
|
||||||
|
IdentityUserService,
|
||||||
|
IdentityUserServiceMock,
|
||||||
|
IdentityGroupService,
|
||||||
|
IdentityGroupServiceMock
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ProcessServicesCloudModule } from '../process-services-cloud.module';
|
import { ProcessServicesCloudModule } from '../process-services-cloud.module';
|
||||||
@@ -43,7 +47,9 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
|
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
|
||||||
{ provide: AppConfigService, useClass: AppConfigServiceMock },
|
{ provide: AppConfigService, useClass: AppConfigServiceMock },
|
||||||
{ provide: TranslationService, useClass: TranslationMock }
|
{ provide: TranslationService, useClass: TranslationMock },
|
||||||
|
{ provide: IdentityUserService, useClass: IdentityUserServiceMock },
|
||||||
|
{ provide: IdentityGroupService, useClass: IdentityGroupServiceMock }
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
<div class="adf-attach-widget {{field.className}}"
|
<div class="adf-attach-widget {{field.className}}"
|
||||||
[class.adf-invalid]="!field.isValid"
|
|
||||||
[class.adf-readonly]="field.readOnly">
|
[class.adf-readonly]="field.readOnly">
|
||||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate}}
|
<label class="adf-label" [attr.for]="field.id">{{field.name | translate}}
|
||||||
<span *ngIf="isRequired()">*</span>
|
<span class="adf-asterisk" *ngIf="isRequired()">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="adf-attach-widget-container">
|
<div class="adf-attach-widget-container">
|
||||||
<div id="adf-attach-widget-simple-upload" *ngIf="isSimpleUploadButton() && isUploadButtonVisible()">
|
<div id="adf-attach-widget-simple-upload" *ngIf="isSimpleUploadButton() && isUploadButtonVisible()">
|
||||||
@@ -16,7 +15,8 @@
|
|||||||
(change)="onAttachFileChanged($event)" />
|
(change)="onAttachFileChanged($event)" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="adf-attach-widget__menu-upload" *ngIf="isUploadButtonVisible() && isMultipleSourceUpload()">
|
<div class="adf-attach-widget__menu-upload" (focusout)="markAsTouched()"
|
||||||
|
*ngIf="isUploadButtonVisible() && isMultipleSourceUpload()">
|
||||||
<button mat-raised-button color="primary" [matMenuTriggerFor]="menu" [id]="field.id">
|
<button mat-raised-button color="primary" [matMenuTriggerFor]="menu" [id]="field.id">
|
||||||
{{ 'FORM.FIELD.UPLOAD' | translate }}
|
{{ 'FORM.FIELD.UPLOAD' | translate }}
|
||||||
<mat-icon>attach_file</mat-icon>
|
<mat-icon>attach_file</mat-icon>
|
||||||
@@ -99,4 +99,4 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
<error-widget *ngIf="!field.isValid && isTouched() && !isSelected()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||||
|
@@ -227,6 +227,10 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSelected(): boolean {
|
||||||
|
return this.hasFile;
|
||||||
|
}
|
||||||
|
|
||||||
private isExternalHost(repository: AlfrescoEndpointRepresentation): boolean {
|
private isExternalHost(repository: AlfrescoEndpointRepresentation): boolean {
|
||||||
const currentECMHost = this.getDomainHost(this.appConfigService.get(AppConfigValues.ECMHOST));
|
const currentECMHost = this.getDomainHost(this.appConfigService.get(AppConfigValues.ECMHOST));
|
||||||
const chosenRepositoryHost = this.getDomainHost(repository.repositoryUrl);
|
const chosenRepositoryHost = this.getDomainHost(repository.repositoryUrl);
|
||||||
|
@@ -49,7 +49,7 @@ adf-start-task {
|
|||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-error-text-container {
|
.adf-error-container {
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ export class FormFields {
|
|||||||
completeButton = $('#adf-form-complete');
|
completeButton = $('#adf-form-complete');
|
||||||
completeNoFormButton = $('#adf-no-form-complete-button');
|
completeNoFormButton = $('#adf-no-form-complete-button');
|
||||||
cancelButton = $('#adf-no-form-cancel-button');
|
cancelButton = $('#adf-no-form-cancel-button');
|
||||||
errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text');
|
errorMessage: Locator = by.css('.adf-error-container .adf-error-text');
|
||||||
|
|
||||||
getWidget = (fieldId: string): ElementFinder => $(`adf-form-field div[id='field-${fieldId}-container']`);
|
getWidget = (fieldId: string): ElementFinder => $(`adf-form-field div[id='field-${fieldId}-container']`);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user