TSK-1683: Saving Workbaskets and Classifications custom fields corrected

This commit is contained in:
Sofie Hofmann 2021-08-03 17:10:31 +02:00
parent be4caceb95
commit 0d41d615d9
7 changed files with 200 additions and 145 deletions

View File

@ -209,9 +209,9 @@
<!-- CUSTOM FIELDS --> <!-- CUSTOM FIELDS -->
<h6 class="detailed-fields__subheading"> Custom Fields </h6> <h6 class="detailed-fields__subheading"> Custom Fields </h6>
<mat-divider class="detailed-fields__horizontal-line"> </mat-divider> <mat-divider class="detailed-fields__horizontal-line"> </mat-divider>
<div class="detailed-fields__custom-fields"> <div class="detailed-fields__custom-fields">
<div *ngFor="let customField of (customFields$ | async), let i = index" class="detailed-fields__input-custom-field"> <ng-container *ngFor="let customField of (customFields$ | async), let i = index">
<div *ngIf="customField.visible" class="detailed-fields__input-custom-field">
<mat-form-field appearance="outline" style="width: 100%"> <mat-form-field appearance="outline" style="width: 100%">
<mat-label>{{customField.field}}</mat-label> <mat-label>{{customField.field}}</mat-label>
<label for="classification-custom-{{i + 1}}"></label> <label for="classification-custom-{{i + 1}}"></label>
@ -223,6 +223,7 @@
</mat-form-field> </mat-form-field>
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div> <div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div> </div>
</ng-container>
</div> </div>
</div> </div>
</ng-form> </ng-form>

View File

@ -1,10 +1,10 @@
import { Component, DebugElement, Input } from '@angular/core'; import { Component, DebugElement, Input } from '@angular/core';
import { ClassificationsService } from '../../../shared/services/classifications/classifications.service'; 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 { ClassificationCategoriesService } from '../../../shared/services/classification-categories/classification-categories.service';
import { DomainService } from '../../../shared/services/domain/domain.service'; import { DomainService } from '../../../shared/services/domain/domain.service';
import { ImportExportService } from '../../services/import-export.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 { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store';
import { ClassificationState } from '../../../shared/store/classification-store/classification.state'; import { ClassificationState } from '../../../shared/store/classification-store/classification.state';
import { EngineConfigurationState } from '../../../shared/store/engine-configuration-store/engine-configuration.state'; import { EngineConfigurationState } from '../../../shared/store/engine-configuration-store/engine-configuration.state';
@ -56,26 +56,28 @@ class TextareaStub {
} }
const classificationServiceSpy: Partial<ClassificationsService> = { const classificationServiceSpy: Partial<ClassificationsService> = {
getClassification: jest.fn().mockReturnValue(of()), getClassification: jest.fn().mockReturnValue(EMPTY),
getClassifications: jest.fn().mockReturnValue(of()), getClassifications: jest.fn().mockReturnValue(EMPTY),
postClassification: jest.fn().mockReturnValue(of()), postClassification: jest.fn().mockReturnValue(EMPTY),
putClassification: jest.fn().mockReturnValue(of()), putClassification: jest.fn().mockReturnValue(EMPTY),
deleteClassification: jest.fn().mockReturnValue(of()) deleteClassification: jest.fn().mockReturnValue(EMPTY)
}; };
const classificationCategoriesServiceSpy: Partial<ClassificationCategoriesService> = { const classificationCategoriesServiceSpy: Partial<ClassificationCategoriesService> = {
getCustomisation: jest.fn().mockReturnValue(of()) getCustomisation: jest.fn().mockReturnValue(EMPTY)
}; };
const domainServiceSpy: Partial<DomainService> = { const domainServiceSpy: Partial<DomainService> = {
getSelectedDomainValue: jest.fn().mockReturnValue(of('A')), 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<ImportExportService> = { const importExportServiceSpy: Partial<ImportExportService> = {
getImportingFinished: getImportingFinishedFn getImportingFinished: jest.fn().mockReturnValue(of(true))
}; };
const requestInProgressServiceSpy: Partial<RequestInProgressService> = { const requestInProgressServiceSpy: Partial<RequestInProgressService> = {
setRequestInProgress: jest.fn().mockReturnValue(of()), setRequestInProgress: jest.fn(),
getRequestInProgress: jest.fn().mockReturnValue(of(false)) getRequestInProgress: jest.fn().mockReturnValue(of(false))
}; };
@ -90,9 +92,9 @@ const formsValidatorServiceSpy: Partial<FormsValidatorService> = {
}; };
const notificationServiceSpy: Partial<NotificationService> = { const notificationServiceSpy: Partial<NotificationService> = {
showError: jest.fn().mockReturnValue(of()), showError: jest.fn(),
showSuccess: jest.fn().mockReturnValue(of()), showSuccess: jest.fn(),
showDialog: jest.fn().mockReturnValue(of()) showDialog: jest.fn()
}; };
describe('ClassificationDetailsComponent', () => { describe('ClassificationDetailsComponent', () => {
@ -102,7 +104,8 @@ describe('ClassificationDetailsComponent', () => {
let store: Store; let store: Store;
let actions$: Observable<any>; let actions$: Observable<any>;
beforeEach(async(() => { beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
NgxsModule.forRoot([ClassificationState, EngineConfigurationState]), NgxsModule.forRoot([ClassificationState, EngineConfigurationState]),
@ -142,7 +145,8 @@ describe('ClassificationDetailsComponent', () => {
engineConfiguration: engineConfigurationMock engineConfiguration: engineConfigurationMock
}); });
fixture.detectChanges(); fixture.detectChanges();
})); })
);
it('should create component', () => { it('should create component', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
@ -380,4 +384,26 @@ describe('ClassificationDetailsComponent', () => {
button.click(); button.click();
expect(component.classification.isValidInDomain).toBe(false); 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);
}));
}); });

View File

@ -68,8 +68,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe( this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe(
map((customisation) => customisation.information), map((customisation) => customisation.information),
getCustomFields(customFieldCount), getCustomFields(customFieldCount)
map((customisationFields) => customisationFields.filter((customisation) => customisation.visible))
); );
this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe((classification) => { this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe((classification) => {

View File

@ -152,7 +152,8 @@
<mat-divider class="horizontal-line"> </mat-divider> <mat-divider class="horizontal-line"> </mat-divider>
<div class="custom-fields"> <div class="custom-fields">
<div *ngFor="let customField of customFields$ | async; let index = index" class="custom-fields__input"> <ng-container *ngFor="let customField of customFields$ | async; let index = index">
<div *ngIf="customField.visible" class="custom-fields__input">
<mat-form-field appearance="outline" class="custom-fields__form-field"> <mat-form-field appearance="outline" class="custom-fields__form-field">
<mat-label>{{customField.field}}</mat-label> <mat-label>{{customField.field}}</mat-label>
<label for='wb-custom-{{index+1}}'></label> <label for='wb-custom-{{index+1}}'></label>
@ -163,6 +164,7 @@
</mat-form-field> </mat-form-field>
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div> <div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div> </div>
</ng-container>
</div> </div>
</div> </div>

View File

@ -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 { WorkbasketInformationComponent } from './workbasket-information.component';
import { Component, DebugElement, Input } from '@angular/core'; import { Component, DebugElement, Input } from '@angular/core';
import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; 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 { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { WorkbasketType } from '../../../shared/models/workbasket-type';
import { MapValuesPipe } from '../../../shared/pipes/map-values.pipe'; 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 { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { By } from '@angular/platform-browser';
@Component({ selector: 'taskana-shared-field-error-display', template: '' }) @Component({ selector: 'taskana-shared-field-error-display', template: '' })
class FieldErrorDisplayStub { class FieldErrorDisplayStub {
@ -53,21 +54,19 @@ class IconTypeStub {
@Input() text: string; @Input() text: string;
} }
const triggerWorkbasketSavedFn = jest.fn().mockReturnValue(true);
const workbasketServiceMock: Partial<WorkbasketService> = { const workbasketServiceMock: Partial<WorkbasketService> = {
triggerWorkBasketSaved: triggerWorkbasketSavedFn, triggerWorkBasketSaved: jest.fn(),
updateWorkbasket: jest.fn().mockReturnValue(of(true)), updateWorkbasket: jest.fn().mockReturnValue(of(true)),
markWorkbasketForDeletion: jest.fn().mockReturnValue(of(true)), markWorkbasketForDeletion: jest.fn().mockReturnValue(of(true)),
createWorkbasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })), createWorkbasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })),
getWorkBasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })), getWorkBasket: jest.fn().mockReturnValue(of({ ...selectedWorkbasketMock })),
getWorkBasketAccessItems: jest.fn().mockReturnValue(of()), getWorkBasketAccessItems: jest.fn().mockReturnValue(EMPTY),
getWorkBasketsDistributionTargets: jest.fn().mockReturnValue(of()) getWorkBasketsDistributionTargets: jest.fn().mockReturnValue(EMPTY)
}; };
const isFieldValidFn = jest.fn().mockReturnValue(true);
const validateFormInformationFn = jest.fn().mockImplementation((): Promise<any> => Promise.resolve(true)); const validateFormInformationFn = jest.fn().mockImplementation((): Promise<any> => Promise.resolve(true));
const formValidatorServiceMock: Partial<FormsValidatorService> = { const formValidatorServiceMock: Partial<FormsValidatorService> = {
isFieldValid: isFieldValidFn, isFieldValid: jest.fn().mockReturnValue(true),
validateInputOverflow: jest.fn(), validateInputOverflow: jest.fn(),
validateFormInformation: validateFormInformationFn, validateFormInformation: validateFormInformationFn,
get inputOverflowObservable(): Observable<Map<string, boolean>> { get inputOverflowObservable(): Observable<Map<string, boolean>> {
@ -75,10 +74,9 @@ const formValidatorServiceMock: Partial<FormsValidatorService> = {
} }
}; };
const showDialogFn = jest.fn().mockReturnValue(true);
const notificationServiceMock: Partial<NotificationService> = { const notificationServiceMock: Partial<NotificationService> = {
showDialog: showDialogFn, showDialog: jest.fn(),
showSuccess: showDialogFn showSuccess: jest.fn()
}; };
describe('WorkbasketInformationComponent', () => { describe('WorkbasketInformationComponent', () => {
@ -88,7 +86,8 @@ describe('WorkbasketInformationComponent', () => {
let store: Store; let store: Store;
let actions$: Observable<any>; let actions$: Observable<any>;
beforeEach(async(() => { beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
FormsModule, FormsModule,
@ -142,7 +141,8 @@ describe('WorkbasketInformationComponent', () => {
component.workbasket = selectedWorkbasketMock; component.workbasket = selectedWorkbasketMock;
fixture.detectChanges(); fixture.detectChanges();
})); })
);
it('should create component', () => { it('should create component', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
@ -174,7 +174,9 @@ describe('WorkbasketInformationComponent', () => {
expect(component.workbasket).toMatchObject(component.workbasketClone); expect(component.workbasket).toMatchObject(component.workbasketClone);
}); });
it('should save workbasket when workbasketId there', async(() => { it(
'should save workbasket when workbasketId there',
waitForAsync(() => {
component.workbasket = { ...selectedWorkbasketMock }; component.workbasket = { ...selectedWorkbasketMock };
component.workbasket.workbasketId = '1'; component.workbasket.workbasketId = '1';
component.action = ACTION.COPY; component.action = ACTION.COPY;
@ -183,14 +185,18 @@ describe('WorkbasketInformationComponent', () => {
component.onSave(); component.onSave();
expect(actionDispatched).toBe(true); expect(actionDispatched).toBe(true);
expect(component.workbasketClone).toMatchObject(component.workbasket); expect(component.workbasketClone).toMatchObject(component.workbasket);
})); })
);
it('should dispatch MarkWorkbasketforDeletion action when onRemoveConfirmed is called', async(() => { it(
'should dispatch MarkWorkbasketforDeletion action when onRemoveConfirmed is called',
waitForAsync(() => {
let actionDispatched = false; let actionDispatched = false;
actions$.pipe(ofActionDispatched(MarkWorkbasketForDeletion)).subscribe(() => (actionDispatched = true)); actions$.pipe(ofActionDispatched(MarkWorkbasketForDeletion)).subscribe(() => (actionDispatched = true));
component.onRemoveConfirmed(); component.onRemoveConfirmed();
expect(actionDispatched).toBe(true); expect(actionDispatched).toBe(true);
})); })
);
it('should create new workbasket when workbasketId is undefined', () => { it('should create new workbasket when workbasketId is undefined', () => {
component.workbasket.workbasketId = undefined; component.workbasket.workbasketId = undefined;
@ -198,4 +204,26 @@ describe('WorkbasketInformationComponent', () => {
component.onSave(); component.onSave();
expect(postNewWorkbasketSpy).toHaveBeenCalled(); 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);
}));
}); });

View File

@ -77,8 +77,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.customFields$ = this.workbasketsCustomisation$.pipe( this.customFields$ = this.workbasketsCustomisation$.pipe(
map((customisation) => customisation.information), map((customisation) => customisation.information),
getCustomFields(customFieldCount), getCustomFields(customFieldCount)
map((customFields) => customFields.filter((customisation) => customisation.visible))
); );
this.workbasketsCustomisation$.pipe(takeUntil(this.destroy$)).subscribe((workbasketsCustomization) => { this.workbasketsCustomisation$.pipe(takeUntil(this.destroy$)).subscribe((workbasketsCustomization) => {
if (workbasketsCustomization.information.owner) { if (workbasketsCustomization.information.owner) {

View File

@ -91,7 +91,7 @@ export class FormsValidatorService {
return result; return result;
} }
isFieldValid(ngForm: NgForm, field: string) { isFieldValid(ngForm: NgForm, field: string): boolean {
if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) { if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) {
return false; return false;
} }