From 4c1e4623646da02ec3a62746da2664f44ee99032 Mon Sep 17 00:00:00 2001 From: Dharan <14145706+dhrn@users.noreply.github.com> Date: Mon, 24 May 2021 13:52:25 +0530 Subject: [PATCH] [ACA-4454] The create library button should get disabled after being clicked once (#7046) --- .../lib/dialogs/library/library.dialog.html | 2 +- .../dialogs/library/library.dialog.spec.ts | 79 +++++++++++-------- .../src/lib/dialogs/library/library.dialog.ts | 19 ++--- lib/core/services/sites.service.ts | 17 +++- 4 files changed, 73 insertions(+), 44 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 3d304b8d32..4a30f63e82 100644 --- a/lib/content-services/src/lib/dialogs/library/library.dialog.html +++ b/lib/content-services/src/lib/dialogs/library/library.dialog.html @@ -87,7 +87,7 @@ color="primary" mat-button (click)="submit()" - [disabled]="!form.valid" + [disabled]="!form.valid || disableCreateButton" data-automation-id="create-library-id" > {{ 'LIBRARY.DIALOG.CREATE' | 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 00a537fea3..fd7982d441 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 @@ -16,16 +16,19 @@ */ import { LibraryDialogComponent } from './library.dialog'; -import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick, flush, ComponentFixture, flushMicrotasks } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; -import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core'; +import { AlfrescoApiService, setupTestBed, SitesService } from '@alfresco/adf-core'; import { ContentTestingModule } from '../../testing/content.testing.module'; import { TranslateModule } from '@ngx-translate/core'; +import { of, throwError } from 'rxjs'; +import { delay } from 'rxjs/operators'; describe('LibraryDialogComponent', () => { - let fixture; - let component; + let fixture: ComponentFixture; + let component: LibraryDialogComponent; + let sitesService: SitesService; let alfrescoApi; let findSitesSpy; const findSitesResponse = { list: { entries: [] } }; @@ -48,6 +51,7 @@ describe('LibraryDialogComponent', () => { fixture = TestBed.createComponent(LibraryDialogComponent); component = fixture.componentInstance; alfrescoApi = TestBed.inject(AlfrescoApiService); + sitesService = TestBed.inject(SitesService); findSitesSpy = spyOn(alfrescoApi.getInstance().core.queriesApi, 'findSites'); }); @@ -57,8 +61,8 @@ describe('LibraryDialogComponent', () => { it('should set library id automatically on title input', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -72,8 +76,8 @@ describe('LibraryDialogComponent', () => { it('should translate library title space character to dash for library id', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -87,8 +91,8 @@ describe('LibraryDialogComponent', () => { it('should not change custom library id on title input', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -107,7 +111,7 @@ describe('LibraryDialogComponent', () => { })); it('should invalidate form when library id already exists', fakeAsync(() => { - spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve()); + spyOn(sitesService, 'getSite').and.returnValue(of(null)); fixture.detectChanges(); component.form.controls.id.setValue('existingLibrary'); @@ -123,11 +127,11 @@ describe('LibraryDialogComponent', () => { it('should create site when form is valid', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue( - Promise.resolve() + spyOn(sitesService, 'createSite').and.returnValue( + of({entry: {id: 'fake-id'}}).pipe(delay(100)) ); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -138,9 +142,18 @@ describe('LibraryDialogComponent', () => { component.submit(); fixture.detectChanges(); - flush(); + const confirmButton = fixture.nativeElement.querySelector(`[data-automation-id="create-library-id"]`); + expect(component.disableCreateButton).toBe(true); + expect(confirmButton.disabled).toBe(true); - expect(alfrescoApi.sitesApi.createSite).toHaveBeenCalledWith({ + tick(500); + flushMicrotasks(); + fixture.detectChanges(); + + expect(confirmButton.disabled).toBe(false); + expect(component.disableCreateButton).toBe(false); + + expect(sitesService.createSite).toHaveBeenCalledWith({ id: 'library-title', title: 'library title', description: '', @@ -150,10 +163,10 @@ describe('LibraryDialogComponent', () => { it('should not create site when form is invalid', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue( + spyOn(sitesService, 'createSite').and.returnValue( Promise.resolve({}) ); - spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve()); + spyOn(sitesService, 'getSite').and.returnValue(of(null)); fixture.detectChanges(); component.form.controls.title.setValue('existingLibrary'); @@ -165,11 +178,11 @@ describe('LibraryDialogComponent', () => { fixture.detectChanges(); flush(); - expect(alfrescoApi.sitesApi.createSite).not.toHaveBeenCalled(); + expect(sitesService.createSite).not.toHaveBeenCalled(); })); it('should notify when library title is already used', fakeAsync(() => { - spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve()); + spyOn(sitesService, 'getSite').and.returnValue(of(null)); findSitesSpy.and.returnValue(Promise.resolve( { list: { entries: [{ entry: { title: 'TEST', id: 'library-id' } }] } } )); @@ -186,11 +199,11 @@ describe('LibraryDialogComponent', () => { 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(alfrescoApi.sitesApi, 'createSite').and.callFake(() => { - return Promise.reject(error); + spyOn(sitesService, 'createSite').and.callFake(() => { + return throwError(error); }); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -210,8 +223,8 @@ describe('LibraryDialogComponent', () => { it('should not translate library title if value is not a valid id', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -225,8 +238,8 @@ describe('LibraryDialogComponent', () => { it('should translate library title partially for library id', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -240,8 +253,8 @@ describe('LibraryDialogComponent', () => { it('should translate library title multiple space character to one dash for library id', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); @@ -255,8 +268,8 @@ describe('LibraryDialogComponent', () => { it('should invalidate library title if is too short', fakeAsync(() => { findSitesSpy.and.returnValue(Promise.resolve(findSitesResponse)); - spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => { - return Promise.reject(); + spyOn(sitesService, 'getSite').and.callFake(() => { + return throwError('error'); }); fixture.detectChanges(); 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 0db3f777eb..5f518d2aed 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, Subject, from } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { Component, OnInit, @@ -33,8 +33,8 @@ import { } from '@angular/forms'; import { MatDialogRef } from '@angular/material/dialog'; import { SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api'; -import { AlfrescoApiService } from '@alfresco/adf-core'; -import { debounceTime, mergeMap, takeUntil } from 'rxjs/operators'; +import { AlfrescoApiService, SitesService } from '@alfresco/adf-core'; +import { debounceTime, finalize, mergeMap, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-library-dialog', @@ -70,9 +70,11 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { disabled: false } ]; + disableCreateButton = false; constructor( private alfrescoApiService: AlfrescoApiService, + private sitesService: SitesService, private formBuilder: FormBuilder, private dialog: MatDialogRef ) {} @@ -152,7 +154,8 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { return; } - this.create().subscribe( + this.disableCreateButton = true; + this.create().pipe(finalize(() => this.disableCreateButton = false)).subscribe( (node: SiteEntry) => { this.success.emit(node); dialog.close(node); @@ -174,7 +177,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { visibility }; - return from(this.alfrescoApiService.sitesApi.createSite(siteBody)); + return this.sitesService.createSite(siteBody); } private sanitize(input: string) { @@ -219,7 +222,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { } } - private async findLibraryByTitle(libraryTitle: string): Promise { + private findLibraryByTitle(libraryTitle: string): Promise { return this.alfrescoApiService .getInstance() .core.queriesApi.findSites(libraryTitle, { @@ -267,9 +270,7 @@ export class LibraryDialogComponent implements OnInit, OnDestroy { return new Promise((resolve) => { timer = setTimeout(() => { - return from( - this.alfrescoApiService.sitesApi.getSite(control.value) - ).subscribe( + return this.sitesService.getSite(control.value).subscribe( () => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }), () => resolve(null) ); diff --git a/lib/core/services/sites.service.ts b/lib/core/services/sites.service.ts index 97060c2f31..6faad7c5b0 100644 --- a/lib/core/services/sites.service.ts +++ b/lib/core/services/sites.service.ts @@ -20,7 +20,10 @@ import { from, Observable, throwError } from 'rxjs'; import { AlfrescoApiService } from './alfresco-api.service'; import { MinimalNode, - SiteEntry, SiteGroupEntry, SiteGroupPaging, + SiteBodyCreate, + SiteEntry, + SiteGroupEntry, + SiteGroupPaging, SiteMemberEntry, SiteMemberPaging, SiteMembershipBodyCreate, @@ -42,6 +45,18 @@ export class SitesService { this.sitesApi = new SitesApi(apiService.getInstance()); } + /** + * Create a site + * @param siteBody SiteBodyCreate to create site + * @returns site SiteEntry + */ + createSite(siteBody: SiteBodyCreate): Observable { + return from(this.sitesApi.createSite(siteBody)) + .pipe( + catchError((err: any) => this.handleError(err)) + ); + } + /** * Gets a list of all sites in the repository. * @param opts Options supported by JS-API