[ACS-10196] other a11y all libraries error message not provided for name and library id fields (#11311)

* [ACS-10196] error message for required fields, unit test coverage, refactor deprecated code

* [ACS-10196] use constructor instead of assertion

* [ACS-10196] fix sonar errors

* [ACS-10196] fix sonar errors

* [ACS-10196] code review fixes

* [ACS-10196] fix after setting autofocus to false
This commit is contained in:
Grzegorz Jaśkowski
2025-11-07 09:05:55 +01:00
committed by GitHub
parent e28e924b87
commit 38d98200a6
4 changed files with 321 additions and 176 deletions
@@ -11,21 +11,18 @@
autocomplete="off"
adf-auto-focus
/>
<mat-hint *ngIf="libraryTitleExists">{{
'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate
}}</mat-hint>
<mat-error *ngIf="form.controls['title'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}
</mat-error>
<mat-error *ngIf="form.controls['title'].hasError('minlength')">
{{ 'LIBRARY.ERRORS.TITLE_TOO_SHORT' | translate }}
</mat-error>
<mat-error *ngIf="form.controls['title'].errors?.message">
{{ form.controls['title'].errors?.message | translate }}
</mat-error>
@if (libraryTitleExists) {
<mat-hint>{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint>
}
@if (form.controls['title'].errors?.maxlength) {
<mat-error>{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}</mat-error>
}
@else if (form.controls['title'].errors?.message) {
<mat-error>{{ form.controls['title'].errors?.message | translate }}</mat-error>
}
@else if (form.controls['title'].errors?.required) {
<mat-error>{{ 'LIBRARY.ERRORS.NAME_REQUIRED' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="adf-library-dialog-form-field">
@@ -37,13 +34,15 @@
autocomplete="off"
/>
<mat-error *ngIf="form.controls['id'].errors?.message">
{{ form.controls['id'].errors?.message | translate }}
</mat-error>
<mat-error *ngIf="form.controls['id'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}
</mat-error>
@if (form.controls['id'].errors?.message) {
<mat-error>{{ form.controls['id'].errors?.message | translate }}</mat-error>
}
@else if (form.controls['id'].errors?.maxlength) {
<mat-error>{{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}</mat-error>
}
@else if (form.controls['id'].errors?.required) {
<mat-error>{{ 'LIBRARY.ERRORS.ID_REQUIRED' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="adf-library-dialog-form-field adf-library-dialog-form-field-description">
@@ -53,10 +52,9 @@
rows="3"
formControlName="description"
></textarea>
<mat-error *ngIf="form.controls['description'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
</mat-error>
@if (form.controls['description'].errors?.maxlength) {
<mat-error>{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}</mat-error>
}
</mat-form-field>
<mat-radio-group
@@ -65,17 +63,18 @@
[(ngModel)]="visibilityOption"
(change)="visibilityChangeHandler($event)"
>
<mat-radio-button
color="primary"
class="adf-library-dialog-radio-group-button"
[disabled]="option.disabled"
*ngFor="let option of visibilityOptions"
[attr.data-automation-id]="option.value"
[value]="option.value"
[checked]="visibilityOption.value === option.value"
>
{{ option.label | translate }}
</mat-radio-button>
@for (option of visibilityOptions; track option.value) {
<mat-radio-button
color="primary"
class="adf-library-dialog-radio-group-button"
[disabled]="option.disabled"
[attr.data-automation-id]="option.value"
[value]="option.value"
[checked]="visibilityOption === option.value"
>
{{ option.label | translate }}
</mat-radio-button>
}
</mat-radio-group>
</form>
</mat-dialog-content>
@@ -22,21 +22,40 @@ import { MatDialogRef } from '@angular/material/dialog';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { of, throwError } from 'rxjs';
import { delay } from 'rxjs/operators';
import { SiteEntry } from '@alfresco/js-api';
import { FindQuery, SiteEntry, SitePaging } from '@alfresco/js-api';
import { SitesService } from '../../common/services/sites.service';
import { NotificationService } from '@alfresco/adf-core';
import { NotificationService, UnitTestingUtils } from '@alfresco/adf-core';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatRadioGroupHarness } from '@angular/material/radio/testing';
describe('LibraryDialogComponent', () => {
let fixture: ComponentFixture<LibraryDialogComponent>;
let component: LibraryDialogComponent;
let sitesService: SitesService;
let findSitesSpy;
let findSitesSpy: jasmine.Spy<(term: string, opts?: FindQuery) => Promise<SitePaging>>;
let notificationService: NotificationService;
const findSitesResponse = { list: { entries: [] } };
let unitTestingUtils: UnitTestingUtils;
let loader: HarnessLoader;
const findSitesResponse = new SitePaging({ list: { entries: [], pagination: {} } });
const dialogRef = {
close: jasmine.createSpy('close')
};
/**
* Sets form field value and triggers necessary events to process value changes
*
* @param fieldName Name of the form field
* @param value Value to set
*/
function setFormFieldValue(fieldName: string, value: string) {
component.form.controls[fieldName].setValue(value);
tick(500);
flush();
fixture.detectChanges();
}
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentTestingModule, LibraryDialogComponent],
@@ -48,6 +67,8 @@ describe('LibraryDialogComponent', () => {
sitesService = TestBed.inject(SitesService);
findSitesSpy = spyOn(component['queriesApi'], 'findSites');
notificationService = TestBed.inject(NotificationService);
loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
});
afterEach(() => {
@@ -56,45 +77,33 @@ describe('LibraryDialogComponent', () => {
it('should set library id automatically on title input', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('libraryTitle');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'libraryTitle');
expect(component.form.controls.id.value).toBe('libraryTitle');
}));
it('should translate library title space character to dash for library id', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'library title');
expect(component.form.controls.id.value).toBe('library-title');
}));
it('should not change custom library id on title input', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.id.setValue('custom-id');
setFormFieldValue('id', 'custom-id');
component.form.controls.id.markAsDirty();
tick(500);
flush();
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'library title');
expect(component.form.controls.id.value).toBe('custom-id');
}));
@@ -103,10 +112,7 @@ describe('LibraryDialogComponent', () => {
spyOn(sitesService, 'getSite').and.returnValue(of(null));
fixture.detectChanges();
component.form.controls.id.setValue('existingLibrary');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('id', 'existingLibrary');
expect(component.form.controls.id.errors).toEqual({
message: 'LIBRARY.ERRORS.EXISTENT_SITE'
@@ -117,13 +123,10 @@ describe('LibraryDialogComponent', () => {
it('should create site when form is valid', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'createSite').and.returnValue(of({ entry: { id: 'fake-id' } } as SiteEntry).pipe(delay(100)));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'library title');
component.submit();
fixture.detectChanges();
@@ -152,10 +155,7 @@ describe('LibraryDialogComponent', () => {
spyOn(sitesService, 'getSite').and.returnValue(of(null));
fixture.detectChanges();
component.form.controls.title.setValue('existingLibrary');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'existingLibrary');
component.submit();
fixture.detectChanges();
@@ -166,108 +166,231 @@ describe('LibraryDialogComponent', () => {
it('should notify when library title is already used', fakeAsync(() => {
spyOn(sitesService, 'getSite').and.returnValue(of(null));
findSitesSpy.and.returnValue(Promise.resolve({ list: { entries: [{ entry: { title: 'TEST', id: 'library-id' } }] } }));
const sitePaging = new SitePaging({
list: {
entries: [
{
entry: {
id: 'library-id',
title: 'TEST',
guid: '',
visibility: 'PUBLIC'
}
}
],
pagination: {}
}
});
findSitesSpy.and.returnValue(Promise.resolve(sitePaging));
fixture.detectChanges();
component.form.controls.title.setValue('test');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'test');
expect(component.libraryTitleExists).toBe(true);
}));
it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
const error = { message: '{ "error": { "statusCode": 409 } }' };
spyOn(sitesService, 'createSite').and.callFake(() => throwError(error));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
describe('create site error', () => {
/**
* Performs setup for create site errors related tests.
*
* @param statusCode Optional status code to include in the error message.
*/
function throwCreateError(statusCode?: number) {
let error = {};
if (statusCode) {
error = { message: `{ "error": { "statusCode": ${statusCode} } }` };
}
fixture.detectChanges();
component.form.controls.title.setValue('test');
tick(500);
flush();
fixture.detectChanges();
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'createSite').and.callFake(() => throwError(() => error));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
component.submit();
fixture.detectChanges();
flush();
fixture.detectChanges();
setFormFieldValue('title', 'test');
expect(component.form.controls.id.errors).toEqual({
message: 'LIBRARY.ERRORS.CONFLICT'
});
}));
component.submit();
fixture.detectChanges();
flush();
}
it('should handle default errors and show generic error in snackbar', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
const error = {};
spyOn(sitesService, 'createSite').and.callFake(() => throwError(error));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(notificationService, 'showError').and.callThrough();
it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => {
throwCreateError(409);
expect(component.form.controls.id.errors).toEqual({
message: 'LIBRARY.ERRORS.CONFLICT'
});
}));
fixture.detectChanges();
component.form.controls.title.setValue('test');
tick(500);
flush();
fixture.detectChanges();
it('should show generic error notification on error code other than 409', fakeAsync(() => {
spyOn(notificationService, 'showError');
throwCreateError(404);
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
}));
component.submit();
fixture.detectChanges();
flush();
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
}));
it('should handle default errors and show generic error in snackbar', fakeAsync(() => {
spyOn(notificationService, 'showError');
throwCreateError();
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
}));
});
it('should not translate library title if value is not a valid id', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('@@@####');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', '@@@####');
expect(component.form.controls.id.value).toBe(null);
}));
it('should translate library title partially for library id', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('@@@####library');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', '@@@####library');
expect(component.form.controls.id.value).toBe('library');
}));
it('should translate library title multiple space character to one dash for library id', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'library title');
expect(component.form.controls.id.value).toBe('library-title');
}));
it('should invalidate library title if is too short', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
fixture.detectChanges();
component.form.controls.title.setValue('l');
tick(500);
flush();
fixture.detectChanges();
setFormFieldValue('title', 'l');
expect(component.form.controls.title.errors['minlength']).toBeTruthy();
expect(component.form.controls.title.errors).toEqual({
message: 'LIBRARY.ERRORS.TITLE_TOO_SHORT'
});
expect(component.form.valid).toBe(false);
setFormFieldValue('title', 'l ');
expect(component.form.controls.title.errors).toEqual({
message: 'LIBRARY.ERRORS.TITLE_TOO_SHORT'
});
expect(component.form.valid).toBe(false);
}));
it('should handle getters when form fields have no values', () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.title.setValue(null);
component.form.controls.id.setValue(null);
component.visibilityOption = null;
expect(component.title).toBe('');
expect(component.id).toBe('');
expect(component.visibility).toBe('');
});
it('should handle visibility change', async () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
const radioGroup = await loader.getHarness(MatRadioGroupHarness);
const radioButtons = await radioGroup.getRadioButtons();
await radioButtons[1].check();
fixture.detectChanges();
expect(component.visibilityOption).toBe('PRIVATE');
expect(component.visibility).toBe('PRIVATE');
});
it('should set libraryTitleExists to false when findSites returns no sites', () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.title.setValue('library');
fixture.detectChanges();
expect(component.libraryTitleExists).toBe(false);
});
it('should clear timeout if validator is called multiple times', () => {
spyOn(sitesService, 'getSite').and.returnValue(of(null));
spyOn(window, 'clearTimeout').and.callThrough();
fixture.detectChanges();
component.form.controls.id.setValue('first');
component.form.controls.id.setValue('second');
expect(window.clearTimeout).toHaveBeenCalled();
});
it('should catch error if findLibraryByTitle fails', async () => {
findSitesSpy.and.returnValue(Promise.reject(new Error('error')));
const result = await component['findLibraryByTitle']('library');
expect(result).toEqual(findSitesResponse);
});
it('should show correct error message when value is only spaces', () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.title.setValue(' ');
expect(component.form.controls.title.errors).toEqual({ message: 'LIBRARY.ERRORS.ONLY_SPACES' });
});
it('should show correct error message when value contains special characters', () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.id.setValue('wąż');
expect(component.form.controls.id.errors).toEqual({ message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS' });
});
it('should not show required error on opening dialog', async () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
const titleFormField = await unitTestingUtils.getMatFormField();
const errors = await titleFormField.getTextErrors();
expect(errors.length).toBe(0);
});
it('should mark title as touched on value change', fakeAsync(() => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
spyOn(sitesService, 'getSite').and.returnValue(of(null));
fixture.detectChanges();
expect(component.form.controls.title.touched).toBeFalse();
setFormFieldValue('title', 'library title');
expect(component.form.controls.title.touched).toBeTrue();
}));
it('should show correct error message when there is no library title', async () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.title.setValue('');
fixture.detectChanges();
const titleFormField = await unitTestingUtils.getMatFormField();
const errors = await titleFormField.getTextErrors();
expect(component.form.controls.title.errors).toEqual({ required: true });
expect(errors[0]).toContain('LIBRARY.ERRORS.NAME_REQUIRED');
});
it('should show correct error message when there is no library id', async () => {
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
fixture.detectChanges();
component.form.controls.id.setValue('');
component.form.controls.id.markAsTouched();
fixture.detectChanges();
const idFormField = await unitTestingUtils.getMatFormFieldByCSS('.adf-library-dialog-form-field:nth-of-type(2)');
const errors = await idFormField.getTextErrors();
expect(component.form.controls.id.errors).toEqual({ required: true });
expect(errors[0]).toContain('LIBRARY.ERRORS.ID_REQUIRED');
});
});
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Observable } from 'rxjs';
import { from, Observable } from 'rxjs';
import { Component, DestroyRef, EventEmitter, inject, OnInit, Output, ViewEncapsulation } from '@angular/core';
import {
AbstractControl,
@@ -24,23 +24,30 @@ import {
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validators
} from '@angular/forms';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { QueriesApi, SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api';
import { NotificationService } from '@alfresco/adf-core';
import { debounceTime, finalize, mergeMap } from 'rxjs/operators';
import { debounceTime, finalize, map, mergeMap, take } from 'rxjs/operators';
import { SitesService } from '../../common/services/sites.service';
import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { AutoFocusDirective } from '../../directives';
import { MatRadioModule } from '@angular/material/radio';
import { MatRadioChange, MatRadioModule } from '@angular/material/radio';
import { MatButtonModule } from '@angular/material/button';
import { AlfrescoApiService } from '../../services';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
interface VisibilityOption {
value: string;
label: string;
disabled: boolean;
}
@Component({
selector: 'adf-library-dialog',
imports: [
@@ -71,13 +78,13 @@ export class LibraryDialogComponent implements OnInit {
* newly-created library.
*/
@Output()
success = new EventEmitter<any>();
success = new EventEmitter<SiteEntry>();
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
libraryTitleExists = false;
form: UntypedFormGroup;
visibilityOption: any;
visibilityOptions = [
visibilityOption: string;
visibilityOptions: VisibilityOption[] = [
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
{
@@ -107,7 +114,7 @@ export class LibraryDialogComponent implements OnInit {
ngOnInit() {
const validators = {
id: [Validators.required, Validators.maxLength(72), this.forbidSpecialCharacters],
title: [Validators.required, this.forbidOnlySpaces, Validators.minLength(2), Validators.maxLength(256)],
title: [Validators.required, this.forbidOnlySpaces, this.minLengthTrimmed, Validators.maxLength(256)],
description: [Validators.maxLength(512)]
};
@@ -119,13 +126,14 @@ export class LibraryDialogComponent implements OnInit {
this.visibilityOption = this.visibilityOptions[0].value;
this.form.controls['title'].valueChanges
.pipe(take(1), takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.form.controls['title'].markAsTouched());
this.form.controls['title'].valueChanges
.pipe(
debounceTime(500),
mergeMap(
(title) => this.checkLibraryNameExists(title),
(title) => title
),
mergeMap((title) => from(this.checkLibraryNameExists(title)).pipe(map(() => title))),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((title: string) => {
@@ -168,16 +176,16 @@ export class LibraryDialogComponent implements OnInit {
this.disableCreateButton = true;
this.create()
.pipe(finalize(() => (this.disableCreateButton = false)))
.subscribe(
(node: SiteEntry) => {
.subscribe({
next: (node: SiteEntry) => {
this.success.emit(node);
dialog.close(node);
},
(error) => this.handleError(error)
);
error: (error) => this.handleError(error)
});
}
visibilityChangeHandler(event) {
visibilityChangeHandler(event: MatRadioChange) {
this.visibilityOption = event.value;
}
@@ -224,13 +232,7 @@ export class LibraryDialogComponent implements OnInit {
}
private async checkLibraryNameExists(libraryTitle: string) {
let entries = [];
try {
entries = (await this.findLibraryByTitle(libraryTitle)).list.entries;
} catch {
entries = [];
}
const entries = (await this.findLibraryByTitle(libraryTitle)).list.entries;
if (entries.length) {
this.libraryTitleExists = entries[0].entry.title.toLowerCase() === libraryTitle.toLowerCase();
@@ -239,20 +241,24 @@ export class LibraryDialogComponent implements OnInit {
}
}
private findLibraryByTitle(libraryTitle: string): Promise<SitePaging> {
return this.queriesApi.findSites(libraryTitle, {
maxItems: 1,
fields: ['title']
});
private async findLibraryByTitle(libraryTitle: string): Promise<SitePaging> {
try {
return await this.queriesApi.findSites(libraryTitle, {
maxItems: 1,
fields: ['title']
});
} catch {
return new SitePaging({ list: { entries: [], pagination: {} } });
}
}
private forbidSpecialCharacters({ value }: UntypedFormControl) {
private forbidSpecialCharacters({ value }: UntypedFormControl): ValidationErrors | null {
if (value === null || value.length === 0) {
return null;
}
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
const isValid: boolean = !validCharacters.test(value);
const isValid = !validCharacters.test(value);
return isValid
? null
@@ -261,12 +267,12 @@ export class LibraryDialogComponent implements OnInit {
};
}
private forbidOnlySpaces({ value }: UntypedFormControl) {
private forbidOnlySpaces({ value }: UntypedFormControl): ValidationErrors | null {
if (value === null || value.length === 0) {
return null;
}
const isValid: boolean = !!(value || '').trim();
const isValid = !!value.trim();
return isValid
? null
@@ -275,24 +281,39 @@ export class LibraryDialogComponent implements OnInit {
};
}
private createSiteIdValidator() {
private minLengthTrimmed({ value }: UntypedFormControl): ValidationErrors | null {
if (value === null || value.length === 0) {
return null;
}
const isValid = value.trim().length !== 1;
return isValid
? null
: {
message: 'LIBRARY.ERRORS.TITLE_TOO_SHORT'
};
}
private createSiteIdValidator(): (control: AbstractControl) => Promise<ValidationErrors | null> {
let timer;
return (control: AbstractControl) => {
if (timer) {
clearTimeout(timer);
}
return new Promise((resolve) => {
timer = setTimeout(
() =>
this.sitesService.getSite(control.value).subscribe(
() => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
() => resolve(null)
),
300
);
timer = setTimeout(() => {
this.checkSite(control.value, resolve);
}, 300);
});
};
}
private checkSite(siteId: string, resolve: (result: ValidationErrors | null) => void): void {
this.sitesService.getSite(siteId).subscribe({
next: () => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
error: () => resolve(null)
});
}
}
+3 -1
View File
@@ -659,7 +659,9 @@
"TITLE_TOO_SHORT": "Title must be at least 2 characters long",
"ILLEGAL_CHARACTERS": "Use numbers and letters only",
"ONLY_SPACES": "Library name can't contain only spaces",
"LIBRARY_UPDATE_ERROR": "There was an error updating library properties"
"LIBRARY_UPDATE_ERROR": "There was an error updating library properties",
"NAME_REQUIRED": "Library name is required",
"ID_REQUIRED": "Library ID is required"
},
"SUCCESS": {
"LIBRARY_UPDATED": "Library properties updated"