diff --git a/src/app/dialogs/library/form.validators.spec.ts b/src/app/dialogs/library/form.validators.spec.ts deleted file mode 100644 index 98c63f4c89..0000000000 --- a/src/app/dialogs/library/form.validators.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2018 Alfresco Software Limited - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -import { SiteIdValidator } from './form.validators'; - -describe('SiteIdValidator', () => { - it('should be defined', () => { - expect(SiteIdValidator).toBeDefined(); - }); -}); diff --git a/src/app/dialogs/library/form.validators.ts b/src/app/dialogs/library/form.validators.ts deleted file mode 100644 index 97937f9b3e..0000000000 --- a/src/app/dialogs/library/form.validators.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * @license - * Copyright 2016 Alfresco Software, Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AbstractControl, FormControl } from '@angular/forms'; -import { ContentApiService } from '../../services/content-api.service'; - -export class SiteIdValidator { - static createValidator(contentApiService: ContentApiService) { - let timer; - - return (control: AbstractControl) => { - if (timer) { - clearTimeout(timer); - } - - return new Promise(resolve => { - timer = setTimeout(() => { - return contentApiService - .getSite(control.value) - .subscribe( - () => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }), - () => resolve(null) - ); - }, 300); - }); - }; - } -} - -export function forbidSpecialCharacters({ value }: FormControl) { - const validCharacters: RegExp = /[^A-Za-z0-9-]/; - const isValid: boolean = !validCharacters.test(value); - - return isValid - ? null - : { - message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS' - }; -} diff --git a/src/app/dialogs/library/library.dialog.html b/src/app/dialogs/library/library.dialog.html index e74c31d73d..8666516030 100644 --- a/src/app/dialogs/library/library.dialog.html +++ b/src/app/dialogs/library/library.dialog.html @@ -1,80 +1,84 @@ -

- {{ createTitle | translate }} -

+

{{ createTitle | translate }}

-
- - + + + + + {{ + 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate + }} + + {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }} + + - {{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }} - - {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }} - - + + - - + + {{ form.controls['id'].errors?.message | translate }} + - - {{ form.controls['id'].errors?.message | translate }} - + + {{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }} + + - - {{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }} - - + + - - + + {{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }} + + - - {{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }} - - - - - - {{ option.label | translate }} - - -
+ + + {{ option.label | translate }} + + +
- + - + diff --git a/src/app/dialogs/library/library.dialog.scss b/src/app/dialogs/library/library.dialog.scss index 370c3bc427..90883b59f8 100644 --- a/src/app/dialogs/library/library.dialog.scss +++ b/src/app/dialogs/library/library.dialog.scss @@ -20,4 +20,8 @@ mat-form-field { display: flex; flex-direction: row; justify-content: flex-end; + + .mat-button { + text-transform: uppercase; + } } diff --git a/src/app/dialogs/library/library.dialog.spec.ts b/src/app/dialogs/library/library.dialog.spec.ts index a5c0f3fa36..2b1a223bb3 100644 --- a/src/app/dialogs/library/library.dialog.spec.ts +++ b/src/app/dialogs/library/library.dialog.spec.ts @@ -23,10 +23,184 @@ * along with Alfresco. If not, see . */ +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CoreModule } from '@alfresco/adf-core'; import { LibraryDialogComponent } from './library.dialog'; +import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; +import { + AlfrescoApiService, + AlfrescoApiServiceMock, + setupTestBed +} from '@alfresco/adf-core'; describe('LibraryDialogComponent', () => { - it('should be defined', () => { - expect(LibraryDialogComponent).toBeDefined(); + let fixture; + let component; + let alfrescoApi; + const dialogRef = { + close: jasmine.createSpy('close') + }; + + setupTestBed({ + imports: [NoopAnimationsModule, CoreModule, ReactiveFormsModule], + declarations: [LibraryDialogComponent], + providers: [ + { + provide: AlfrescoApiService, + useClass: AlfrescoApiServiceMock + }, + { provide: MatDialogRef, useValue: dialogRef } + ], + schemas: [NO_ERRORS_SCHEMA] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LibraryDialogComponent); + component = fixture.componentInstance; + alfrescoApi = TestBed.get(AlfrescoApiService); + + spyOn( + alfrescoApi.getInstance().core.queriesApi, + 'findSites' + ).and.returnValue( + Promise.resolve({ + list: { entries: [] } + }) + ); }); + + it('should set library id automatically on title input', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { + return new Promise((resolve, reject) => reject()); + }); + + fixture.detectChanges(); + component.form.controls.title.setValue('libraryTitle'); + tick(500); + flush(); + fixture.detectChanges(); + + expect(component.form.controls.id.value).toBe('libraryTitle'); + })); + + it('should translate library title space character to dash for library id', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { + return new Promise((resolve, reject) => reject()); + }); + + fixture.detectChanges(); + component.form.controls.title.setValue('library title'); + tick(500); + flush(); + fixture.detectChanges(); + + expect(component.form.controls.id.value).toBe('library-title'); + })); + + it('should not change custom library id on title input', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { + return new Promise((resolve, reject) => reject()); + }); + + fixture.detectChanges(); + component.form.controls.id.setValue('custom-id'); + component.form.controls.id.markAsDirty(); + tick(500); + flush(); + fixture.detectChanges(); + + component.form.controls.title.setValue('library title'); + tick(500); + flush(); + fixture.detectChanges(); + + expect(component.form.controls.id.value).toBe('custom-id'); + })); + + it('should invalidate form when library id already exists', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve()); + + fixture.detectChanges(); + component.form.controls.id.setValue('existingLibrary'); + tick(500); + flush(); + fixture.detectChanges(); + + expect(component.form.controls.id.errors).toEqual({ + message: 'LIBRARY.ERRORS.EXISTENT_SITE' + }); + expect(component.form.valid).toBe(false); + })); + + it('should create site when form is valid', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue( + Promise.resolve() + ); + spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { + return new Promise((resolve, reject) => reject()); + }); + + fixture.detectChanges(); + component.form.controls.title.setValue('library title'); + tick(500); + flush(); + fixture.detectChanges(); + + component.submit(); + fixture.detectChanges(); + flush(); + + expect(alfrescoApi.sitesApi.createSite).toHaveBeenCalledWith({ + id: 'library-title', + title: 'library title', + description: '', + visibility: 'PUBLIC' + }); + })); + + it('should not create site when form is invalid', fakeAsync(() => { + spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue( + Promise.resolve({}) + ); + spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve()); + + fixture.detectChanges(); + component.form.controls.title.setValue('existingLibrary'); + tick(500); + flush(); + fixture.detectChanges(); + + component.submit(); + fixture.detectChanges(); + flush(); + + expect(alfrescoApi.sitesApi.createSite).not.toHaveBeenCalled(); + })); + + it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => { + const error = { message: '{ "error": { "statusCode": 409 } }' }; + spyOn(alfrescoApi.sitesApi, 'createSite').and.callFake(() => { + return new Promise((resolve, reject) => reject(error)); + }); + spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { + return new Promise((resolve, reject) => reject()); + }); + + fixture.detectChanges(); + component.form.controls.title.setValue('test'); + tick(500); + flush(); + fixture.detectChanges(); + + component.submit(); + fixture.detectChanges(); + flush(); + + expect(component.form.controls.id.errors).toEqual({ + message: 'LIBRARY.ERRORS.CONFLICT' + }); + })); }); diff --git a/src/app/dialogs/library/library.dialog.ts b/src/app/dialogs/library/library.dialog.ts index 0f10eaadba..103b1d0e37 100644 --- a/src/app/dialogs/library/library.dialog.ts +++ b/src/app/dialogs/library/library.dialog.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Observable, Subject } from 'rxjs'; +import { Observable, Subject, from } from 'rxjs'; import { Component, OnInit, @@ -23,11 +23,15 @@ import { EventEmitter, OnDestroy } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { + FormBuilder, + FormGroup, + Validators, + FormControl, + AbstractControl +} from '@angular/forms'; import { MatDialogRef } from '@angular/material'; import { SiteBody, SiteEntry, SitePaging } from 'alfresco-js-api'; -import { ContentApiService } from '../../services/content-api.service'; -import { SiteIdValidator, forbidSpecialCharacters } from './form.validators'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { debounceTime, mergeMap, takeUntil } from 'rxjs/operators'; @@ -62,8 +66,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { constructor( private alfrescoApiService: AlfrescoApiService, private formBuilder: FormBuilder, - private dialog: MatDialogRef, - private contentApi: ContentApiService + private dialog: MatDialogRef ) {} ngOnInit() { @@ -71,7 +74,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { id: [ Validators.required, Validators.maxLength(72), - forbidSpecialCharacters + this.forbidSpecialCharacters ], title: [Validators.required, Validators.maxLength(256)], description: [Validators.maxLength(512)] @@ -79,7 +82,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { this.form = this.formBuilder.group({ title: ['', validators.title], - id: ['', validators.id, SiteIdValidator.createValidator(this.contentApi)], + id: ['', validators.id, this.createSiteIdValidator()], description: ['', validators.description] }); @@ -151,7 +154,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { } private create(): Observable { - const { contentApi, title, id, description, visibility } = this; + const { title, id, description, visibility } = this; const siteBody = { id, title, @@ -159,7 +162,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { visibility }; - return contentApi.createSite(siteBody); + return from(this.alfrescoApiService.sitesApi.createSite(siteBody)); } private sanitize(input: string) { @@ -199,4 +202,36 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { }) .catch(() => ({ list: { entries: [] } })); } + + private forbidSpecialCharacters({ value }: FormControl) { + const validCharacters: RegExp = /[^A-Za-z0-9-]/; + const isValid: boolean = !validCharacters.test(value); + + return isValid + ? null + : { + message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS' + }; + } + + private createSiteIdValidator() { + let timer; + + return (control: AbstractControl) => { + if (timer) { + clearTimeout(timer); + } + + return new Promise(resolve => { + timer = setTimeout(() => { + return from( + this.alfrescoApiService.sitesApi.getSite(control.value) + ).subscribe( + () => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }), + () => resolve(null) + ); + }, 300); + }); + }; + } }