[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:
Darya Blavanovich
2024-04-15 19:03:11 +02:00
committed by GitHub
parent 05884e829b
commit 86d3ca7892
3 changed files with 176 additions and 165 deletions

View File

@@ -63,7 +63,7 @@
id="adf-folder-create-button" id="adf-folder-create-button"
mat-button mat-button
(click)="submit()" (click)="submit()"
[disabled]="!form.valid"> [disabled]="!form.valid || disableSubmitButton">
{{ {{
(editing (editing
? 'CORE.FOLDER_DIALOG.UPDATE_BUTTON.LABEL' ? 'CORE.FOLDER_DIALOG.UPDATE_BUTTON.LABEL'

View File

@@ -20,7 +20,7 @@ import { MatDialogRef } from '@angular/material/dialog';
import { NodesApiService } from '../common/services/nodes-api.service'; import { NodesApiService } from '../common/services/nodes-api.service';
import { FolderDialogComponent } from './folder.dialog'; import { FolderDialogComponent } from './folder.dialog';
import { of, throwError } from 'rxjs'; import { BehaviorSubject, throwError } from 'rxjs';
import { ContentTestingModule } from '../testing/content.testing.module'; import { ContentTestingModule } from '../testing/content.testing.module';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@@ -28,9 +28,16 @@ describe('FolderDialogComponent', () => {
let fixture: ComponentFixture<FolderDialogComponent>; let fixture: ComponentFixture<FolderDialogComponent>;
let component: FolderDialogComponent; let component: FolderDialogComponent;
let nodesApi: NodesApiService; let nodesApi: NodesApiService;
let submitButton: HTMLButtonElement;
const dialogRef = { const dialogRef = {
close: jasmine.createSpy('close') close: jasmine.createSpy('close')
}; };
let updateNodeSpy: jasmine.Spy;
let createFolderSpy: jasmine.Spy;
const updateNode$ = new BehaviorSubject(null);
const createFolderNode$ = new BehaviorSubject(null);
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -41,6 +48,10 @@ describe('FolderDialogComponent', () => {
fixture = TestBed.createComponent(FolderDialogComponent); fixture = TestBed.createComponent(FolderDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
nodesApi = TestBed.inject(NodesApiService); 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(() => { afterEach(() => {
@@ -77,9 +88,7 @@ describe('FolderDialogComponent', () => {
}); });
it('should update form input', () => { it('should update form input', () => {
component.form.controls['name'].setValue('folder-name-update'); setFormValues();
component.form.controls['title'].setValue('folder-title-update');
component.form.controls['description'].setValue('folder-description-update');
expect(component.name).toBe('folder-name-update'); expect(component.name).toBe('folder-name-update');
expect(component.title).toBe('folder-title-update'); expect(component.title).toBe('folder-title-update');
@@ -87,14 +96,17 @@ describe('FolderDialogComponent', () => {
}); });
it('should submit updated values if form is valid', () => { 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'); setFormValues();
component.form.controls['title'].setValue('folder-title-update');
component.form.controls['description'].setValue('folder-description-update');
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', { expect(nodesApi.updateNode).toHaveBeenCalledWith('node-id', {
name: 'folder-name-update', name: 'folder-name-update',
properties: { properties: {
@@ -104,24 +116,35 @@ describe('FolderDialogComponent', () => {
}); });
}); });
it('should call dialog to close with form data when submit is successfully', () => { it('should not submit if form is invalid', () => {
const folder: any = { component.form.controls['name'].setValue('');
data: 'folder-data' component.form.controls['description'].setValue('');
};
spyOn(nodesApi, 'updateNode').and.returnValue(of(folder)); fixture.detectChanges();
submitButton.click();
fixture.detectChanges();
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(); component.submit();
expect(dialogRef.close).toHaveBeenCalledWith(folder); expect(dialogRef.close).toHaveBeenCalledWith(folder);
}); });
it('should emit success output event with folder when submit is successful', async () => { it('should emit success output event with folder', async () => {
const folder: any = { data: 'folder-data' };
let expectedNode = null; let expectedNode = null;
spyOn(nodesApi, 'updateNode').and.returnValue(of(folder));
component.success.subscribe((node) => { component.success.subscribe((node) => {
expectedNode = node; expectedNode = node;
}); });
@@ -132,21 +155,10 @@ describe('FolderDialogComponent', () => {
expect(expectedNode).toBe(folder); 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();
expect(component.form.valid).toBe(false);
expect(nodesApi.updateNode).not.toHaveBeenCalled();
}); });
it('should not call dialog to close if submit fails', () => { 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); spyOn(component, 'handleError').and.callFake((val) => val);
component.submit(); component.submit();
@@ -177,23 +189,23 @@ describe('FolderDialogComponent', () => {
}); });
it('should update form input', () => { it('should update form input', () => {
component.form.controls['name'].setValue('folder-name-update'); setFormValues();
component.form.controls['description'].setValue('folder-description-update');
expect(component.name).toBe('folder-name-update'); expect(component.name).toBe('folder-name-update');
expect(component.description).toBe('folder-description-update'); expect(component.description).toBe('folder-description-update');
}); });
it('should submit updated values if form is valid', () => { describe('when form is valid', () => {
spyOn(nodesApi, 'createFolder').and.returnValue(of(null)); beforeEach(() => {
createFolderNode$.next(null);
component.form.controls['name'].setValue('folder-name-update'); setFormValues();
component.form.controls['title'].setValue('folder-title-update'); });
component.form.controls['description'].setValue('folder-description-update');
it('should submit updated values', () => {
component.submit(); component.submit();
expect(nodesApi.createFolder).toHaveBeenCalledWith('parentNodeId', { expect(component.disableSubmitButton).toBeTrue();
expect(createFolderSpy).toHaveBeenCalledWith('parentNodeId', {
name: 'folder-name-update', name: 'folder-name-update',
properties: { properties: {
'cm:title': 'folder-title-update', 'cm:title': 'folder-title-update',
@@ -203,17 +215,17 @@ describe('FolderDialogComponent', () => {
}); });
}); });
it('should submit updated values if form is valid (with custom nodeType)', () => { it('should submit updated values (with custom nodeType)', () => {
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.nodeType = 'cm:sushi'; component.nodeType = 'cm:sushi';
component.submit(); fixture.detectChanges();
submitButton.click();
fixture.detectChanges();
expect(nodesApi.createFolder).toHaveBeenCalledWith('parentNodeId', { expect(component.form.valid).toBeTrue();
expect(submitButton.disabled).toBeTrue();
expect(component.disableSubmitButton).toBeTrue();
expect(createFolderSpy).toHaveBeenCalledWith('parentNodeId', {
name: 'folder-name-update', name: 'folder-name-update',
properties: { properties: {
'cm:title': 'folder-title-update', 'cm:title': 'folder-title-update',
@@ -222,17 +234,16 @@ describe('FolderDialogComponent', () => {
nodeType: 'cm:sushi' nodeType: 'cm:sushi'
}); });
}); });
});
it('should call dialog to close with form data when submit is successfully', () => { it('should call dialog to close with form data when submit is successfully', () => {
const folder: any = { const folder: any = {
data: 'folder-data' data: 'folder-data'
}; };
component.form.controls['name'].setValue('name'); setFormValues();
component.form.controls['title'].setValue('title');
component.form.controls['description'].setValue('description');
spyOn(nodesApi, 'createFolder').and.returnValue(of(folder)); createFolderNode$.next(folder);
component.submit(); component.submit();
@@ -240,19 +251,16 @@ describe('FolderDialogComponent', () => {
}); });
it('should not submit if form is invalid', () => { it('should not submit if form is invalid', () => {
spyOn(nodesApi, 'createFolder');
component.form.controls['name'].setValue(''); component.form.controls['name'].setValue('');
component.form.controls['description'].setValue(''); component.form.controls['description'].setValue('');
component.submit(); expect(submitButton.disabled).toBeTrue();
expect(component.form.valid).toBeFalse();
expect(component.form.valid).toBe(false); expect(createFolderSpy).not.toHaveBeenCalled();
expect(nodesApi.createFolder).not.toHaveBeenCalled();
}); });
it('should not call dialog to close if submit fails', () => { 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); spyOn(component, 'handleError').and.callFake((val) => val);
component.form.controls['name'].setValue('name'); component.form.controls['name'].setValue('name');
@@ -265,18 +273,21 @@ describe('FolderDialogComponent', () => {
}); });
describe('Error events', () => { describe('Error events', () => {
afterEach(() => {
createFolderNode$.next(null);
});
it('should raise error for 409', (done) => { it('should raise error for 409', (done) => {
const error = { const error = {
message: '{ "error": { "statusCode" : 409 } }' message: '{ "error": { "statusCode" : 409 } }'
}; };
createFolderNode$.error(error);
component.error.subscribe((message) => { component.error.subscribe((message) => {
expect(message).toBe('CORE.MESSAGES.ERRORS.EXISTENT_FOLDER'); expect(message).toBe('CORE.MESSAGES.ERRORS.EXISTENT_FOLDER');
done(); done();
}); });
spyOn(nodesApi, 'createFolder').and.returnValue(throwError(error));
component.form.controls['name'].setValue('name'); component.form.controls['name'].setValue('name');
component.form.controls['description'].setValue('description'); component.form.controls['description'].setValue('description');
@@ -287,14 +298,13 @@ describe('FolderDialogComponent', () => {
const error = { const error = {
message: '{ "error": { "statusCode" : 123 } }' message: '{ "error": { "statusCode" : 123 } }'
}; };
createFolderNode$.error(error);
component.error.subscribe((message) => { component.error.subscribe((message) => {
expect(message).toBe('CORE.MESSAGES.ERRORS.GENERIC'); expect(message).toBe('CORE.MESSAGES.ERRORS.GENERIC');
done(); done();
}); });
spyOn(nodesApi, 'createFolder').and.returnValue(throwError(error));
component.form.controls['name'].setValue('name'); component.form.controls['name'].setValue('name');
component.form.controls['description'].setValue('description'); 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');
}
}); });

View File

@@ -34,11 +34,6 @@ import { forbidEndingDot, forbidOnlySpaces, forbidSpecialCharacters } from './fo
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class FolderDialogComponent implements OnInit { 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 * 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 * Emitted when the edit/create folder is successfully created/modified
*/ */
@Output() @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'; editTitle = 'CORE.FOLDER_DIALOG.EDIT_FOLDER_TITLE';
createTitle = 'CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE'; createTitle = 'CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE';
nodeType = 'cm:folder'; 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( constructor(
private formBuilder: UntypedFormBuilder, private formBuilder: UntypedFormBuilder,
private dialog: MatDialogRef<FolderDialogComponent>, private dialog: MatDialogRef<FolderDialogComponent>,
@@ -71,10 +94,6 @@ export class FolderDialogComponent implements OnInit {
} }
} }
get editing(): boolean {
return !!this.data.folder;
}
ngOnInit() { ngOnInit() {
const { folder } = this.data; const { folder } = this.data;
let name = ''; let name = '';
@@ -90,12 +109,7 @@ export class FolderDialogComponent implements OnInit {
} }
const validators = { const validators = {
name: [ name: [Validators.required, forbidSpecialCharacters, forbidEndingDot, forbidOnlySpaces]
Validators.required,
forbidSpecialCharacters,
forbidEndingDot,
forbidOnlySpaces
]
}; };
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
@@ -105,55 +119,13 @@ export class FolderDialogComponent implements OnInit {
}); });
} }
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() { submit() {
const { form, dialog, editing } = this; this.disableSubmitButton = true;
if (!form.valid) { (this.editing ? this.edit() : this.create()).subscribe(
return;
}
(editing ? this.edit() : this.create())
.subscribe(
(folder: Node) => { (folder: Node) => {
this.success.emit(folder); this.success.emit(folder);
dialog.close(folder); this.dialog.close(folder);
}, },
(error) => this.handleError(error) (error) => this.handleError(error)
); );
@@ -163,15 +135,35 @@ export class FolderDialogComponent implements OnInit {
let errorMessage = 'CORE.MESSAGES.ERRORS.GENERIC'; let errorMessage = 'CORE.MESSAGES.ERRORS.GENERIC';
try { try {
const { error: { statusCode } } = JSON.parse(error.message); const {
error: { statusCode }
} = JSON.parse(error.message);
if (statusCode === 409) { if (statusCode === 409) {
errorMessage = 'CORE.MESSAGES.ERRORS.EXISTENT_FOLDER'; 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)); this.error.emit(this.translation.instant(errorMessage));
return error; 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();
}
} }