From 686e6c9dbbbdd0290af405482538b06da0a7d0bf Mon Sep 17 00:00:00 2001 From: AleksanderSklorz <115619721+AleksanderSklorz@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:29:19 +0200 Subject: [PATCH] [ACS-6085] user is not prevented from renaming library to name containing only spaces (#3476) * ACS-6085 Prevent user from renaming library to name containing only spaces * ACS-6085 Trimmed value * ACS-6085 Unit tests for titleErrorTranslationKey and update function * ACS-6085 Unit tests * ACS-6085 Removed redundant code * ACS-6085 Set type for validateEmptyName function * ACS-6085 Rename error translation key for required error * ACS-6085 Empty commit --- projects/aca-content/assets/i18n/en.json | 2 +- .../library-metadata-form.component.html | 2 +- .../library-metadata-form.component.spec.ts | 53 +++++++++++++++++++ .../library-metadata-form.component.ts | 42 +++++++++++++-- 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index bab98e67e3..a3a48e7ad3 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -488,7 +488,7 @@ "CONFLICT": "This Library ID is already used. Check the trashcan.", "ID_TOO_LONG": "Use 72 characters or less for the URL name", "DESCRIPTION_TOO_LONG": "Use 512 characters or less for description", - "TITLE_TOO_LONG": "Use 256 characters or less for title", + "TITLE_TOO_LONG_OR_MISSING": "Use 256 characters or less for title", "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" diff --git a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.html b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.html index 6faceb3f67..2202212058 100644 --- a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.html +++ b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.html @@ -92,7 +92,7 @@ /> {{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }} - {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }} + {{ titleErrorTranslationKey | translate }} diff --git a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts index 988f70d4be..80881c92ed 100644 --- a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts +++ b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts @@ -167,6 +167,21 @@ describe('LibraryMetadataFormComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new UpdateLibraryAction(siteEntryModel)); }); + it('should update library node with trimmed title', () => { + component.node.entry.role = Site.RoleEnum.SiteManager; + siteEntryModel.title = ' some title '; + component.node.entry.title = siteEntryModel.title; + component.ngOnInit(); + + component.update(); + expect(store.dispatch).toHaveBeenCalledWith( + new UpdateLibraryAction({ + ...siteEntryModel, + title: siteEntryModel.title.trim() + }) + ); + }); + it('should call markAsPristine on form when updating valid form and has permission to update', () => { component.node.entry.role = Site.RoleEnum.SiteManager; spyOn(component.form, 'markAsPristine'); @@ -264,6 +279,23 @@ describe('LibraryMetadataFormComponent', () => { expect(component.libraryTitleExists).toBe(true); })); + it('should call findSites on queriesApi with trimmed title', fakeAsync(() => { + const title = ' test '; + spyOn(component.queriesApi, 'findSites').and.returnValue( + Promise.resolve({ + list: { entries: [{ entry: { title } }] } + } as SitePaging) + ); + component.ngOnInit(); + + component.form.controls.title.setValue(title); + tick(300); + expect(component.queriesApi.findSites).toHaveBeenCalledWith(title.trim(), { + maxItems: 1, + fields: ['title'] + }); + })); + it('should not warn if library name input is the same with library node data', fakeAsync(() => { spyOn(component['queriesApi'], 'findSites').and.returnValue( Promise.resolve({ @@ -293,4 +325,25 @@ describe('LibraryMetadataFormComponent', () => { tick(500); expect(component.libraryTitleExists).toBe(false); })); + + it('should set proper titleErrorTranslationKey when there is error for empty title', () => { + component.ngOnInit(); + + component.form.controls.title.setValue(' '); + expect(component.titleErrorTranslationKey).toBe('LIBRARY.ERRORS.ONLY_SPACES'); + }); + + it('should set proper titleErrorTranslationKey when there is error for too long title', () => { + component.ngOnInit(); + + component.form.controls.title.setValue('t'.repeat(257)); + expect(component.titleErrorTranslationKey).toBe('LIBRARY.ERRORS.TITLE_TOO_LONG_OR_MISSING'); + }); + + it('should set proper titleErrorTranslationKey when there is error for missing title', () => { + component.ngOnInit(); + + component.form.controls.title.setValue(''); + expect(component.titleErrorTranslationKey).toBe('LIBRARY.ERRORS.TITLE_TOO_LONG_OR_MISSING'); + }); }); diff --git a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts index 0f14836eae..d443168966 100644 --- a/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts +++ b/projects/aca-content/src/lib/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts @@ -23,7 +23,17 @@ */ import { Component, Input, OnChanges, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; -import { UntypedFormGroup, UntypedFormControl, Validators, FormGroupDirective, NgForm, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { + UntypedFormGroup, + UntypedFormControl, + Validators, + FormGroupDirective, + NgForm, + FormsModule, + ReactiveFormsModule, + FormControl, + ValidationErrors +} from '@angular/forms'; import { QueriesApi, SiteEntry, SitePaging } from '@alfresco/js-api'; import { Store } from '@ngrx/store'; import { @@ -77,11 +87,17 @@ export class InstantErrorStateMatcher implements ErrorStateMatcher { }) export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestroy { private _queriesApi: QueriesApi; + private _titleErrorTranslationKey: string; + get queriesApi(): QueriesApi { this._queriesApi = this._queriesApi ?? new QueriesApi(this.alfrescoApiService.getInstance()); return this._queriesApi; } + get titleErrorTranslationKey(): string { + return this._titleErrorTranslationKey; + } + @Input() node: SiteEntry; @@ -96,7 +112,7 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro form: UntypedFormGroup = new UntypedFormGroup({ id: new UntypedFormControl({ value: '', disabled: true }), - title: new UntypedFormControl({ value: '' }, [Validators.required, Validators.maxLength(256)]), + title: new UntypedFormControl({ value: '' }, [Validators.required, Validators.maxLength(256), this.validateEmptyName]), description: new UntypedFormControl({ value: '' }, [Validators.maxLength(512)]), visibility: new UntypedFormControl(this.libraryType[0].value) }); @@ -124,7 +140,14 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro ngOnInit() { this.updateForm(this.node); - + this.form.controls.title.statusChanges + .pipe(takeUntil(this.onDestroy$)) + .subscribe( + () => + (this._titleErrorTranslationKey = this.form.controls.title.errors?.empty + ? 'LIBRARY.ERRORS.ONLY_SPACES' + : 'LIBRARY.ERRORS.TITLE_TOO_LONG_OR_MISSING') + ); this.form.controls['title'].valueChanges .pipe( debounceTime(300), @@ -164,7 +187,12 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro update() { if (this.canUpdateLibrary && this.form.valid) { this.form.markAsPristine(); - this.store.dispatch(new UpdateLibraryAction(this.form.value)); + this.store.dispatch( + new UpdateLibraryAction({ + ...this.form.value, + title: this.form.value.title.trim() + }) + ); } } @@ -182,7 +210,7 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro private findLibraryByTitle(libraryTitle: string): Observable { return from( this.queriesApi - .findSites(libraryTitle, { + .findSites(libraryTitle.trim(), { maxItems: 1, fields: ['title'] }) @@ -199,4 +227,8 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro ) .subscribe(handle); } + + private validateEmptyName(control: FormControl): ValidationErrors { + return control.value.length && !control.value.trim() ? { empty: true } : null; + } }