mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[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:
@@ -11,21 +11,18 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
adf-auto-focus
|
adf-auto-focus
|
||||||
/>
|
/>
|
||||||
|
@if (libraryTitleExists) {
|
||||||
<mat-hint *ngIf="libraryTitleExists">{{
|
<mat-hint>{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint>
|
||||||
'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate
|
}
|
||||||
}}</mat-hint>
|
@if (form.controls['title'].errors?.maxlength) {
|
||||||
<mat-error *ngIf="form.controls['title'].hasError('maxlength')">
|
<mat-error>{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}</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>
|
||||||
<mat-error *ngIf="form.controls['title'].hasError('minlength')">
|
}
|
||||||
{{ 'LIBRARY.ERRORS.TITLE_TOO_SHORT' | translate }}
|
@else if (form.controls['title'].errors?.required) {
|
||||||
</mat-error>
|
<mat-error>{{ 'LIBRARY.ERRORS.NAME_REQUIRED' | translate }}</mat-error>
|
||||||
|
}
|
||||||
<mat-error *ngIf="form.controls['title'].errors?.message">
|
|
||||||
{{ form.controls['title'].errors?.message | translate }}
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="adf-library-dialog-form-field">
|
<mat-form-field class="adf-library-dialog-form-field">
|
||||||
@@ -37,13 +34,15 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<mat-error *ngIf="form.controls['id'].errors?.message">
|
@if (form.controls['id'].errors?.message) {
|
||||||
{{ form.controls['id'].errors?.message | translate }}
|
<mat-error>{{ form.controls['id'].errors?.message | translate }}</mat-error>
|
||||||
</mat-error>
|
}
|
||||||
|
@else if (form.controls['id'].errors?.maxlength) {
|
||||||
<mat-error *ngIf="form.controls['id'].hasError('maxlength')">
|
<mat-error>{{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}</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>
|
||||||
|
|
||||||
<mat-form-field class="adf-library-dialog-form-field adf-library-dialog-form-field-description">
|
<mat-form-field class="adf-library-dialog-form-field adf-library-dialog-form-field-description">
|
||||||
@@ -53,10 +52,9 @@
|
|||||||
rows="3"
|
rows="3"
|
||||||
formControlName="description"
|
formControlName="description"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
@if (form.controls['description'].errors?.maxlength) {
|
||||||
<mat-error *ngIf="form.controls['description'].hasError('maxlength')">
|
<mat-error>{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}</mat-error>
|
||||||
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
|
}
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-radio-group
|
<mat-radio-group
|
||||||
@@ -65,17 +63,18 @@
|
|||||||
[(ngModel)]="visibilityOption"
|
[(ngModel)]="visibilityOption"
|
||||||
(change)="visibilityChangeHandler($event)"
|
(change)="visibilityChangeHandler($event)"
|
||||||
>
|
>
|
||||||
<mat-radio-button
|
@for (option of visibilityOptions; track option.value) {
|
||||||
color="primary"
|
<mat-radio-button
|
||||||
class="adf-library-dialog-radio-group-button"
|
color="primary"
|
||||||
[disabled]="option.disabled"
|
class="adf-library-dialog-radio-group-button"
|
||||||
*ngFor="let option of visibilityOptions"
|
[disabled]="option.disabled"
|
||||||
[attr.data-automation-id]="option.value"
|
[attr.data-automation-id]="option.value"
|
||||||
[value]="option.value"
|
[value]="option.value"
|
||||||
[checked]="visibilityOption.value === option.value"
|
[checked]="visibilityOption === option.value"
|
||||||
>
|
>
|
||||||
{{ option.label | translate }}
|
{{ option.label | translate }}
|
||||||
</mat-radio-button>
|
</mat-radio-button>
|
||||||
|
}
|
||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
</form>
|
</form>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|||||||
@@ -22,21 +22,40 @@ import { MatDialogRef } from '@angular/material/dialog';
|
|||||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||||
import { of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { delay } from 'rxjs/operators';
|
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 { 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', () => {
|
describe('LibraryDialogComponent', () => {
|
||||||
let fixture: ComponentFixture<LibraryDialogComponent>;
|
let fixture: ComponentFixture<LibraryDialogComponent>;
|
||||||
let component: LibraryDialogComponent;
|
let component: LibraryDialogComponent;
|
||||||
let sitesService: SitesService;
|
let sitesService: SitesService;
|
||||||
let findSitesSpy;
|
let findSitesSpy: jasmine.Spy<(term: string, opts?: FindQuery) => Promise<SitePaging>>;
|
||||||
let notificationService: NotificationService;
|
let notificationService: NotificationService;
|
||||||
const findSitesResponse = { list: { entries: [] } };
|
let unitTestingUtils: UnitTestingUtils;
|
||||||
|
let loader: HarnessLoader;
|
||||||
|
|
||||||
|
const findSitesResponse = new SitePaging({ list: { entries: [], pagination: {} } });
|
||||||
const dialogRef = {
|
const dialogRef = {
|
||||||
close: jasmine.createSpy('close')
|
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(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule, LibraryDialogComponent],
|
imports: [ContentTestingModule, LibraryDialogComponent],
|
||||||
@@ -48,6 +67,8 @@ describe('LibraryDialogComponent', () => {
|
|||||||
sitesService = TestBed.inject(SitesService);
|
sitesService = TestBed.inject(SitesService);
|
||||||
findSitesSpy = spyOn(component['queriesApi'], 'findSites');
|
findSitesSpy = spyOn(component['queriesApi'], 'findSites');
|
||||||
notificationService = TestBed.inject(NotificationService);
|
notificationService = TestBed.inject(NotificationService);
|
||||||
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -56,45 +77,33 @@ describe('LibraryDialogComponent', () => {
|
|||||||
|
|
||||||
it('should set library id automatically on title input', fakeAsync(() => {
|
it('should set library id automatically on title input', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('libraryTitle');
|
setFormFieldValue('title', 'libraryTitle');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe('libraryTitle');
|
expect(component.form.controls.id.value).toBe('libraryTitle');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should translate library title space character to dash for library id', fakeAsync(() => {
|
it('should translate library title space character to dash for library id', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('library title');
|
setFormFieldValue('title', 'library title');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe('library-title');
|
expect(component.form.controls.id.value).toBe('library-title');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not change custom library id on title input', fakeAsync(() => {
|
it('should not change custom library id on title input', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.id.setValue('custom-id');
|
setFormFieldValue('id', 'custom-id');
|
||||||
component.form.controls.id.markAsDirty();
|
component.form.controls.id.markAsDirty();
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
component.form.controls.title.setValue('library title');
|
setFormFieldValue('title', 'library title');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe('custom-id');
|
expect(component.form.controls.id.value).toBe('custom-id');
|
||||||
}));
|
}));
|
||||||
@@ -103,10 +112,7 @@ describe('LibraryDialogComponent', () => {
|
|||||||
spyOn(sitesService, 'getSite').and.returnValue(of(null));
|
spyOn(sitesService, 'getSite').and.returnValue(of(null));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.id.setValue('existingLibrary');
|
setFormFieldValue('id', 'existingLibrary');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.errors).toEqual({
|
expect(component.form.controls.id.errors).toEqual({
|
||||||
message: 'LIBRARY.ERRORS.EXISTENT_SITE'
|
message: 'LIBRARY.ERRORS.EXISTENT_SITE'
|
||||||
@@ -117,13 +123,10 @@ describe('LibraryDialogComponent', () => {
|
|||||||
it('should create site when form is valid', fakeAsync(() => {
|
it('should create site when form is valid', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'createSite').and.returnValue(of({ entry: { id: 'fake-id' } } as SiteEntry).pipe(delay(100)));
|
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();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('library title');
|
setFormFieldValue('title', 'library title');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
component.submit();
|
component.submit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -152,10 +155,7 @@ describe('LibraryDialogComponent', () => {
|
|||||||
spyOn(sitesService, 'getSite').and.returnValue(of(null));
|
spyOn(sitesService, 'getSite').and.returnValue(of(null));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('existingLibrary');
|
setFormFieldValue('title', 'existingLibrary');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
component.submit();
|
component.submit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -166,108 +166,231 @@ describe('LibraryDialogComponent', () => {
|
|||||||
|
|
||||||
it('should notify when library title is already used', fakeAsync(() => {
|
it('should notify when library title is already used', fakeAsync(() => {
|
||||||
spyOn(sitesService, 'getSite').and.returnValue(of(null));
|
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();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('test');
|
setFormFieldValue('title', 'test');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.libraryTitleExists).toBe(true);
|
expect(component.libraryTitleExists).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => {
|
describe('create site error', () => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
/**
|
||||||
const error = { message: '{ "error": { "statusCode": 409 } }' };
|
* Performs setup for create site errors related tests.
|
||||||
spyOn(sitesService, 'createSite').and.callFake(() => throwError(error));
|
*
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
* @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();
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
component.form.controls.title.setValue('test');
|
spyOn(sitesService, 'createSite').and.callFake(() => throwError(() => error));
|
||||||
tick(500);
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
component.submit();
|
fixture.detectChanges();
|
||||||
fixture.detectChanges();
|
setFormFieldValue('title', 'test');
|
||||||
flush();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.errors).toEqual({
|
component.submit();
|
||||||
message: 'LIBRARY.ERRORS.CONFLICT'
|
fixture.detectChanges();
|
||||||
});
|
flush();
|
||||||
}));
|
}
|
||||||
|
|
||||||
it('should handle default errors and show generic error in snackbar', fakeAsync(() => {
|
it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
throwCreateError(409);
|
||||||
const error = {};
|
expect(component.form.controls.id.errors).toEqual({
|
||||||
spyOn(sitesService, 'createSite').and.callFake(() => throwError(error));
|
message: 'LIBRARY.ERRORS.CONFLICT'
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
});
|
||||||
spyOn(notificationService, 'showError').and.callThrough();
|
}));
|
||||||
|
|
||||||
fixture.detectChanges();
|
it('should show generic error notification on error code other than 409', fakeAsync(() => {
|
||||||
component.form.controls.title.setValue('test');
|
spyOn(notificationService, 'showError');
|
||||||
tick(500);
|
throwCreateError(404);
|
||||||
flush();
|
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
|
||||||
fixture.detectChanges();
|
}));
|
||||||
|
|
||||||
component.submit();
|
it('should handle default errors and show generic error in snackbar', fakeAsync(() => {
|
||||||
fixture.detectChanges();
|
spyOn(notificationService, 'showError');
|
||||||
flush();
|
throwCreateError();
|
||||||
|
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
|
||||||
expect(notificationService.showError).toHaveBeenCalledWith('CORE.MESSAGES.ERRORS.GENERIC');
|
}));
|
||||||
}));
|
});
|
||||||
|
|
||||||
it('should not translate library title if value is not a valid id', fakeAsync(() => {
|
it('should not translate library title if value is not a valid id', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('@@@####');
|
setFormFieldValue('title', '@@@####');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe(null);
|
expect(component.form.controls.id.value).toBe(null);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should translate library title partially for library id', fakeAsync(() => {
|
it('should translate library title partially for library id', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('@@@####library');
|
setFormFieldValue('title', '@@@####library');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe('library');
|
expect(component.form.controls.id.value).toBe('library');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should translate library title multiple space character to one dash for library id', fakeAsync(() => {
|
it('should translate library title multiple space character to one dash for library id', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('library title');
|
setFormFieldValue('title', 'library title');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.form.controls.id.value).toBe('library-title');
|
expect(component.form.controls.id.value).toBe('library-title');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should invalidate library title if is too short', fakeAsync(() => {
|
it('should invalidate library title if is too short', fakeAsync(() => {
|
||||||
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse));
|
||||||
spyOn(sitesService, 'getSite').and.callFake(() => throwError('error'));
|
spyOn(sitesService, 'getSite').and.callFake(() => throwError(() => 'error'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.form.controls.title.setValue('l');
|
setFormFieldValue('title', 'l');
|
||||||
tick(500);
|
|
||||||
flush();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
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);
|
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.
|
* 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 { Component, DestroyRef, EventEmitter, inject, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
AbstractControl,
|
AbstractControl,
|
||||||
@@ -24,23 +24,30 @@ import {
|
|||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
UntypedFormControl,
|
UntypedFormControl,
|
||||||
UntypedFormGroup,
|
UntypedFormGroup,
|
||||||
|
ValidationErrors,
|
||||||
Validators
|
Validators
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { QueriesApi, SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api';
|
import { QueriesApi, SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api';
|
||||||
import { NotificationService } from '@alfresco/adf-core';
|
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 { SitesService } from '../../common/services/sites.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslatePipe } from '@ngx-translate/core';
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { AutoFocusDirective } from '../../directives';
|
import { AutoFocusDirective } from '../../directives';
|
||||||
import { MatRadioModule } from '@angular/material/radio';
|
import { MatRadioChange, MatRadioModule } from '@angular/material/radio';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { AlfrescoApiService } from '../../services';
|
import { AlfrescoApiService } from '../../services';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
|
interface VisibilityOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-library-dialog',
|
selector: 'adf-library-dialog',
|
||||||
imports: [
|
imports: [
|
||||||
@@ -71,13 +78,13 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
* newly-created library.
|
* newly-created library.
|
||||||
*/
|
*/
|
||||||
@Output()
|
@Output()
|
||||||
success = new EventEmitter<any>();
|
success = new EventEmitter<SiteEntry>();
|
||||||
|
|
||||||
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
|
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
|
||||||
libraryTitleExists = false;
|
libraryTitleExists = false;
|
||||||
form: UntypedFormGroup;
|
form: UntypedFormGroup;
|
||||||
visibilityOption: any;
|
visibilityOption: string;
|
||||||
visibilityOptions = [
|
visibilityOptions: VisibilityOption[] = [
|
||||||
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
|
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
|
||||||
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
|
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
|
||||||
{
|
{
|
||||||
@@ -107,7 +114,7 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const validators = {
|
const validators = {
|
||||||
id: [Validators.required, Validators.maxLength(72), this.forbidSpecialCharacters],
|
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)]
|
description: [Validators.maxLength(512)]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,13 +126,14 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
|
|
||||||
this.visibilityOption = this.visibilityOptions[0].value;
|
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
|
this.form.controls['title'].valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
debounceTime(500),
|
debounceTime(500),
|
||||||
mergeMap(
|
mergeMap((title) => from(this.checkLibraryNameExists(title)).pipe(map(() => title))),
|
||||||
(title) => this.checkLibraryNameExists(title),
|
|
||||||
(title) => title
|
|
||||||
),
|
|
||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
)
|
)
|
||||||
.subscribe((title: string) => {
|
.subscribe((title: string) => {
|
||||||
@@ -168,16 +176,16 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
this.disableCreateButton = true;
|
this.disableCreateButton = true;
|
||||||
this.create()
|
this.create()
|
||||||
.pipe(finalize(() => (this.disableCreateButton = false)))
|
.pipe(finalize(() => (this.disableCreateButton = false)))
|
||||||
.subscribe(
|
.subscribe({
|
||||||
(node: SiteEntry) => {
|
next: (node: SiteEntry) => {
|
||||||
this.success.emit(node);
|
this.success.emit(node);
|
||||||
dialog.close(node);
|
dialog.close(node);
|
||||||
},
|
},
|
||||||
(error) => this.handleError(error)
|
error: (error) => this.handleError(error)
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
visibilityChangeHandler(event) {
|
visibilityChangeHandler(event: MatRadioChange) {
|
||||||
this.visibilityOption = event.value;
|
this.visibilityOption = event.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +232,7 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async checkLibraryNameExists(libraryTitle: string) {
|
private async checkLibraryNameExists(libraryTitle: string) {
|
||||||
let entries = [];
|
const entries = (await this.findLibraryByTitle(libraryTitle)).list.entries;
|
||||||
|
|
||||||
try {
|
|
||||||
entries = (await this.findLibraryByTitle(libraryTitle)).list.entries;
|
|
||||||
} catch {
|
|
||||||
entries = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
this.libraryTitleExists = entries[0].entry.title.toLowerCase() === libraryTitle.toLowerCase();
|
this.libraryTitleExists = entries[0].entry.title.toLowerCase() === libraryTitle.toLowerCase();
|
||||||
@@ -239,20 +241,24 @@ export class LibraryDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private findLibraryByTitle(libraryTitle: string): Promise<SitePaging> {
|
private async findLibraryByTitle(libraryTitle: string): Promise<SitePaging> {
|
||||||
return this.queriesApi.findSites(libraryTitle, {
|
try {
|
||||||
maxItems: 1,
|
return await this.queriesApi.findSites(libraryTitle, {
|
||||||
fields: ['title']
|
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) {
|
if (value === null || value.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
|
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
|
||||||
const isValid: boolean = !validCharacters.test(value);
|
const isValid = !validCharacters.test(value);
|
||||||
|
|
||||||
return isValid
|
return isValid
|
||||||
? null
|
? 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) {
|
if (value === null || value.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid: boolean = !!(value || '').trim();
|
const isValid = !!value.trim();
|
||||||
|
|
||||||
return isValid
|
return isValid
|
||||||
? null
|
? 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;
|
let timer;
|
||||||
|
|
||||||
return (control: AbstractControl) => {
|
return (control: AbstractControl) => {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
timer = setTimeout(
|
timer = setTimeout(() => {
|
||||||
() =>
|
this.checkSite(control.value, resolve);
|
||||||
this.sitesService.getSite(control.value).subscribe(
|
}, 300);
|
||||||
() => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
|
|
||||||
() => resolve(null)
|
|
||||||
),
|
|
||||||
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)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,7 +659,9 @@
|
|||||||
"TITLE_TOO_SHORT": "Title must be at least 2 characters long",
|
"TITLE_TOO_SHORT": "Title must be at least 2 characters long",
|
||||||
"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",
|
||||||
|
"NAME_REQUIRED": "Library name is required",
|
||||||
|
"ID_REQUIRED": "Library ID is required"
|
||||||
},
|
},
|
||||||
"SUCCESS": {
|
"SUCCESS": {
|
||||||
"LIBRARY_UPDATED": "Library properties updated"
|
"LIBRARY_UPDATED": "Library properties updated"
|
||||||
|
|||||||
Reference in New Issue
Block a user