mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-10282] screen reader personal files perform actions empty state not announced by screen reader (#11444)
* [ACS-10282] Display and read errors for actions values * [ACS-10282] Added documentation
This commit is contained in:
+1
-1
@@ -140,7 +140,7 @@ A collection of Angular components for generic use.
|
|||||||
| [Logout directive](core/directives/logout.directive.md) | Logs the user out when the decorated element is clicked. | [Source](../lib/core/src/lib/directives/logout.directive.ts) |
|
| [Logout directive](core/directives/logout.directive.md) | Logs the user out when the decorated element is clicked. | [Source](../lib/core/src/lib/directives/logout.directive.ts) |
|
||||||
| [Node Download directive](core/directives/node-download.directive.md) | Allows folders and/or files to be downloaded, with multiple nodes packed as a '.ZIP' archive. | [Source](../lib/content-services/src/lib/directives/node-download.directive.ts) |
|
| [Node Download directive](core/directives/node-download.directive.md) | Allows folders and/or files to be downloaded, with multiple nodes packed as a '.ZIP' archive. | [Source](../lib/content-services/src/lib/directives/node-download.directive.ts) |
|
||||||
| [Upload Directive](core/directives/upload.directive.md) | Uploads content in response to file drag and drop. | [Source](../lib/core/src/lib/directives/upload.directive.ts) |
|
| [Upload Directive](core/directives/upload.directive.md) | Uploads content in response to file drag and drop. | [Source](../lib/core/src/lib/directives/upload.directive.ts) |
|
||||||
|
| [CardViewPropertyValidator Directive](core/directives/card-view-property-validator.directive.md) | Checks validators defined on property.| [Source](../lib/core/src/lib/card-view/directives/card-view-property-validator.directive.ts) |
|
||||||
### Dialogs
|
### Dialogs
|
||||||
|
|
||||||
| Name | Description | Source link |
|
| Name | Description | Source link |
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
Title: CardViewPropertyValidator directive
|
||||||
|
Added: v8.3.0
|
||||||
|
Status: Active
|
||||||
|
Last reviewed: 2025-12-10
|
||||||
|
---
|
||||||
|
|
||||||
|
# [CardViewPropertyValidator directive](../../../lib/core/src/lib/card-view/directives/card-view-property-validator.directive.ts "Defined in card-view-property-validator.directive.ts")
|
||||||
|
|
||||||
|
Checks validators defined on property.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input
|
||||||
|
adf-card-view-property-validator
|
||||||
|
[property]="property"
|
||||||
|
(validated)="onValidation($event)"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class members
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Default value | Description |
|
||||||
|
|----------|---------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------|
|
||||||
|
| property | [`CardViewBaseItemModel`](../../../lib/core/src/lib/card-view/models/card-view-baseitem.model.ts) | | Property for which validations should be triggered. |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|-----------|------------------------------------------------------------------------|----------------------------------------------------------------------------------|
|
||||||
|
| validated | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string[]>` | Emitted after validation. Emits list of errors or empty array if input is valid. |
|
||||||
@@ -12,6 +12,7 @@ backend services have been tested with each released version of ADF.
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
- [v8.3.0](#v830)
|
||||||
- [v7.0.0-alpha.3](#v700-alpha3)
|
- [v7.0.0-alpha.3](#v700-alpha3)
|
||||||
- [v6.8.0](#v680)
|
- [v6.8.0](#v680)
|
||||||
- [v6.7.0](#v670)
|
- [v6.7.0](#v670)
|
||||||
@@ -48,6 +49,14 @@ backend services have been tested with each released version of ADF.
|
|||||||
- [v2.1.0](#v210)
|
- [v2.1.0](#v210)
|
||||||
- [v2.0.0](#v200)
|
- [v2.0.0](#v200)
|
||||||
|
|
||||||
|
## v8.3.0
|
||||||
|
|
||||||
|
<!--7.0.0-alpha.3 start-->
|
||||||
|
|
||||||
|
- [CardViewPropertyValidator Directive](core/directives/card-view-property-validator.directive.md)
|
||||||
|
|
||||||
|
<!--7.0.0-alpha.3 end-->
|
||||||
|
|
||||||
## v7.0.0-alpha.3
|
## v7.0.0-alpha.3
|
||||||
|
|
||||||
<!--7.0.0-alpha.3 start-->
|
<!--7.0.0-alpha.3 start-->
|
||||||
|
|||||||
+7
-1
@@ -2,12 +2,18 @@
|
|||||||
<div class="adf-property-value">
|
<div class="adf-property-value">
|
||||||
<mat-checkbox [attr.data-automation-id]="'card-boolean-' + property.key"
|
<mat-checkbox [attr.data-automation-id]="'card-boolean-' + property.key"
|
||||||
[attr.title]="'CORE.METADATA.ACTIONS.TOGGLE' | translate"
|
[attr.title]="'CORE.METADATA.ACTIONS.TOGGLE' | translate"
|
||||||
|
[ngModel]="property.displayValue"
|
||||||
[checked]="property.displayValue"
|
[checked]="property.displayValue"
|
||||||
[disabled]="!isEditable"
|
[disabled]="!isEditable"
|
||||||
color="primary"
|
color="primary"
|
||||||
(change)="changed($event)">
|
(ngModelChange)="changed($event)"
|
||||||
|
adf-card-view-property-validator
|
||||||
|
[property]="property"
|
||||||
|
(validated)="onValidation($event)"
|
||||||
|
#checkbox="ngModel" >
|
||||||
<div [attr.data-automation-id]="'card-boolean-label-' + property.key"
|
<div [attr.data-automation-id]="'card-boolean-label-' + property.key"
|
||||||
class="adf-property-label">{{ property.label | translate }}</div>
|
class="adf-property-label">{{ property.label | translate }}</div>
|
||||||
|
<mat-error *ngIf="checkbox.touched" [innerHTML]="error" />
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
+38
-3
@@ -16,13 +16,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
|
||||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||||
import { CardViewBoolItemComponent } from './card-view-boolitem.component';
|
import { CardViewBoolItemComponent } from './card-view-boolitem.component';
|
||||||
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
||||||
import { UnitTestingUtils } from '../../../testing/unit-testing-utils';
|
import { UnitTestingUtils } from '../../../testing/unit-testing-utils';
|
||||||
import { HarnessLoader } from '@angular/cdk/testing';
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
|
import { CardViewPropertyValidatorDirective } from '../../directives/card-view-property-validator.directive';
|
||||||
|
import { FormControl, NgModel } from '@angular/forms';
|
||||||
|
import { MatError } from '@angular/material/form-field';
|
||||||
|
import { Injector } from '@angular/core';
|
||||||
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
|
||||||
describe('CardViewBoolItemComponent', () => {
|
describe('CardViewBoolItemComponent', () => {
|
||||||
let fixture: ComponentFixture<CardViewBoolItemComponent>;
|
let fixture: ComponentFixture<CardViewBoolItemComponent>;
|
||||||
@@ -170,7 +174,7 @@ describe('CardViewBoolItemComponent', () => {
|
|||||||
spyOn(cardViewUpdateService, 'update');
|
spyOn(cardViewUpdateService, 'update');
|
||||||
const property = { ...component.property };
|
const property = { ...component.property };
|
||||||
|
|
||||||
component.changed({ checked: true } as MatCheckboxChange);
|
component.changed(true);
|
||||||
|
|
||||||
expect(cardViewUpdateService.update).toHaveBeenCalledWith(property, true);
|
expect(cardViewUpdateService.update).toHaveBeenCalledWith(property, true);
|
||||||
});
|
});
|
||||||
@@ -178,7 +182,7 @@ describe('CardViewBoolItemComponent', () => {
|
|||||||
it('should update the property value after a changed', async () => {
|
it('should update the property value after a changed', async () => {
|
||||||
component.property.value = true;
|
component.property.value = true;
|
||||||
|
|
||||||
component.changed({ checked: false } as MatCheckboxChange);
|
component.changed(false);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
@@ -202,4 +206,35 @@ describe('CardViewBoolItemComponent', () => {
|
|||||||
testingUtils.clickByDataAutomationId('card-boolean-label-boolKey');
|
testingUtils.clickByDataAutomationId('card-boolean-label-boolKey');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Validation', () => {
|
||||||
|
let cardViewPropertyValidator: CardViewPropertyValidatorDirective;
|
||||||
|
let control: FormControl<string | number>;
|
||||||
|
|
||||||
|
const getCheckboxElementInjector = (): Injector => testingUtils.getByDirective(MatCheckbox).injector;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const checkboxElementInjector = getCheckboxElementInjector();
|
||||||
|
cardViewPropertyValidator = checkboxElementInjector.get(CardViewPropertyValidatorDirective);
|
||||||
|
control = checkboxElementInjector.get(NgModel).control;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have assigned correct property', () => {
|
||||||
|
expect(cardViewPropertyValidator.property).toBe(component.property);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display correct error', () => {
|
||||||
|
cardViewPropertyValidator.validated.emit(['Error 1', 'Error 2']);
|
||||||
|
control.setErrors({
|
||||||
|
error1: 'Error 1',
|
||||||
|
error2: 'Error 2'
|
||||||
|
});
|
||||||
|
control.markAsTouched();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(testingUtils.getByDirective(MatError).nativeElement.textContent).toBe('Error 1Error 2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+18
-5
@@ -16,15 +16,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
||||||
import { BaseCardView } from '../base-card-view';
|
import { BaseCardView } from '../base-card-view';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslatePipe } from '@ngx-translate/core';
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
|
import { CardViewPropertyValidatorDirective } from '../../directives/card-view-property-validator.directive';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatError } from '@angular/material/form-field';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-card-view-boolitem',
|
selector: 'adf-card-view-boolitem',
|
||||||
imports: [CommonModule, MatCheckboxModule, TranslatePipe],
|
imports: [CommonModule, MatCheckboxModule, TranslatePipe, CardViewPropertyValidatorDirective, FormsModule, MatError],
|
||||||
templateUrl: './card-view-boolitem.component.html',
|
templateUrl: './card-view-boolitem.component.html',
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
@@ -38,8 +41,18 @@ export class CardViewBoolItemComponent extends BaseCardView<CardViewBoolItemMode
|
|||||||
@Input()
|
@Input()
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
|
|
||||||
changed(change: MatCheckboxChange) {
|
private _error: string;
|
||||||
this.cardViewUpdateService.update({ ...this.property } as CardViewBoolItemModel, change.checked);
|
|
||||||
this.property.value = change.checked;
|
get error(): string {
|
||||||
|
return this._error;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed(checked: boolean) {
|
||||||
|
this.cardViewUpdateService.update({ ...this.property } as CardViewBoolItemModel, checked);
|
||||||
|
this.property.value = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
onValidation(errors: string[]): void {
|
||||||
|
this._error = errors.join('<br>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -27,12 +27,15 @@
|
|||||||
>{{ property.label | translate }}
|
>{{ property.label | translate }}
|
||||||
</mat-label>
|
</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
[(value)]="value"
|
[(ngModel)]="value"
|
||||||
[ngClass]="{ 'adf-property-readonly-value': isReadonlyProperty }"
|
[ngClass]="{ 'adf-property-readonly-value': isReadonlyProperty }"
|
||||||
panelClass="adf-select-filter"
|
panelClass="adf-select-filter"
|
||||||
(selectionChange)="onChange($event)"
|
(selectionChange)="onChange($event)"
|
||||||
data-automation-class="select-box"
|
data-automation-class="select-box"
|
||||||
[aria-label]="property.label | translate"
|
[aria-label]="property.label | translate"
|
||||||
|
adf-card-view-property-validator
|
||||||
|
[property]="property"
|
||||||
|
(validated)="onValidation($event)"
|
||||||
>
|
>
|
||||||
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)" />
|
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)" />
|
||||||
<mat-option *ngIf="displayNoneOption">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
<mat-option *ngIf="displayNoneOption">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
||||||
@@ -40,6 +43,7 @@
|
|||||||
{{ option.label | translate }}
|
{{ option.label | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
<mat-error [innerHTML]="error" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#{ms.$mat-form-field-subscript-wrapper} {
|
#{ms.$mat-form-field-subscript-wrapper} {
|
||||||
display: none;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-property-read-only {
|
.adf-property-read-only {
|
||||||
|
|||||||
+39
-2
@@ -26,6 +26,9 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
|||||||
import { UnitTestingUtils } from '../../../testing/unit-testing-utils';
|
import { UnitTestingUtils } from '../../../testing/unit-testing-utils';
|
||||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { CardViewPropertyValidatorDirective } from '../../directives/card-view-property-validator.directive';
|
||||||
|
import { MatError } from '@angular/material/form-field';
|
||||||
|
import { FormControl, NgModel } from '@angular/forms';
|
||||||
|
|
||||||
describe('CardViewSelectItemComponent', () => {
|
describe('CardViewSelectItemComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@@ -59,6 +62,8 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
editable: true
|
editable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSelectElement = (): DebugElement => testingUtils.getByDataAutomationClass('select-box');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CardViewSelectItemComponent]
|
imports: [CardViewSelectItemComponent]
|
||||||
@@ -96,10 +101,9 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
|
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const selectBox = testingUtils.getByDataAutomationClass('select-box');
|
|
||||||
|
|
||||||
expect(getReadOnlyElement()).not.toBeNull();
|
expect(getReadOnlyElement()).not.toBeNull();
|
||||||
expect(selectBox).toBeNull();
|
expect(getSelectElement()).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should read only value have title', () => {
|
it('should read only value have title', () => {
|
||||||
@@ -336,4 +340,37 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
expect(await options[1].getText()).toContain('Option 2');
|
expect(await options[1].getText()).toContain('Option 2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Validation', () => {
|
||||||
|
let cardViewPropertyValidator: CardViewPropertyValidatorDirective;
|
||||||
|
let control: FormControl<string | number>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.property = new CardViewSelectItemModel({
|
||||||
|
...mockDefaultProps,
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
component.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const selectElementInjector = getSelectElement().injector;
|
||||||
|
cardViewPropertyValidator = selectElementInjector.get(CardViewPropertyValidatorDirective);
|
||||||
|
control = selectElementInjector.get(NgModel).control;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have assigned correct property', () => {
|
||||||
|
expect(cardViewPropertyValidator.property).toBe(component.property);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display correct error', () => {
|
||||||
|
cardViewPropertyValidator.validated.emit(['Error 1', 'Error 2']);
|
||||||
|
control.setErrors({
|
||||||
|
error1: 'Error 1',
|
||||||
|
error2: 'Error 2'
|
||||||
|
});
|
||||||
|
control.markAsTouched();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(testingUtils.getByDirective(MatError).nativeElement.textContent).toBe('Error 1Error 2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-3
@@ -29,8 +29,9 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|||||||
import { SelectFilterInputComponent } from './select-filter-input/select-filter-input.component';
|
import { SelectFilterInputComponent } from './select-filter-input/select-filter-input.component';
|
||||||
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { CardViewPropertyValidatorDirective } from '../../directives/card-view-property-validator.directive';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-card-view-selectitem',
|
selector: 'adf-card-view-selectitem',
|
||||||
@@ -42,7 +43,9 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|||||||
SelectFilterInputComponent,
|
SelectFilterInputComponent,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule,
|
||||||
|
CardViewPropertyValidatorDirective,
|
||||||
|
FormsModule
|
||||||
],
|
],
|
||||||
templateUrl: './card-view-selectitem.component.html',
|
templateUrl: './card-view-selectitem.component.html',
|
||||||
styleUrls: ['./card-view-selectitem.component.scss'],
|
styleUrls: ['./card-view-selectitem.component.scss'],
|
||||||
@@ -50,7 +53,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|||||||
host: { class: 'adf-card-view-selectitem' }
|
host: { class: 'adf-card-view-selectitem' }
|
||||||
})
|
})
|
||||||
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string | number>> implements OnInit, OnChanges {
|
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string | number>> implements OnInit, OnChanges {
|
||||||
private appConfig = inject(AppConfigService);
|
|
||||||
static HIDE_FILTER_LIMIT = 5;
|
static HIDE_FILTER_LIMIT = 5;
|
||||||
|
|
||||||
@Input() options$: Observable<CardViewSelectItemOption<string | number>[]>;
|
@Input() options$: Observable<CardViewSelectItemOption<string | number>[]>;
|
||||||
@@ -70,6 +72,13 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
editedValue: string | number;
|
editedValue: string | number;
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
private readonly appConfig = inject(AppConfigService);
|
||||||
|
|
||||||
|
private _error = '';
|
||||||
|
|
||||||
|
get error(): string {
|
||||||
|
return this._error;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this.value = this.property.value;
|
this.value = this.property.value;
|
||||||
@@ -142,6 +151,10 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
this.property.value = selectedOption;
|
this.property.value = selectedOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onValidation(errors: string[]): void {
|
||||||
|
this._error = errors.join('<br>');
|
||||||
|
}
|
||||||
|
|
||||||
get showProperty(): boolean {
|
get showProperty(): boolean {
|
||||||
return this.displayEmpty || !this.property.isEmpty();
|
return this.displayEmpty || !this.property.isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -38,6 +38,8 @@
|
|||||||
[title]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate"
|
[title]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate"
|
||||||
[attr.data-automation-id]="'card-textitem-value-' + property.key"
|
[attr.data-automation-id]="'card-textitem-value-' + property.key"
|
||||||
(keydown)="undoText($event)"
|
(keydown)="undoText($event)"
|
||||||
|
(blur)="update()"
|
||||||
|
[aria-describedby]="'adf-card-textitem-error-' + property.key"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
matInput
|
matInput
|
||||||
@@ -142,6 +144,7 @@
|
|||||||
(keydown.enter)="update()"
|
(keydown.enter)="update()"
|
||||||
[readonly]="!isEditable"
|
[readonly]="!isEditable"
|
||||||
[attr.data-automation-id]="'card-textitem-value-' + property.key"
|
[attr.data-automation-id]="'card-textitem-value-' + property.key"
|
||||||
|
[aria-describedby]="'adf-card-textitem-error-' + property.key"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
@@ -160,7 +163,13 @@
|
|||||||
<span class="adf-textitem-default-value">{{ property.default | translate }}</span>
|
<span class="adf-textitem-default-value">{{ property.default | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-error *ngIf="isEditable && hasErrors" class="adf-textitem-error" [attr.data-automation-id]="'card-textitem-error-' + property.key">
|
<mat-error
|
||||||
|
*ngIf="isEditable && hasErrors"
|
||||||
|
class="adf-textitem-error"
|
||||||
|
[attr.data-automation-id]="'card-textitem-error-' + property.key"
|
||||||
|
[id]="'adf-card-textitem-error-' + property.key"
|
||||||
|
role="alert"
|
||||||
|
aria-live="polite">
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let error of errors">{{ error.message | translate : error }}</li>
|
<li *ngFor="let error of errors">{{ error.message | translate : error }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+60
@@ -191,6 +191,66 @@ describe('CardViewTextItemComponent', () => {
|
|||||||
expect(await getTextFieldValue(component.property.key)).toBe('FAKE-DEFAULT-KEY');
|
expect(await getTextFieldValue(component.property.key)).toBe('FAKE-DEFAULT-KEY');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set errors on textInput when blur event is triggered and field is invalid', async () => {
|
||||||
|
component.property = new CardViewTextItemModel({
|
||||||
|
label: 'Name label',
|
||||||
|
value: { id: 123, displayName: 'User Name' },
|
||||||
|
key: 'namekey',
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
spyOn(component.property, 'isValid').and.returnValue(false);
|
||||||
|
component.editable = true;
|
||||||
|
spyOn(component.textInput, 'setErrors');
|
||||||
|
const textField = await getTextField(component.property.key);
|
||||||
|
await textField.blur();
|
||||||
|
|
||||||
|
expect(component.textInput.setErrors).toHaveBeenCalledWith({
|
||||||
|
customError: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call markAsTouched on textInput when blur event is triggered and field is invalid', async () => {
|
||||||
|
component.property = new CardViewTextItemModel({
|
||||||
|
label: 'Name label',
|
||||||
|
value: { id: 123, displayName: 'User Name' },
|
||||||
|
key: 'namekey',
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
spyOn(component.property, 'isValid').and.returnValue(false);
|
||||||
|
component.editable = true;
|
||||||
|
spyOn(component.textInput, 'markAsTouched');
|
||||||
|
const textField = await getTextField(component.property.key);
|
||||||
|
await textField.blur();
|
||||||
|
|
||||||
|
expect(component.textInput.markAsTouched).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render errors when blur event is triggered and field is invalid', async () => {
|
||||||
|
component.property = new CardViewTextItemModel({
|
||||||
|
label: 'Name label',
|
||||||
|
value: { id: 123, displayName: 'User Name' },
|
||||||
|
key: 'namekey',
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
spyOn(component.property, 'isValid').and.returnValue(false);
|
||||||
|
spyOn(component.property, 'getValidationErrors').and.returnValue([
|
||||||
|
{
|
||||||
|
message: 'Error 1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Error 2'
|
||||||
|
}
|
||||||
|
] as CardViewItemValidator[]);
|
||||||
|
component.editable = true;
|
||||||
|
const textField = await getTextField(component.property.key);
|
||||||
|
await textField.blur();
|
||||||
|
|
||||||
|
expect(getErrorElements(component.property.key, true).map((element) => element.nativeElement.textContent)).toEqual([
|
||||||
|
'Error 1',
|
||||||
|
'Error 2'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should render value when editable:true', async () => {
|
it('should render value when editable:true', async () => {
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.property.editable = true;
|
component.property.editable = true;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 { Directive, EventEmitter, forwardRef, Input, Output } from '@angular/core';
|
||||||
|
import { AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
|
||||||
|
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[adf-card-view-property-validator]',
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => CardViewPropertyValidatorDirective),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CardViewPropertyValidatorDirective {
|
||||||
|
@Input()
|
||||||
|
property: CardViewBaseItemModel;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
validated = new EventEmitter<string[]>();
|
||||||
|
|
||||||
|
constructor(private readonly translateService: TranslateService) {}
|
||||||
|
|
||||||
|
validate(control: AbstractControl): ValidationErrors | null {
|
||||||
|
const errors: ValidationErrors | null = this.property.isValid(control.value)
|
||||||
|
? null
|
||||||
|
: Object.fromEntries(this.property.validators.map((validator) => [validator.message, this.translateService.instant(validator.message)]));
|
||||||
|
this.validated.emit(errors ? Object.values(errors) : []);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 { Component, Input } from '@angular/core';
|
||||||
|
import { CardViewPropertyValidatorDirective } from './card-view-property-validator.directive';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CardViewBaseItemModel, CardViewTextItemModel, UnitTestingUtils } from '@alfresco/adf-core';
|
||||||
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
imports: [CardViewPropertyValidatorDirective, ReactiveFormsModule],
|
||||||
|
template: `<input [formControl]="control" adf-card-view-property-validator [property]="property" />`
|
||||||
|
})
|
||||||
|
class MockComponent {
|
||||||
|
@Input()
|
||||||
|
property: CardViewBaseItemModel;
|
||||||
|
|
||||||
|
control = new FormControl('');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CardViewPropertyValidatorDirective', () => {
|
||||||
|
let fixture: ComponentFixture<MockComponent>;
|
||||||
|
let component: MockComponent;
|
||||||
|
let unitTestingUtils: UnitTestingUtils;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [MockComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(MockComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Validation', () => {
|
||||||
|
let validatorDirective: CardViewPropertyValidatorDirective;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.property = new CardViewTextItemModel({
|
||||||
|
label: 'Some label',
|
||||||
|
key: 'Some key',
|
||||||
|
value: 'Some value'
|
||||||
|
});
|
||||||
|
validatorDirective = unitTestingUtils.getByCSS('input').injector.get(CardViewPropertyValidatorDirective);
|
||||||
|
spyOn(validatorDirective.validated, 'emit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should control be valid if property has valid value', () => {
|
||||||
|
spyOn(component.property, 'isValid').and.returnValue(true);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.control.valid).toBeTrue();
|
||||||
|
expect(component.control.errors).toBeNull();
|
||||||
|
expect(validatorDirective.validated.emit).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should control be invalid if property has invalid value', () => {
|
||||||
|
spyOn(component.property, 'isValid').and.returnValue(false);
|
||||||
|
const translationKey = 'ERROR_TRANSLATION_KEY';
|
||||||
|
component.property.validators = [
|
||||||
|
{
|
||||||
|
isValid: (): boolean => false,
|
||||||
|
message: translationKey
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const error = 'Some error';
|
||||||
|
const translateService = TestBed.inject(TranslateService);
|
||||||
|
spyOn(translateService, 'instant').and.returnValue(error);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.control.valid).toBeFalse();
|
||||||
|
expect(component.control.errors).toEqual({
|
||||||
|
[translationKey]: error
|
||||||
|
});
|
||||||
|
expect(validatorDirective.validated.emit).toHaveBeenCalledWith([error]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user