mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-6842] [ACA] Clicking "Create" button multiple times in folder creation modal sends the same folder creation request multiple times (#9554)
* ACS-6842 add disable mode to submit button * ACS-6842 refactor ts and spec files * ACS-6842 add setFormValues function --------- Co-authored-by: DaryaBalvanovich <darya.balvanovich1@hyland.com>
This commit is contained in:
committed by
GitHub
parent
05884e829b
commit
86d3ca7892
@@ -63,7 +63,7 @@
|
||||
id="adf-folder-create-button"
|
||||
mat-button
|
||||
(click)="submit()"
|
||||
[disabled]="!form.valid">
|
||||
[disabled]="!form.valid || disableSubmitButton">
|
||||
{{
|
||||
(editing
|
||||
? 'CORE.FOLDER_DIALOG.UPDATE_BUTTON.LABEL'
|
||||
|
@@ -20,7 +20,7 @@ import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { NodesApiService } from '../common/services/nodes-api.service';
|
||||
|
||||
import { FolderDialogComponent } from './folder.dialog';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { BehaviorSubject, throwError } from 'rxjs';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
@@ -28,9 +28,16 @@ describe('FolderDialogComponent', () => {
|
||||
let fixture: ComponentFixture<FolderDialogComponent>;
|
||||
let component: FolderDialogComponent;
|
||||
let nodesApi: NodesApiService;
|
||||
|
||||
let submitButton: HTMLButtonElement;
|
||||
const dialogRef = {
|
||||
close: jasmine.createSpy('close')
|
||||
};
|
||||
let updateNodeSpy: jasmine.Spy;
|
||||
let createFolderSpy: jasmine.Spy;
|
||||
|
||||
const updateNode$ = new BehaviorSubject(null);
|
||||
const createFolderNode$ = new BehaviorSubject(null);
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -41,6 +48,10 @@ describe('FolderDialogComponent', () => {
|
||||
fixture = TestBed.createComponent(FolderDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
nodesApi = TestBed.inject(NodesApiService);
|
||||
|
||||
createFolderSpy = spyOn(nodesApi, 'createFolder').and.returnValue(createFolderNode$);
|
||||
updateNodeSpy = spyOn(nodesApi, 'updateNode').and.returnValue(updateNode$);
|
||||
submitButton = fixture.nativeElement.querySelector('#adf-folder-create-button');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -77,9 +88,7 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should update form input', () => {
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['title'].setValue('folder-title-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
setFormValues();
|
||||
|
||||
expect(component.name).toBe('folder-name-update');
|
||||
expect(component.title).toBe('folder-title-update');
|
||||
@@ -87,14 +96,17 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should submit updated values if form is valid', () => {
|
||||
spyOn(nodesApi, 'updateNode').and.returnValue(of(null));
|
||||
updateNode$.next(null);
|
||||
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['title'].setValue('folder-title-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
setFormValues();
|
||||
|
||||
component.submit();
|
||||
fixture.detectChanges();
|
||||
submitButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.valid).toBeTrue();
|
||||
expect(submitButton.disabled).toBeTrue();
|
||||
expect(component.disableSubmitButton).toBeTrue();
|
||||
expect(nodesApi.updateNode).toHaveBeenCalledWith('node-id', {
|
||||
name: 'folder-name-update',
|
||||
properties: {
|
||||
@@ -104,49 +116,49 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should call dialog to close with form data when submit is successfully', () => {
|
||||
const folder: any = {
|
||||
data: 'folder-data'
|
||||
};
|
||||
|
||||
spyOn(nodesApi, 'updateNode').and.returnValue(of(folder));
|
||||
|
||||
component.submit();
|
||||
|
||||
expect(dialogRef.close).toHaveBeenCalledWith(folder);
|
||||
});
|
||||
|
||||
it('should emit success output event with folder when submit is successful', async () => {
|
||||
const folder: any = { data: 'folder-data' };
|
||||
let expectedNode = null;
|
||||
|
||||
spyOn(nodesApi, 'updateNode').and.returnValue(of(folder));
|
||||
|
||||
component.success.subscribe((node) => {
|
||||
expectedNode = node;
|
||||
});
|
||||
component.submit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(expectedNode).toBe(folder);
|
||||
});
|
||||
|
||||
it('should not submit if form is invalid', () => {
|
||||
spyOn(nodesApi, 'updateNode');
|
||||
|
||||
component.form.controls['name'].setValue('');
|
||||
component.form.controls['description'].setValue('');
|
||||
|
||||
component.submit();
|
||||
fixture.detectChanges();
|
||||
submitButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.valid).toBe(false);
|
||||
expect(nodesApi.updateNode).not.toHaveBeenCalled();
|
||||
expect(submitButton.disabled).toBeTrue();
|
||||
expect(component.form.valid).toBeFalse();
|
||||
expect(updateNodeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when submit is successfully', () => {
|
||||
const folder: any = { data: 'folder-data' };
|
||||
|
||||
beforeAll(() => {
|
||||
updateNode$.next(folder);
|
||||
});
|
||||
|
||||
it('should call dialog to close with form data', () => {
|
||||
component.submit();
|
||||
|
||||
expect(dialogRef.close).toHaveBeenCalledWith(folder);
|
||||
});
|
||||
|
||||
it('should emit success output event with folder', async () => {
|
||||
let expectedNode = null;
|
||||
|
||||
component.success.subscribe((node) => {
|
||||
expectedNode = node;
|
||||
});
|
||||
component.submit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(expectedNode).toBe(folder);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call dialog to close if submit fails', () => {
|
||||
spyOn(nodesApi, 'updateNode').and.returnValue(throwError('error'));
|
||||
updateNode$.error(throwError('error'));
|
||||
spyOn(component, 'handleError').and.callFake((val) => val);
|
||||
|
||||
component.submit();
|
||||
@@ -177,49 +189,50 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should update form input', () => {
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
setFormValues();
|
||||
|
||||
expect(component.name).toBe('folder-name-update');
|
||||
expect(component.description).toBe('folder-description-update');
|
||||
});
|
||||
|
||||
it('should submit updated values if form is valid', () => {
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(of(null));
|
||||
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['title'].setValue('folder-title-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
|
||||
component.submit();
|
||||
|
||||
expect(nodesApi.createFolder).toHaveBeenCalledWith('parentNodeId', {
|
||||
name: 'folder-name-update',
|
||||
properties: {
|
||||
'cm:title': 'folder-title-update',
|
||||
'cm:description': 'folder-description-update'
|
||||
},
|
||||
nodeType: 'cm:folder'
|
||||
describe('when form is valid', () => {
|
||||
beforeEach(() => {
|
||||
createFolderNode$.next(null);
|
||||
setFormValues();
|
||||
});
|
||||
});
|
||||
|
||||
it('should submit updated values if form is valid (with custom nodeType)', () => {
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(of(null));
|
||||
it('should submit updated values', () => {
|
||||
component.submit();
|
||||
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['title'].setValue('folder-title-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
component.nodeType = 'cm:sushi';
|
||||
expect(component.disableSubmitButton).toBeTrue();
|
||||
expect(createFolderSpy).toHaveBeenCalledWith('parentNodeId', {
|
||||
name: 'folder-name-update',
|
||||
properties: {
|
||||
'cm:title': 'folder-title-update',
|
||||
'cm:description': 'folder-description-update'
|
||||
},
|
||||
nodeType: 'cm:folder'
|
||||
});
|
||||
});
|
||||
|
||||
component.submit();
|
||||
it('should submit updated values (with custom nodeType)', () => {
|
||||
component.nodeType = 'cm:sushi';
|
||||
|
||||
expect(nodesApi.createFolder).toHaveBeenCalledWith('parentNodeId', {
|
||||
name: 'folder-name-update',
|
||||
properties: {
|
||||
'cm:title': 'folder-title-update',
|
||||
'cm:description': 'folder-description-update'
|
||||
},
|
||||
nodeType: 'cm:sushi'
|
||||
fixture.detectChanges();
|
||||
submitButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.valid).toBeTrue();
|
||||
expect(submitButton.disabled).toBeTrue();
|
||||
expect(component.disableSubmitButton).toBeTrue();
|
||||
expect(createFolderSpy).toHaveBeenCalledWith('parentNodeId', {
|
||||
name: 'folder-name-update',
|
||||
properties: {
|
||||
'cm:title': 'folder-title-update',
|
||||
'cm:description': 'folder-description-update'
|
||||
},
|
||||
nodeType: 'cm:sushi'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -228,11 +241,9 @@ describe('FolderDialogComponent', () => {
|
||||
data: 'folder-data'
|
||||
};
|
||||
|
||||
component.form.controls['name'].setValue('name');
|
||||
component.form.controls['title'].setValue('title');
|
||||
component.form.controls['description'].setValue('description');
|
||||
setFormValues();
|
||||
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(of(folder));
|
||||
createFolderNode$.next(folder);
|
||||
|
||||
component.submit();
|
||||
|
||||
@@ -240,19 +251,16 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should not submit if form is invalid', () => {
|
||||
spyOn(nodesApi, 'createFolder');
|
||||
|
||||
component.form.controls['name'].setValue('');
|
||||
component.form.controls['description'].setValue('');
|
||||
|
||||
component.submit();
|
||||
|
||||
expect(component.form.valid).toBe(false);
|
||||
expect(nodesApi.createFolder).not.toHaveBeenCalled();
|
||||
expect(submitButton.disabled).toBeTrue();
|
||||
expect(component.form.valid).toBeFalse();
|
||||
expect(createFolderSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call dialog to close if submit fails', () => {
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(throwError('error'));
|
||||
createFolderNode$.error(throwError('error'));
|
||||
spyOn(component, 'handleError').and.callFake((val) => val);
|
||||
|
||||
component.form.controls['name'].setValue('name');
|
||||
@@ -264,19 +272,22 @@ describe('FolderDialogComponent', () => {
|
||||
expect(dialogRef.close).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Error events ', () => {
|
||||
describe('Error events', () => {
|
||||
afterEach(() => {
|
||||
createFolderNode$.next(null);
|
||||
});
|
||||
|
||||
it('should raise error for 409', (done) => {
|
||||
const error = {
|
||||
message: '{ "error": { "statusCode" : 409 } }'
|
||||
};
|
||||
createFolderNode$.error(error);
|
||||
|
||||
component.error.subscribe((message) => {
|
||||
expect(message).toBe('CORE.MESSAGES.ERRORS.EXISTENT_FOLDER');
|
||||
done();
|
||||
});
|
||||
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(throwError(error));
|
||||
|
||||
component.form.controls['name'].setValue('name');
|
||||
component.form.controls['description'].setValue('description');
|
||||
|
||||
@@ -287,14 +298,13 @@ describe('FolderDialogComponent', () => {
|
||||
const error = {
|
||||
message: '{ "error": { "statusCode" : 123 } }'
|
||||
};
|
||||
createFolderNode$.error(error);
|
||||
|
||||
component.error.subscribe((message) => {
|
||||
expect(message).toBe('CORE.MESSAGES.ERRORS.GENERIC');
|
||||
done();
|
||||
});
|
||||
|
||||
spyOn(nodesApi, 'createFolder').and.returnValue(throwError(error));
|
||||
|
||||
component.form.controls['name'].setValue('name');
|
||||
component.form.controls['description'].setValue('description');
|
||||
|
||||
@@ -302,4 +312,13 @@ describe('FolderDialogComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Set mock values to form
|
||||
*/
|
||||
function setFormValues() {
|
||||
component.form.controls['name'].setValue('folder-name-update');
|
||||
component.form.controls['title'].setValue('folder-title-update');
|
||||
component.form.controls['description'].setValue('folder-description-update');
|
||||
}
|
||||
});
|
||||
|
@@ -34,11 +34,6 @@ import { forbidEndingDot, forbidOnlySpaces, forbidSpecialCharacters } from './fo
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class FolderDialogComponent implements OnInit {
|
||||
|
||||
form: UntypedFormGroup;
|
||||
|
||||
folder: Node = null;
|
||||
|
||||
/**
|
||||
* Emitted when the edit/create folder give error for example a folder with same name already exist
|
||||
*/
|
||||
@@ -49,12 +44,40 @@ export class FolderDialogComponent implements OnInit {
|
||||
* Emitted when the edit/create folder is successfully created/modified
|
||||
*/
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<Node>();
|
||||
success: EventEmitter<Node> = new EventEmitter<Node>();
|
||||
|
||||
form: UntypedFormGroup;
|
||||
folder: Node = null;
|
||||
|
||||
editTitle = 'CORE.FOLDER_DIALOG.EDIT_FOLDER_TITLE';
|
||||
createTitle = 'CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE';
|
||||
nodeType = 'cm:folder';
|
||||
|
||||
disableSubmitButton = false;
|
||||
|
||||
get editing(): boolean {
|
||||
return !!this.data.folder;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.getTrimmedValue(this.form.value.name);
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.getTrimmedValue(this.form.value.title);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.getTrimmedValue(this.form.value.description);
|
||||
}
|
||||
|
||||
private get properties(): { [key: string]: string } {
|
||||
return {
|
||||
'cm:title': this.title,
|
||||
'cm:description': this.description
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private dialog: MatDialogRef<FolderDialogComponent>,
|
||||
@@ -71,10 +94,6 @@ export class FolderDialogComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
get editing(): boolean {
|
||||
return !!this.data.folder;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const { folder } = this.data;
|
||||
let name = '';
|
||||
@@ -90,88 +109,61 @@ export class FolderDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
const validators = {
|
||||
name: [
|
||||
Validators.required,
|
||||
forbidSpecialCharacters,
|
||||
forbidEndingDot,
|
||||
forbidOnlySpaces
|
||||
]
|
||||
name: [Validators.required, forbidSpecialCharacters, forbidEndingDot, forbidOnlySpaces]
|
||||
};
|
||||
|
||||
this.form = this.formBuilder.group({
|
||||
name: [ name, validators.name ],
|
||||
title: [ title ],
|
||||
description: [ description ]
|
||||
name: [name, validators.name],
|
||||
title: [title],
|
||||
description: [description]
|
||||
});
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
const { name } = this.form.value;
|
||||
|
||||
return (name || '').trim();
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
const { title } = this.form.value;
|
||||
|
||||
return (title || '').trim();
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const { description } = this.form.value;
|
||||
|
||||
return (description || '').trim();
|
||||
}
|
||||
|
||||
private get properties(): any {
|
||||
const { title, description } = this;
|
||||
|
||||
return {
|
||||
'cm:title': title,
|
||||
'cm:description': description
|
||||
};
|
||||
}
|
||||
|
||||
private create(): Observable<Node> {
|
||||
const { name, properties, nodeType, nodesApi, data: { parentNodeId} } = this;
|
||||
return nodesApi.createFolder(parentNodeId, { name, properties, nodeType });
|
||||
}
|
||||
|
||||
private edit(): Observable<Node> {
|
||||
const { name, properties, nodesApi, data: { folder: { id: nodeId }} } = this;
|
||||
return nodesApi.updateNode(nodeId, { name, properties });
|
||||
}
|
||||
|
||||
submit() {
|
||||
const { form, dialog, editing } = this;
|
||||
this.disableSubmitButton = true;
|
||||
|
||||
if (!form.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
(editing ? this.edit() : this.create())
|
||||
.subscribe(
|
||||
(folder: Node) => {
|
||||
this.success.emit(folder);
|
||||
dialog.close(folder);
|
||||
},
|
||||
(error) => this.handleError(error)
|
||||
);
|
||||
(this.editing ? this.edit() : this.create()).subscribe(
|
||||
(folder: Node) => {
|
||||
this.success.emit(folder);
|
||||
this.dialog.close(folder);
|
||||
},
|
||||
(error) => this.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
handleError(error: any): any {
|
||||
let errorMessage = 'CORE.MESSAGES.ERRORS.GENERIC';
|
||||
|
||||
try {
|
||||
const { error: { statusCode } } = JSON.parse(error.message);
|
||||
const {
|
||||
error: { statusCode }
|
||||
} = JSON.parse(error.message);
|
||||
|
||||
if (statusCode === 409) {
|
||||
errorMessage = 'CORE.MESSAGES.ERRORS.EXISTENT_FOLDER';
|
||||
}
|
||||
} catch (err) { /* Do nothing, keep the original message */ }
|
||||
} catch (err) {
|
||||
/* Do nothing, keep the original message */
|
||||
}
|
||||
|
||||
this.error.emit(this.translation.instant(errorMessage));
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
private create(): Observable<Node> {
|
||||
const parentNodeId = this.data.parentNodeId;
|
||||
|
||||
return this.nodesApi.createFolder(parentNodeId, { name: this.name, properties: this.properties, nodeType: this.nodeType });
|
||||
}
|
||||
|
||||
private edit(): Observable<Node> {
|
||||
const nodeId = this.data.folder.id;
|
||||
|
||||
return this.nodesApi.updateNode(nodeId, { name: this.name, properties: this.properties });
|
||||
}
|
||||
|
||||
private getTrimmedValue(value: string): string {
|
||||
return (value || '').trim();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user