From 0d41d615d93f0f9dee5da87bcfc9b3150d521d7a Mon Sep 17 00:00:00 2001 From: Sofie Hofmann <29145005+sofie29@users.noreply.github.com> Date: Tue, 3 Aug 2021 17:10:31 +0200 Subject: [PATCH] TSK-1683: Saving Workbaskets and Classifications custom fields corrected --- .../classification-details.component.html | 7 +- .../classification-details.component.spec.ts | 136 ++++++++----- .../classification-details.component.ts | 3 +- .../workbasket-information.component.html | 6 +- .../workbasket-information.component.spec.ts | 188 ++++++++++-------- .../workbasket-information.component.ts | 3 +- .../forms-validator.service.ts | 2 +- 7 files changed, 200 insertions(+), 145 deletions(-) diff --git a/web/src/app/administration/components/classification-details/classification-details.component.html b/web/src/app/administration/components/classification-details/classification-details.component.html index a890d597d..65107b078 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.html +++ b/web/src/app/administration/components/classification-details/classification-details.component.html @@ -209,9 +209,9 @@
Custom Fields
-
-
+ +
{{customField.field}} @@ -222,7 +222,8 @@ (input)="validateInputOverflow(custom, 255)">
{{lengthError}}
-
+
+
diff --git a/web/src/app/administration/components/classification-details/classification-details.component.spec.ts b/web/src/app/administration/components/classification-details/classification-details.component.spec.ts index 9dc2abc88..1ff94d64c 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.spec.ts +++ b/web/src/app/administration/components/classification-details/classification-details.component.spec.ts @@ -1,10 +1,10 @@ import { Component, DebugElement, Input } from '@angular/core'; import { ClassificationsService } from '../../../shared/services/classifications/classifications.service'; -import { Observable, of } from 'rxjs'; +import { EMPTY, Observable, of } from 'rxjs'; import { ClassificationCategoriesService } from '../../../shared/services/classification-categories/classification-categories.service'; import { DomainService } from '../../../shared/services/domain/domain.service'; import { ImportExportService } from '../../services/import-export.service'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; import { ClassificationState } from '../../../shared/store/classification-store/classification.state'; import { EngineConfigurationState } from '../../../shared/store/engine-configuration-store/engine-configuration.state'; @@ -56,26 +56,28 @@ class TextareaStub { } const classificationServiceSpy: Partial = { - getClassification: jest.fn().mockReturnValue(of()), - getClassifications: jest.fn().mockReturnValue(of()), - postClassification: jest.fn().mockReturnValue(of()), - putClassification: jest.fn().mockReturnValue(of()), - deleteClassification: jest.fn().mockReturnValue(of()) + getClassification: jest.fn().mockReturnValue(EMPTY), + getClassifications: jest.fn().mockReturnValue(EMPTY), + postClassification: jest.fn().mockReturnValue(EMPTY), + putClassification: jest.fn().mockReturnValue(EMPTY), + deleteClassification: jest.fn().mockReturnValue(EMPTY) }; + const classificationCategoriesServiceSpy: Partial = { - getCustomisation: jest.fn().mockReturnValue(of()) + getCustomisation: jest.fn().mockReturnValue(EMPTY) }; + const domainServiceSpy: Partial = { getSelectedDomainValue: jest.fn().mockReturnValue(of('A')), - getSelectedDomain: jest.fn().mockReturnValue(of()) + getSelectedDomain: jest.fn().mockReturnValue(EMPTY) }; -const getImportingFinishedFn = jest.fn().mockReturnValue(of(true)); + const importExportServiceSpy: Partial = { - getImportingFinished: getImportingFinishedFn + getImportingFinished: jest.fn().mockReturnValue(of(true)) }; const requestInProgressServiceSpy: Partial = { - setRequestInProgress: jest.fn().mockReturnValue(of()), + setRequestInProgress: jest.fn(), getRequestInProgress: jest.fn().mockReturnValue(of(false)) }; @@ -90,9 +92,9 @@ const formsValidatorServiceSpy: Partial = { }; const notificationServiceSpy: Partial = { - showError: jest.fn().mockReturnValue(of()), - showSuccess: jest.fn().mockReturnValue(of()), - showDialog: jest.fn().mockReturnValue(of()) + showError: jest.fn(), + showSuccess: jest.fn(), + showDialog: jest.fn() }; describe('ClassificationDetailsComponent', () => { @@ -102,47 +104,49 @@ describe('ClassificationDetailsComponent', () => { let store: Store; let actions$: Observable; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - NgxsModule.forRoot([ClassificationState, EngineConfigurationState]), - FormsModule, - MatIconModule, - MatToolbarModule, - MatDividerModule, - MatFormFieldModule, - MatInputModule, - MatOptionModule, - MatSelectModule, - MatProgressBarModule, - MatMenuModule, - MatTooltipModule, - BrowserAnimationsModule - ], - declarations: [ClassificationDetailsComponent, InputStub, FieldErrorDisplayStub, SvgIconStub, TextareaStub], - providers: [ - { provide: ClassificationsService, useValue: classificationServiceSpy }, - { provide: ClassificationCategoriesService, useValue: classificationCategoriesServiceSpy }, - { provide: DomainService, useValue: domainServiceSpy }, - { provide: ImportExportService, useValue: importExportServiceSpy }, - { provide: RequestInProgressService, useValue: requestInProgressServiceSpy }, - { provide: FormsValidatorService, useValue: formsValidatorServiceSpy }, - { provide: NotificationService, useValue: notificationServiceSpy } - ] - }).compileComponents(); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + NgxsModule.forRoot([ClassificationState, EngineConfigurationState]), + FormsModule, + MatIconModule, + MatToolbarModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatOptionModule, + MatSelectModule, + MatProgressBarModule, + MatMenuModule, + MatTooltipModule, + BrowserAnimationsModule + ], + declarations: [ClassificationDetailsComponent, InputStub, FieldErrorDisplayStub, SvgIconStub, TextareaStub], + providers: [ + { provide: ClassificationsService, useValue: classificationServiceSpy }, + { provide: ClassificationCategoriesService, useValue: classificationCategoriesServiceSpy }, + { provide: DomainService, useValue: domainServiceSpy }, + { provide: ImportExportService, useValue: importExportServiceSpy }, + { provide: RequestInProgressService, useValue: requestInProgressServiceSpy }, + { provide: FormsValidatorService, useValue: formsValidatorServiceSpy }, + { provide: NotificationService, useValue: notificationServiceSpy } + ] + }).compileComponents(); - fixture = TestBed.createComponent(ClassificationDetailsComponent); - debugElement = fixture.debugElement; - component = fixture.debugElement.componentInstance; - store = TestBed.inject(Store); - actions$ = TestBed.inject(Actions); - store.reset({ - ...store.snapshot(), - classification: classificationStateMock, - engineConfiguration: engineConfigurationMock - }); - fixture.detectChanges(); - })); + fixture = TestBed.createComponent(ClassificationDetailsComponent); + debugElement = fixture.debugElement; + component = fixture.debugElement.componentInstance; + store = TestBed.inject(Store); + actions$ = TestBed.inject(Actions); + store.reset({ + ...store.snapshot(), + classification: classificationStateMock, + engineConfiguration: engineConfigurationMock + }); + fixture.detectChanges(); + }) + ); it('should create component', () => { expect(component).toBeTruthy(); @@ -380,4 +384,26 @@ describe('ClassificationDetailsComponent', () => { button.click(); expect(component.classification.isValidInDomain).toBe(false); }); + + it('should not show custom fields with attribute visible = false', () => { + const inputCustoms = debugElement.queryAll(By.css('.detailed-fields__input-custom-field')); + expect(inputCustoms).toHaveLength(7); + }); + + it('should save custom field input at position 4 when custom field at position 3 is not visible', fakeAsync(() => { + const newValue = 'New value'; + + let inputCustom3 = debugElement.nativeElement.querySelector('#classification-custom-3'); + let inputCustom4 = debugElement.nativeElement.querySelector('#classification-custom-4'); + expect(inputCustom3).toBeFalsy(); + expect(inputCustom4).toBeTruthy(); + inputCustom4.value = newValue; + inputCustom4.dispatchEvent(new Event('input')); + + tick(); + fixture.detectChanges(); + + expect(component.classification['custom3']).toBe(undefined); + expect(component.classification['custom4']).toBe(newValue); + })); }); diff --git a/web/src/app/administration/components/classification-details/classification-details.component.ts b/web/src/app/administration/components/classification-details/classification-details.component.ts index 83e60a898..4ff3d93bf 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.ts +++ b/web/src/app/administration/components/classification-details/classification-details.component.ts @@ -68,8 +68,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { ngOnInit() { this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe( map((customisation) => customisation.information), - getCustomFields(customFieldCount), - map((customisationFields) => customisationFields.filter((customisation) => customisation.visible)) + getCustomFields(customFieldCount) ); this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe((classification) => { diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.html b/web/src/app/administration/components/workbasket-information/workbasket-information.component.html index 1ba663479..4cf5101b0 100644 --- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.html +++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.html @@ -152,7 +152,8 @@
-
+ +
{{customField.field}} @@ -162,7 +163,8 @@ (input)="validateInputOverflow(custom, 255)">
{{lengthError}}
-
+
+
diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts b/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts index 91833ce89..fd159670c 100644 --- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts +++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts @@ -1,8 +1,8 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { WorkbasketInformationComponent } from './workbasket-information.component'; import { Component, DebugElement, Input } from '@angular/core'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; -import { Observable, of } from 'rxjs'; +import { EMPTY, Observable, of } from 'rxjs'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { MapValuesPipe } from '../../../shared/pipes/map-values.pipe'; @@ -39,6 +39,7 @@ import { MatSelectModule } from '@angular/material/select'; import { MatInputModule } from '@angular/material/input'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { By } from '@angular/platform-browser'; @Component({ selector: 'taskana-shared-field-error-display', template: '' }) class FieldErrorDisplayStub { @@ -53,21 +54,19 @@ class IconTypeStub { @Input() text: string; } -const triggerWorkbasketSavedFn = jest.fn().mockReturnValue(true); const workbasketServiceMock: Partial = { - triggerWorkBasketSaved: triggerWorkbasketSavedFn, + triggerWorkBasketSaved: jest.fn(), updateWorkbasket: jest.fn().mockReturnValue(of(true)), markWorkbasketForDeletion: jest.fn().mockReturnValue(of(true)), createWorkbasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })), getWorkBasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })), - getWorkBasketAccessItems: jest.fn().mockReturnValue(of()), - getWorkBasketsDistributionTargets: jest.fn().mockReturnValue(of()) + getWorkBasketAccessItems: jest.fn().mockReturnValue(EMPTY), + getWorkBasketsDistributionTargets: jest.fn().mockReturnValue(EMPTY) }; -const isFieldValidFn = jest.fn().mockReturnValue(true); const validateFormInformationFn = jest.fn().mockImplementation((): Promise => Promise.resolve(true)); const formValidatorServiceMock: Partial = { - isFieldValid: isFieldValidFn, + isFieldValid: jest.fn().mockReturnValue(true), validateInputOverflow: jest.fn(), validateFormInformation: validateFormInformationFn, get inputOverflowObservable(): Observable> { @@ -75,10 +74,9 @@ const formValidatorServiceMock: Partial = { } }; -const showDialogFn = jest.fn().mockReturnValue(true); const notificationServiceMock: Partial = { - showDialog: showDialogFn, - showSuccess: showDialogFn + showDialog: jest.fn(), + showSuccess: jest.fn() }; describe('WorkbasketInformationComponent', () => { @@ -88,61 +86,63 @@ describe('WorkbasketInformationComponent', () => { let store: Store; let actions$: Observable; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - FormsModule, - HttpClientTestingModule, - MatDialogModule, - NgxsModule.forRoot([EngineConfigurationState, WorkbasketState]), - TypeaheadModule.forRoot(), - ReactiveFormsModule, - RouterTestingModule.withRoutes([]), - BrowserAnimationsModule, - MatProgressBarModule, - MatDividerModule, - MatFormFieldModule, - MatInputModule, - MatSelectModule, - MatAutocompleteModule, - MatTooltipModule - ], - declarations: [ - WorkbasketInformationComponent, - FieldErrorDisplayStub, - IconTypeStub, - TypeAheadComponent, - MapValuesPipe, - RemoveNoneTypePipe - ], - providers: [ - { provide: WorkbasketService, useValue: workbasketServiceMock }, - { provide: FormsValidatorService, useValue: formValidatorServiceMock }, - { provide: NotificationService, useValue: notificationServiceMock }, - RequestInProgressService, - DomainService, - SelectedRouteService, - ClassificationCategoriesService, - StartupService, - TaskanaEngineService, - WindowRefService - ] - }).compileComponents(); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + HttpClientTestingModule, + MatDialogModule, + NgxsModule.forRoot([EngineConfigurationState, WorkbasketState]), + TypeaheadModule.forRoot(), + ReactiveFormsModule, + RouterTestingModule.withRoutes([]), + BrowserAnimationsModule, + MatProgressBarModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatAutocompleteModule, + MatTooltipModule + ], + declarations: [ + WorkbasketInformationComponent, + FieldErrorDisplayStub, + IconTypeStub, + TypeAheadComponent, + MapValuesPipe, + RemoveNoneTypePipe + ], + providers: [ + { provide: WorkbasketService, useValue: workbasketServiceMock }, + { provide: FormsValidatorService, useValue: formValidatorServiceMock }, + { provide: NotificationService, useValue: notificationServiceMock }, + RequestInProgressService, + DomainService, + SelectedRouteService, + ClassificationCategoriesService, + StartupService, + TaskanaEngineService, + WindowRefService + ] + }).compileComponents(); - fixture = TestBed.createComponent(WorkbasketInformationComponent); - debugElement = fixture.debugElement; - component = fixture.componentInstance; - store = TestBed.inject(Store); - actions$ = TestBed.inject(Actions); - store.reset({ - ...store.snapshot(), - engineConfiguration: engineConfigurationMock, - workbasket: workbasketReadStateMock - }); - component.workbasket = selectedWorkbasketMock; + fixture = TestBed.createComponent(WorkbasketInformationComponent); + debugElement = fixture.debugElement; + component = fixture.componentInstance; + store = TestBed.inject(Store); + actions$ = TestBed.inject(Actions); + store.reset({ + ...store.snapshot(), + engineConfiguration: engineConfigurationMock, + workbasket: workbasketReadStateMock + }); + component.workbasket = selectedWorkbasketMock; - fixture.detectChanges(); - })); + fixture.detectChanges(); + }) + ); it('should create component', () => { expect(component).toBeTruthy(); @@ -174,23 +174,29 @@ describe('WorkbasketInformationComponent', () => { expect(component.workbasket).toMatchObject(component.workbasketClone); }); - it('should save workbasket when workbasketId there', async(() => { - component.workbasket = { ...selectedWorkbasketMock }; - component.workbasket.workbasketId = '1'; - component.action = ACTION.COPY; - let actionDispatched = false; - actions$.pipe(ofActionDispatched(UpdateWorkbasket)).subscribe(() => (actionDispatched = true)); - component.onSave(); - expect(actionDispatched).toBe(true); - expect(component.workbasketClone).toMatchObject(component.workbasket); - })); + it( + 'should save workbasket when workbasketId there', + waitForAsync(() => { + component.workbasket = { ...selectedWorkbasketMock }; + component.workbasket.workbasketId = '1'; + component.action = ACTION.COPY; + let actionDispatched = false; + actions$.pipe(ofActionDispatched(UpdateWorkbasket)).subscribe(() => (actionDispatched = true)); + component.onSave(); + expect(actionDispatched).toBe(true); + expect(component.workbasketClone).toMatchObject(component.workbasket); + }) + ); - it('should dispatch MarkWorkbasketforDeletion action when onRemoveConfirmed is called', async(() => { - let actionDispatched = false; - actions$.pipe(ofActionDispatched(MarkWorkbasketForDeletion)).subscribe(() => (actionDispatched = true)); - component.onRemoveConfirmed(); - expect(actionDispatched).toBe(true); - })); + it( + 'should dispatch MarkWorkbasketforDeletion action when onRemoveConfirmed is called', + waitForAsync(() => { + let actionDispatched = false; + actions$.pipe(ofActionDispatched(MarkWorkbasketForDeletion)).subscribe(() => (actionDispatched = true)); + component.onRemoveConfirmed(); + expect(actionDispatched).toBe(true); + }) + ); it('should create new workbasket when workbasketId is undefined', () => { component.workbasket.workbasketId = undefined; @@ -198,4 +204,26 @@ describe('WorkbasketInformationComponent', () => { component.onSave(); expect(postNewWorkbasketSpy).toHaveBeenCalled(); }); + + it('should not show custom fields with attribute visible = false', () => { + const inputCustoms = debugElement.queryAll(By.css('.custom-fields__input')); + expect(inputCustoms).toHaveLength(3); + }); + + it('should save custom field input at position 4 when custom field at position 3 is not visible', fakeAsync(() => { + const newValue = 'New value'; + + let inputCustom3 = debugElement.nativeElement.querySelector('#wb-custom-3'); + let inputCustom4 = debugElement.nativeElement.querySelector('#wb-custom-4'); + expect(inputCustom3).toBeFalsy(); + expect(inputCustom4).toBeTruthy(); + inputCustom4.value = newValue; + inputCustom4.dispatchEvent(new Event('input')); + + tick(); + fixture.detectChanges(); + + expect(component.workbasket['custom3']).toBe(''); + expect(component.workbasket['custom4']).toBe(newValue); + })); }); diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts b/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts index 7ddf8ace3..1966a3975 100644 --- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts +++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts @@ -77,8 +77,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest this.customFields$ = this.workbasketsCustomisation$.pipe( map((customisation) => customisation.information), - getCustomFields(customFieldCount), - map((customFields) => customFields.filter((customisation) => customisation.visible)) + getCustomFields(customFieldCount) ); this.workbasketsCustomisation$.pipe(takeUntil(this.destroy$)).subscribe((workbasketsCustomization) => { if (workbasketsCustomization.information.owner) { diff --git a/web/src/app/shared/services/forms-validator/forms-validator.service.ts b/web/src/app/shared/services/forms-validator/forms-validator.service.ts index 4bf3e3767..3c5e764b4 100644 --- a/web/src/app/shared/services/forms-validator/forms-validator.service.ts +++ b/web/src/app/shared/services/forms-validator/forms-validator.service.ts @@ -91,7 +91,7 @@ export class FormsValidatorService { return result; } - isFieldValid(ngForm: NgForm, field: string) { + isFieldValid(ngForm: NgForm, field: string): boolean { if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) { return false; }