From 38d98200a6d52dad612acc29e2d0bb4a02bbb151 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Grzegorz=20Ja=C5=9Bkowski?=
<138671284+g-jaskowski@users.noreply.github.com>
Date: Fri, 7 Nov 2025 09:05:55 +0100
Subject: [PATCH] [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
---
.../lib/dialogs/library/library.dialog.html | 73 ++--
.../dialogs/library/library.dialog.spec.ts | 315 ++++++++++++------
.../src/lib/dialogs/library/library.dialog.ts | 105 +++---
lib/content-services/src/lib/i18n/en.json | 4 +-
4 files changed, 321 insertions(+), 176 deletions(-)
diff --git a/lib/content-services/src/lib/dialogs/library/library.dialog.html b/lib/content-services/src/lib/dialogs/library/library.dialog.html
index 42fb246dbe..6fb35bac7c 100644
--- a/lib/content-services/src/lib/dialogs/library/library.dialog.html
+++ b/lib/content-services/src/lib/dialogs/library/library.dialog.html
@@ -11,21 +11,18 @@
autocomplete="off"
adf-auto-focus
/>
-
- {{
- 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate
- }}
-
- {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}
-
-
-
- {{ 'LIBRARY.ERRORS.TITLE_TOO_SHORT' | translate }}
-
-
-
- {{ form.controls['title'].errors?.message | translate }}
-
+ @if (libraryTitleExists) {
+ {{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}
+ }
+ @if (form.controls['title'].errors?.maxlength) {
+ {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}
+ }
+ @else if (form.controls['title'].errors?.message) {
+ {{ form.controls['title'].errors?.message | translate }}
+ }
+ @else if (form.controls['title'].errors?.required) {
+ {{ 'LIBRARY.ERRORS.NAME_REQUIRED' | translate }}
+ }
@@ -37,13 +34,15 @@
autocomplete="off"
/>
-
- {{ form.controls['id'].errors?.message | translate }}
-
-
-
- {{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}
-
+ @if (form.controls['id'].errors?.message) {
+ {{ form.controls['id'].errors?.message | translate }}
+ }
+ @else if (form.controls['id'].errors?.maxlength) {
+ {{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}
+ }
+ @else if (form.controls['id'].errors?.required) {
+ {{ 'LIBRARY.ERRORS.ID_REQUIRED' | translate }}
+ }
@@ -53,10 +52,9 @@
rows="3"
formControlName="description"
>
-
-
- {{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
-
+ @if (form.controls['description'].errors?.maxlength) {
+ {{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
+ }
-
- {{ option.label | translate }}
-
+ @for (option of visibilityOptions; track option.value) {
+
+ {{ option.label | translate }}
+
+ }
diff --git a/lib/content-services/src/lib/dialogs/library/library.dialog.spec.ts b/lib/content-services/src/lib/dialogs/library/library.dialog.spec.ts
index 304432de6d..fa9a477d31 100644
--- a/lib/content-services/src/lib/dialogs/library/library.dialog.spec.ts
+++ b/lib/content-services/src/lib/dialogs/library/library.dialog.spec.ts
@@ -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;
let component: LibraryDialogComponent;
let sitesService: SitesService;
- let findSitesSpy;
+ let findSitesSpy: jasmine.Spy<(term: string, opts?: FindQuery) => Promise>;
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');
+ });
});
diff --git a/lib/content-services/src/lib/dialogs/library/library.dialog.ts b/lib/content-services/src/lib/dialogs/library/library.dialog.ts
index 269dd7a984..c7de2d700d 100644
--- a/lib/content-services/src/lib/dialogs/library/library.dialog.ts
+++ b/lib/content-services/src/lib/dialogs/library/library.dialog.ts
@@ -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();
+ success = new EventEmitter();
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 {
- return this.queriesApi.findSites(libraryTitle, {
- maxItems: 1,
- fields: ['title']
- });
+ private async findLibraryByTitle(libraryTitle: string): Promise {
+ 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 {
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)
+ });
+ }
}
diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json
index bfd41e488f..1efca447cb 100644
--- a/lib/content-services/src/lib/i18n/en.json
+++ b/lib/content-services/src/lib/i18n/en.json
@@ -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"