[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
This commit is contained in:
AleksanderSklorz
2023-10-16 23:29:19 +02:00
committed by GitHub
parent 928c6b5731
commit 686e6c9dbb
4 changed files with 92 additions and 7 deletions

View File

@@ -488,7 +488,7 @@
"CONFLICT": "This Library ID is already used. Check the trashcan.", "CONFLICT": "This Library ID is already used. Check the trashcan.",
"ID_TOO_LONG": "Use 72 characters or less for the URL name", "ID_TOO_LONG": "Use 72 characters or less for the URL name",
"DESCRIPTION_TOO_LONG": "Use 512 characters or less for description", "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", "ILLEGAL_CHARACTERS": "Use numbers and letters only",
"ONLY_SPACES": "Library name can't contain only spaces", "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"

View File

@@ -92,7 +92,7 @@
/> />
<mat-hint *ngIf="libraryTitleExists">{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint> <mat-hint *ngIf="libraryTitleExists">{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint>
<mat-error> <mat-error>
{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }} {{ titleErrorTranslationKey | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>

View File

@@ -167,6 +167,21 @@ describe('LibraryMetadataFormComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new UpdateLibraryAction(siteEntryModel)); 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', () => { it('should call markAsPristine on form when updating valid form and has permission to update', () => {
component.node.entry.role = Site.RoleEnum.SiteManager; component.node.entry.role = Site.RoleEnum.SiteManager;
spyOn(component.form, 'markAsPristine'); spyOn(component.form, 'markAsPristine');
@@ -264,6 +279,23 @@ describe('LibraryMetadataFormComponent', () => {
expect(component.libraryTitleExists).toBe(true); 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(() => { it('should not warn if library name input is the same with library node data', fakeAsync(() => {
spyOn(component['queriesApi'], 'findSites').and.returnValue( spyOn(component['queriesApi'], 'findSites').and.returnValue(
Promise.resolve({ Promise.resolve({
@@ -293,4 +325,25 @@ describe('LibraryMetadataFormComponent', () => {
tick(500); tick(500);
expect(component.libraryTitleExists).toBe(false); 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');
});
}); });

View File

@@ -23,7 +23,17 @@
*/ */
import { Component, Input, OnChanges, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; 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 { QueriesApi, SiteEntry, SitePaging } from '@alfresco/js-api';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { import {
@@ -77,11 +87,17 @@ export class InstantErrorStateMatcher implements ErrorStateMatcher {
}) })
export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestroy { export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestroy {
private _queriesApi: QueriesApi; private _queriesApi: QueriesApi;
private _titleErrorTranslationKey: string;
get queriesApi(): QueriesApi { get queriesApi(): QueriesApi {
this._queriesApi = this._queriesApi ?? new QueriesApi(this.alfrescoApiService.getInstance()); this._queriesApi = this._queriesApi ?? new QueriesApi(this.alfrescoApiService.getInstance());
return this._queriesApi; return this._queriesApi;
} }
get titleErrorTranslationKey(): string {
return this._titleErrorTranslationKey;
}
@Input() @Input()
node: SiteEntry; node: SiteEntry;
@@ -96,7 +112,7 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
form: UntypedFormGroup = new UntypedFormGroup({ form: UntypedFormGroup = new UntypedFormGroup({
id: new UntypedFormControl({ value: '', disabled: true }), 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)]), description: new UntypedFormControl({ value: '' }, [Validators.maxLength(512)]),
visibility: new UntypedFormControl(this.libraryType[0].value) visibility: new UntypedFormControl(this.libraryType[0].value)
}); });
@@ -124,7 +140,14 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
ngOnInit() { ngOnInit() {
this.updateForm(this.node); 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 this.form.controls['title'].valueChanges
.pipe( .pipe(
debounceTime(300), debounceTime(300),
@@ -164,7 +187,12 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
update() { update() {
if (this.canUpdateLibrary && this.form.valid) { if (this.canUpdateLibrary && this.form.valid) {
this.form.markAsPristine(); 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<SitePaging | { list: { entries: any[] } }> { private findLibraryByTitle(libraryTitle: string): Observable<SitePaging | { list: { entries: any[] } }> {
return from( return from(
this.queriesApi this.queriesApi
.findSites(libraryTitle, { .findSites(libraryTitle.trim(), {
maxItems: 1, maxItems: 1,
fields: ['title'] fields: ['title']
}) })
@@ -199,4 +227,8 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
) )
.subscribe(handle); .subscribe(handle);
} }
private validateEmptyName(control: FormControl<string>): ValidationErrors {
return control.value.length && !control.value.trim() ? { empty: true } : null;
}
} }