From e6c68c9396d8acb13e4c2ead6a94f1dfee2efef4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 22 Jun 2018 15:13:49 +0200 Subject: [PATCH] Added more tests --- .../integration/integration.service.spec.ts | 94 +++++++++++++++++++ src/app/shared/chips/chips.component.spec.ts | 74 ++++++++++++++- src/app/shared/chips/chips.component.ts | 8 +- .../models/ds-dynamic-concat.model.ts | 7 +- .../builder/parsers/concat-field-parser.ts | 6 +- .../builder/parsers/date-field-parser.spec.ts | 15 ++- .../form/builder/parsers/date-field-parser.ts | 14 +-- .../form/builder/parsers/field-parser.ts | 4 +- .../builder/parsers/name-field-parser.spec.ts | 20 +++- .../parsers/series-field-parser.spec.ts | 16 +++- src/app/shared/form/form.service.spec.ts | 3 +- src/app/shared/object.util.spec.ts | 87 +++++++++++++++++ src/app/shared/object.util.ts | 22 +++-- 13 files changed, 325 insertions(+), 45 deletions(-) create mode 100644 src/app/core/integration/integration.service.spec.ts create mode 100644 src/app/shared/object.util.spec.ts diff --git a/src/app/core/integration/integration.service.spec.ts b/src/app/core/integration/integration.service.spec.ts new file mode 100644 index 0000000000..fda3008bec --- /dev/null +++ b/src/app/core/integration/integration.service.spec.ts @@ -0,0 +1,94 @@ +import { cold, getTestScheduler } from 'jasmine-marbles'; +import { TestScheduler } from 'rxjs/Rx'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; + +import { RequestService } from '../data/request.service'; +import { IntegrationRequest } from '../data/request.models'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; +import { IntegrationService } from './integration.service'; +import { IntegrationSearchOptions } from './models/integration-options.model'; + +const LINK_NAME = 'authorities'; +const BROWSE = 'entries'; + +class TestService extends IntegrationService { + protected linkPath = LINK_NAME; + protected browseEndpoint = BROWSE; + + constructor( + protected responseCache: ResponseCacheService, + protected requestService: RequestService, + protected halService: HALEndpointService) { + super(); + } +} + +describe('IntegrationService', () => { + let scheduler: TestScheduler; + let service: TestService; + let responseCache: ResponseCacheService; + let requestService: RequestService; + let halService: any; + let findOptions: IntegrationSearchOptions; + + const name = 'type'; + const metadata = 'dc.type'; + const query = ''; + const uuid = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; + const integrationEndpoint = 'https://rest.api/integration'; + const serviceEndpoint = `${integrationEndpoint}/${LINK_NAME}`; + const entriesEndpoint = `${serviceEndpoint}/${name}/entries?query=${query}&metadata=${metadata}&uuid=${uuid}`; + + findOptions = new IntegrationSearchOptions(uuid, name, metadata); + + function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService { + return jasmine.createSpyObj('responseCache', { + get: cold('c-', { + c: {response: {isSuccessful}} + }) + }); + } + + function initTestService(): TestService { + return new TestService( + responseCache, + requestService, + halService + ); + } + + beforeEach(() => { + responseCache = initMockResponseCacheService(true); + requestService = getMockRequestService(); + scheduler = getTestScheduler(); + halService = new HALEndpointServiceStub(integrationEndpoint); + findOptions = new IntegrationSearchOptions(uuid, name, metadata, query); + service = initTestService(); + + }); + + describe('getEntriesByName', () => { + + it('should configure a new IntegrationRequest', () => { + const expected = new IntegrationRequest(requestService.generateRequestId(), entriesEndpoint); + scheduler.schedule(() => service.getEntriesByName(findOptions).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + }); + + // describe('getConfigBySearch', () => { + // + // it('should configure a new ConfigRequest', () => { + // findOptions.uuid = uuid; + // const expected = new ConfigRequest(requestService.generateRequestId(), searchEndpoint); + // scheduler.schedule(() => service.getConfigBySearch(findOptions).subscribe()); + // scheduler.flush(); + // + // expect(requestService.configure).toHaveBeenCalledWith(expected); + // }); + // }); +}); diff --git a/src/app/shared/chips/chips.component.spec.ts b/src/app/shared/chips/chips.component.spec.ts index add22b5d42..20cd22488f 100644 --- a/src/app/shared/chips/chips.component.spec.ts +++ b/src/app/shared/chips/chips.component.spec.ts @@ -1,6 +1,6 @@ // Load the implementations that should be tested import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing'; import 'rxjs/add/observable/of'; import { Chips } from './models/chips.model'; @@ -8,6 +8,9 @@ import { UploaderService } from '../uploader/uploader.service'; import { ChipsComponent } from './chips.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SortablejsModule } from 'angular-sortablejs'; +import { By } from '@angular/platform-browser'; +import { PaginationComponent } from '../pagination/pagination.component'; +import { TruncatableComponent } from '../truncatable/truncatable.component'; function createTestComponent(html: string, type: { new(...args: any[]): T }): ComponentFixture { TestBed.overrideComponent(type, { @@ -22,8 +25,11 @@ function createTestComponent(html: string, type: { new(...args: any[]): T }): describe('Chips component', () => { let testComp: TestComponent; + let chipsComp: ChipsComponent; let testFixture: ComponentFixture; + let chipsFixture: ComponentFixture; let html; + let chips: Chips; // async beforeEach beforeEach(async(() => { @@ -53,7 +59,7 @@ describe('Chips component', () => { `; testFixture = createTestComponent(html, TestComponent) as ComponentFixture; @@ -61,10 +67,68 @@ describe('Chips component', () => { }); it('should create Chips Component', inject([ChipsComponent], (app: ChipsComponent) => { - expect(app).toBeDefined(); })); + beforeEach(() => { + chips = new Chips(['a', 'b', 'c']); + chipsFixture = TestBed.createComponent(ChipsComponent); + chipsComp = chipsFixture.componentInstance; // TruncatableComponent test instance + chipsComp.editable = true; + chipsComp.chips = chips; + chipsFixture.detectChanges(); + }); + + afterEach(() => { + chipsFixture.destroy(); + chipsComp = null; + }); + + it('should set edit mode when a chip item is selected', fakeAsync(() => { + + spyOn(chipsComp.selected, 'emit'); + + chipsComp.chipsSelected(new Event('click'), 1); + chipsFixture.detectChanges(); + tick(); + + const item = chipsComp.chips.getChipByIndex(1); + + expect(item.editMode).toBe(true); + expect(chipsComp.selected.emit).toHaveBeenCalledWith(1); + })); + + it('should not set edit mode when a chip item is selected and editable is false', fakeAsync(() => { + chipsComp.editable = false; + spyOn(chipsComp.selected, 'emit'); + + chipsComp.chipsSelected(new Event('click'), 1); + chipsFixture.detectChanges(); + tick(); + + const item = chipsComp.chips.getChipByIndex(1); + + expect(item.editMode).toBe(false); + expect(chipsComp.selected.emit).not.toHaveBeenCalledWith(1); + })); + + it('should emit when a chip item is removed and editable is true', fakeAsync(() => { + + spyOn(chipsComp.chips, 'remove'); + + const item = chipsComp.chips.getChipByIndex(1); + + chipsComp.removeChips(new Event('click'), 1); + chipsFixture.detectChanges(); + tick(); + + expect(chipsComp.chips.remove).toHaveBeenCalledWith(item); + })); + + // it('should chipsSelected', inject([ChipsComponent], (app: ChipsComponent) => { + // app.chipsSelected(new Event('click'), 1); + // expect(app).toBeDefined(); + // })); }); // declare a test component @@ -74,6 +138,6 @@ describe('Chips component', () => { }) class TestComponent { - public chips = new Chips([]); - + public chips = new Chips(['a', 'b', 'c']); + public editable = true; } diff --git a/src/app/shared/chips/chips.component.ts b/src/app/shared/chips/chips.component.ts index eea0993c98..e8167ec5a1 100644 --- a/src/app/shared/chips/chips.component.ts +++ b/src/app/shared/chips/chips.component.ts @@ -16,7 +16,7 @@ import { UploaderService } from '../uploader/uploader.service'; export class ChipsComponent implements OnChanges { @Input() chips: Chips; @Input() wrapperClass: string; - @Input() editable: boolean; + @Input() editable = true; @Output() selected: EventEmitter = new EventEmitter(); @Output() remove: EventEmitter = new EventEmitter(); @@ -36,12 +36,6 @@ export class ChipsComponent implements OnChanges { }; } - ngOnInit() { - if (!this.editable) { - this.editable = false; - } - } - ngOnChanges(changes: SimpleChanges) { if (changes.chips && !changes.chips.isFirstChange()) { this.chips = changes.chips.currentValue; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts index aee2e65f3a..47d410f2ed 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts @@ -2,6 +2,7 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelC import { isNotEmpty } from '../../../../empty.util'; import { DsDynamicInputModel } from './ds-dynamic-input.model'; import { AuthorityValueModel } from '../../../../../core/integration/models/authority-value.model'; +import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model'; export const CONCAT_GROUP_SUFFIX = '_CONCAT_GROUP'; export const CONCAT_FIRST_INPUT_SUFFIX = '_CONCAT_FIRST_INPUT'; @@ -27,13 +28,13 @@ export class DynamicConcatModel extends DynamicFormGroupModel { const firstValue = (this.get(0) as DsDynamicInputModel).value; const secondValue = (this.get(1) as DsDynamicInputModel).value; if (isNotEmpty(firstValue) && isNotEmpty(secondValue)) { - return firstValue + this.separator + secondValue; + return new FormFieldMetadataValueObject(firstValue + this.separator + secondValue); } else { - return null + return null; } } - set value(value: string | AuthorityValueModel) { + set value(value: string | FormFieldMetadataValueObject) { let values; if (typeof value === 'string') { values = value ? value.split(this.separator) : [null, null]; diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts index bd636e94de..68d34f7bbe 100644 --- a/src/app/shared/form/builder/parsers/concat-field-parser.ts +++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts @@ -71,11 +71,11 @@ export class ConcatFieldParser extends FieldParser { // Init values if (isNotEmpty(fieldValue)) { - const values = fieldValue.split(this.separator); + const values = fieldValue.value.split(this.separator); if (values.length > 1) { - input1ModelConfig.value = values[0]; - input2ModelConfig.value = values[1]; + input1ModelConfig.value = values[0].trim(); + input2ModelConfig.value = values[1].trim(); } } diff --git a/src/app/shared/form/builder/parsers/date-field-parser.spec.ts b/src/app/shared/form/builder/parsers/date-field-parser.spec.ts index 430e6df3cb..a103b53b35 100644 --- a/src/app/shared/form/builder/parsers/date-field-parser.spec.ts +++ b/src/app/shared/form/builder/parsers/date-field-parser.spec.ts @@ -3,11 +3,12 @@ import { DynamicConcatModel } from '../ds-dynamic-form-ui/models/ds-dynamic-conc import { SeriesFieldParser } from './series-field-parser'; import { DateFieldParser } from './date-field-parser'; import { DynamicDsDatePickerModel } from '../ds-dynamic-form-ui/models/date-picker/date-picker.model'; +import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; describe('DateFieldParser test suite', () => { let field: FormFieldModel; + let initFormValues: any = {}; - const initFormValues = {}; const readOnly = false; beforeEach(() => { @@ -44,4 +45,16 @@ describe('DateFieldParser test suite', () => { expect(fieldModel instanceof DynamicDsDatePickerModel).toBe(true); }); + it('should set init value properly', () => { + initFormValues = { + date: [new FormFieldMetadataValueObject('1983-11-18')], + }; + const expectedValue = '1983-11-18'; + + const parser = new DateFieldParser(field, initFormValues, readOnly); + + const fieldModel = parser.parse(); + + expect(fieldModel.value).toEqual(expectedValue); + }); }); diff --git a/src/app/shared/form/builder/parsers/date-field-parser.ts b/src/app/shared/form/builder/parsers/date-field-parser.ts index 2c997211f1..842036db6c 100644 --- a/src/app/shared/form/builder/parsers/date-field-parser.ts +++ b/src/app/shared/form/builder/parsers/date-field-parser.ts @@ -8,13 +8,13 @@ import { FormFieldMetadataValueObject } from '../models/form-field-metadata-valu export class DateFieldParser extends FieldParser { public modelFactory(fieldValue: FormFieldMetadataValueObject): any { + let malformedDate = false; const inputDateModelConfig: DynamicDatePickerModelConfig = this.initModel(); inputDateModelConfig.toggleIcon = 'fa fa-calendar'; this.setValues(inputDateModelConfig as any, fieldValue); // Init Data and validity check if (isNotEmpty(inputDateModelConfig.value)) { - let malformedData = false; const value = inputDateModelConfig.value.toString(); if (value.length >= 4) { const valuesArray = value.split(DS_DATE_PICKER_SEPARATOR); @@ -22,23 +22,15 @@ export class DateFieldParser extends FieldParser { for (let i = 0; i < valuesArray.length; i++) { const len = i === 0 ? 4 : 2; if (valuesArray[i].length !== len) { - malformedData = true; + malformedDate = true; } } } - - if (malformedData) { - // TODO Set error message - // const errorMessage = 'The stored date is not compliant'; - // dateModel.validators = Object.assign({}, dateModel.validators, {malformedDate: null}); - // dateModel.errorMessages = Object.assign({}, dateModel.errorMessages, {malformedDate: errorMessage}); - - // this.formService.addErrorToField(this.group.get(this.model.id), this.model, errorMessage) - } } } const dateModel = new DynamicDsDatePickerModel(inputDateModelConfig); + dateModel.malformedDate = malformedDate; return dateModel; } } diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index 9845f1772d..409726ebe9 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -185,7 +185,7 @@ export abstract class FieldParser { controlModel.placeholder = this.configData.label; if (this.configData.mandatory && setErrors) { - this.setErrors(controlModel); + this.markAsRequired(controlModel); } // Available Languages @@ -196,7 +196,7 @@ export abstract class FieldParser { return controlModel; } - protected setErrors(controlModel) { + protected markAsRequired(controlModel) { controlModel.required = true; controlModel.validators = Object.assign({}, controlModel.validators, {required: null}); controlModel.errorMessages = Object.assign( diff --git a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts index e565ea881d..a72dacd909 100644 --- a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts +++ b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts @@ -1,18 +1,21 @@ import { FormFieldModel } from '../models/form-field.model'; import { NameFieldParser } from './name-field-parser'; import { DynamicConcatModel } from '../ds-dynamic-form-ui/models/ds-dynamic-concat.model'; +import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; describe('NameFieldParser test suite', () => { let field1: FormFieldModel; let field2: FormFieldModel; let field3: FormFieldModel; + let initFormValues: any = {}; - const initFormValues = {}; const readOnly = false; beforeEach(() => { field1 = { - input: {type: 'name'}, + input: { + type: 'name' + }, label: 'Name', mandatory: 'false', repeatable: false, @@ -82,4 +85,17 @@ describe('NameFieldParser test suite', () => { expect((fieldModel as DynamicConcatModel).separator).toBe(', '); }); + it('should set init value properly', () => { + initFormValues = { + name: [new FormFieldMetadataValueObject('test, name')], + }; + const expectedValue = new FormFieldMetadataValueObject('test, name'); + + const parser = new NameFieldParser(field1, initFormValues, readOnly); + + const fieldModel = parser.parse(); + + expect(fieldModel.value).toEqual(expectedValue); + }); + }); diff --git a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts index 21889a1b53..af228ea488 100644 --- a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts +++ b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts @@ -1,11 +1,12 @@ import { FormFieldModel } from '../models/form-field.model'; import { DynamicConcatModel } from '../ds-dynamic-form-ui/models/ds-dynamic-concat.model'; import { SeriesFieldParser } from './series-field-parser'; +import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; describe('SeriesFieldParser test suite', () => { let field: FormFieldModel; + let initFormValues: any = {}; - const initFormValues = {}; const readOnly = false; beforeEach(() => { @@ -47,4 +48,17 @@ describe('SeriesFieldParser test suite', () => { expect((fieldModel as DynamicConcatModel).separator).toBe('; '); }); + it('should set init value properly', () => { + initFormValues = { + series: [new FormFieldMetadataValueObject('test; series')], + }; + const expectedValue = new FormFieldMetadataValueObject('test; series'); + + const parser = new SeriesFieldParser(field, initFormValues, readOnly); + + const fieldModel = parser.parse(); + + expect(fieldModel.value).toEqual(expectedValue); + }); + }); diff --git a/src/app/shared/form/form.service.spec.ts b/src/app/shared/form/form.service.spec.ts index a3f8e09e84..dcea3b6cf2 100644 --- a/src/app/shared/form/form.service.spec.ts +++ b/src/app/shared/form/form.service.spec.ts @@ -157,9 +157,8 @@ describe('FormService test suite', () => { model = builderService.findById('title', formModel); service.addErrorToField(control, model, 'error.required'); - errorKeys = Object.keys(control.errors); - service.removeErrorFromField(control, model, errorKeys[0]); + service.removeErrorFromField(control, model, 'error.required'); expect(errorKeys.length).toBe(1); diff --git a/src/app/shared/object.util.spec.ts b/src/app/shared/object.util.spec.ts new file mode 100644 index 0000000000..9f0e687dc1 --- /dev/null +++ b/src/app/shared/object.util.spec.ts @@ -0,0 +1,87 @@ +import { deleteProperty, difference, isObjectEmpty } from './object.util'; + +describe('Object Utils', () => { + let object: any = {}; + let anotherObject: any = {}; + let objectExpected: any = {}; + + describe('deleteProperty', () => { + it('should return object without property \'a\'', () => { + object = {a: 'a', b: 'b'}; + objectExpected = {b: 'b'}; + expect(deleteProperty(object, 'a')).toEqual(objectExpected); + }); + + it('should return same object', () => { + object = {a: 'a', b: 'b'}; + expect(deleteProperty(object, 'c')).toEqual(object); + }); + + }); + + describe('isObjectEmpty', () => { + + it('should return true when object is empty', () => { + object = {}; + expect(isObjectEmpty(object)).toBe(true); + }); + + it('should return true when object has a null property', () => { + object = {a: null}; + expect(isObjectEmpty(object)).toBe(true); + }); + + it('should return true when object property has an empty array as value', () => { + object = {a: []}; + expect(isObjectEmpty(object)).toBe(true); + }); + + it('should return true when object property has an empty object as value', () => { + object = {a: {}}; + expect(isObjectEmpty(object)).toBe(true); + }); + + it('should return false when object is not empty', () => { + object = {a: 'a', b: 'b'}; + expect(isObjectEmpty(object)).toBe(false); + }); + + it('should return false when object has at least a valued property', () => { + object = {a: [], b: 'b'}; + expect(isObjectEmpty(object)).toBe(false); + }); + + }); + + describe('difference', () => { + + it('should return an empty object', () => { + object = {}; + anotherObject = {}; + objectExpected = {}; + expect(difference(object, anotherObject)).toEqual(objectExpected); + }); + + it('should return object properties that are not included in the base object', () => { + object = {a: 'a', b: 'b'}; + anotherObject = {a: 'a'}; + objectExpected = {b: 'b'}; + expect(difference(object, anotherObject)).toEqual(objectExpected); + }); + + it('should not return object properties that are included only in the base object', () => { + object = {a: 'a'}; + anotherObject = {a: 'a', b: 'b'}; + objectExpected = {}; + expect(difference(object, anotherObject)).toEqual(objectExpected); + }); + + it('should not return empty object properties that are not included in the base object', () => { + object = {a: 'a', b: {}}; + anotherObject = {a: 'a'}; + objectExpected = {}; + expect(difference(object, anotherObject)).toEqual(objectExpected); + }); + + }); +}); diff --git a/src/app/shared/object.util.ts b/src/app/shared/object.util.ts index 50680d6d32..1dccd2070a 100644 --- a/src/app/shared/object.util.ts +++ b/src/app/shared/object.util.ts @@ -10,14 +10,13 @@ export function deleteProperty(object, key): object { } /** - * Returns true if the passed value is null or undefined. - * hasNoValue(); // true - * hasNoValue(null); // true - * hasNoValue(undefined); // true - * hasNoValue(''); // false - * hasNoValue({}); // false - * hasNoValue([]); // false - * hasNoValue(function() {}); // false + * Returns true if the passed object is empty or has only empty property. + * isObjectEmpty({}); // true + * isObjectEmpty({a: null}); // true + * isObjectEmpty({a: []}); // true + * isObjectEmpty({a: [], b: {}); // true + * isObjectEmpty({a: 'a', b: 'b'}); // false + * isObjectEmpty({a: [], b: 'b'}); // false */ export function isObjectEmpty(obj: any): boolean { const objectType = typeof obj; @@ -37,6 +36,13 @@ export function isObjectEmpty(obj: any): boolean { } } +/** + * Returns diff from the base object. + * difference({}, {}); // {} + * difference({a: 'a', b: 'b'}, {a: 'a'}); // {b: 'b'} + * difference({a: 'a', b: {}}, {a: 'a'}); // {} + * difference({a: 'a'}, {a: 'a', b: 'b'}); // {} + */ export function difference(object, base) { const changes = (o, b) => { return transform(o, (result, value, key) => {