diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts index 3a5b4a167..2fe94ecf4 100644 --- a/e2e/src/app.e2e-spec.ts +++ b/e2e/src/app.e2e-spec.ts @@ -37,7 +37,7 @@ describe('Test App', () => { await page.navigateTo('modify'); - const valueEleComp: WebElement = await page.getComponentBySelector('dsp-int-value', timeout); + let valueEleComp: WebElement = await page.getComponentBySelector('dsp-int-value', timeout); const displayEditComp: WebElement = await page.getDisplayEditComponentFromValueComponent(valueEleComp); @@ -64,6 +64,10 @@ describe('Test App', () => { await browser.wait(EC.presenceOf(element(by.css('.rm-value'))), timeout, 'Wait for read value to be visible.'); + // a new element is created in the DOM when we update a value + // therefore we need to get a reference to the element again, otherwise it will be stale + valueEleComp = await page.getComponentBySelector('dsp-int-value', timeout); + const readEle = await page.getReadValueFieldFromValueComponent(valueEleComp); expect(await readEle.getText()).toEqual('3'); diff --git a/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.spec.ts b/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.spec.ts index e76783d07..5b0ff950c 100644 --- a/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.spec.ts @@ -19,7 +19,7 @@ import { import { of, throwError } from 'rxjs'; import { AjaxError } from 'rxjs/ajax'; import { DspApiConnectionToken } from '../../../core'; -import { EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; +import { AddedEventValue, EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; import { AddValueComponent } from './add-value.component'; @@ -283,7 +283,7 @@ describe('AddValueComponent', () => { expect(valuesSpy.v2.values.getValue).toHaveBeenCalledWith(testHostComponent.readResource.id, 'uuid'); expect(valueEventSpy.emit).toHaveBeenCalledTimes(1); - expect(valueEventSpy.emit).toHaveBeenCalledWith(new EmitEvent(Events.ValueAdded, newReadValue)); + expect(valueEventSpy.emit).toHaveBeenCalledWith(new EmitEvent(Events.ValueAdded, new AddedEventValue(newReadValue))); }); diff --git a/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.ts b/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.ts index a4624ac79..60ef1a35c 100644 --- a/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.ts +++ b/projects/dsp-ui/src/lib/viewer/operations/add-value/add-value.component.ts @@ -18,7 +18,7 @@ import { } from '@dasch-swiss/dsp-js'; import { mergeMap } from 'rxjs/operators'; import { DspApiConnectionToken } from '../../../core/core.module'; -import { EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; +import { AddedEventValue, EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; import { ValueTypeService } from '../../services/value-type.service'; import { BaseValueComponent } from '../../values/base-value.component'; @@ -110,7 +110,8 @@ export class AddValueComponent implements OnInit, AfterViewInit { // emit a ValueAdded event to the listeners in: // property-view component to hide the add value form // resource-view component to trigger a refresh of the resource - this._valueOperationEventService.emit(new EmitEvent(Events.ValueAdded, res2.getValues(updateRes.property)[0])); + this._valueOperationEventService.emit( + new EmitEvent(Events.ValueAdded, new AddedEventValue(res2.getValues(updateRes.property)[0]))); // hide the progress indicator this.submittingValue = false; diff --git a/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts b/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts index 9f75184e7..a45bb8077 100644 --- a/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.spec.ts @@ -39,12 +39,16 @@ import { import { of, throwError } from 'rxjs'; import { AjaxError } from 'rxjs/ajax'; import { DspApiConnectionToken } from '../../../core'; -import { EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; +import { + DeletedEventValue, + EmitEvent, + Events, + UpdatedEventValues, + ValueOperationEventService +} from '../../services/value-operation-event.service'; import { ValueTypeService } from '../../services/value-type.service'; import { DisplayEditComponent } from './display-edit.component'; - - @Component({ selector: `dsp-text-value-as-string`, template: `` @@ -578,79 +582,88 @@ describe('DisplayEditComponent', () => { it('should save a new version of a value', () => { - const valuesSpy = TestBed.inject(DspApiConnectionToken); + const valueEventSpy = TestBed.inject(ValueOperationEventService); - (valuesSpy.v2.values as jasmine.SpyObj).updateValue.and.callFake( - () => { + const valuesSpy = TestBed.inject(DspApiConnectionToken); - const response = new WriteValueResponse(); + (valueEventSpy as jasmine.SpyObj).emit.and.stub(); - response.id = 'newID'; - response.type = 'type'; - response.uuid = 'uuid'; + (valuesSpy.v2.values as jasmine.SpyObj).updateValue.and.callFake( + () => { - return of(response); - } - ); + const response = new WriteValueResponse(); - (valuesSpy.v2.values as jasmine.SpyObj).getValue.and.callFake( - () => { + response.id = 'newID'; + response.type = 'type'; + response.uuid = 'uuid'; - const updatedVal = new ReadIntValue(); + return of(response); + } + ); - updatedVal.id = 'newID'; - updatedVal.int = 1; + (valuesSpy.v2.values as jasmine.SpyObj).getValue.and.callFake( + () => { - const resource = new ReadResource(); + const updatedVal = new ReadIntValue(); - resource.properties = { - 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger': [updatedVal] - }; + updatedVal.id = 'newID'; + updatedVal.int = 1; - return of(resource); - } - ); + const resource = new ReadResource(); - testHostComponent.displayEditValueComponent.canModify = true; - testHostComponent.displayEditValueComponent.editModeActive = true; - testHostComponent.displayEditValueComponent.mode = 'update'; + resource.properties = { + 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger': [updatedVal] + }; - testHostComponent.displayEditValueComponent.displayValueComponent.form.controls.test.clearValidators(); - testHostComponent.displayEditValueComponent.displayValueComponent.form.controls.test.updateValueAndValidity(); + return of(resource); + } + ); - testHostFixture.detectChanges(); + testHostComponent.displayEditValueComponent.canModify = true; + testHostComponent.displayEditValueComponent.editModeActive = true; + testHostComponent.displayEditValueComponent.mode = 'update'; - const saveButtonDebugElement = displayEditComponentDe.query(By.css('button.save')); - const saveButtonNativeElement = saveButtonDebugElement.nativeElement; + testHostComponent.displayEditValueComponent.displayValueComponent.form.controls.test.clearValidators(); + testHostComponent.displayEditValueComponent.displayValueComponent.form.controls.test.updateValueAndValidity(); - expect(saveButtonNativeElement.disabled).toBeFalsy(); + testHostFixture.detectChanges(); - saveButtonNativeElement.click(); + const saveButtonDebugElement = displayEditComponentDe.query(By.css('button.save')); + const saveButtonNativeElement = saveButtonDebugElement.nativeElement; - testHostFixture.detectChanges(); + expect(saveButtonNativeElement.disabled).toBeFalsy(); - const expectedUpdateResource = new UpdateResource(); + saveButtonNativeElement.click(); - expectedUpdateResource.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw'; - expectedUpdateResource.type = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'; - expectedUpdateResource.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; + testHostFixture.detectChanges(); - const expectedUpdateVal = new UpdateIntValue(); - expectedUpdateVal.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw/values/dJ1ES8QTQNepFKF5-EAqdg'; - expectedUpdateVal.int = 1; + const expectedUpdateResource = new UpdateResource(); - expectedUpdateResource.value = expectedUpdateVal; + expectedUpdateResource.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw'; + expectedUpdateResource.type = 'http://0.0.0.0:3333/ontology/0001/anything/v2#Thing'; + expectedUpdateResource.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; - expect(valuesSpy.v2.values.updateValue).toHaveBeenCalledWith(expectedUpdateResource); - expect(valuesSpy.v2.values.updateValue).toHaveBeenCalledTimes(1); + const expectedUpdateVal = new UpdateIntValue(); + expectedUpdateVal.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw/values/dJ1ES8QTQNepFKF5-EAqdg'; + expectedUpdateVal.int = 1; + + expectedUpdateResource.value = expectedUpdateVal; + + expect(valuesSpy.v2.values.updateValue).toHaveBeenCalledWith(expectedUpdateResource); + expect(valuesSpy.v2.values.updateValue).toHaveBeenCalledTimes(1); + + expect(valueEventSpy.emit).toHaveBeenCalledTimes(1); + expect(valueEventSpy.emit).toHaveBeenCalledWith(new EmitEvent(Events.ValueUpdated, new UpdatedEventValues( + testHostComponent.readValue, testHostComponent.displayEditValueComponent.displayValue))); + + expect(valuesSpy.v2.values.getValue).toHaveBeenCalledTimes(1); + expect(valuesSpy.v2.values.getValue).toHaveBeenCalledWith(testHostComponent.readResource.id, 'uuid'); + + expect(testHostComponent.displayEditValueComponent.displayValue.id).toEqual('newID'); + expect(testHostComponent.displayEditValueComponent.displayValueComponent.displayValue.id).toEqual('newID'); + expect(testHostComponent.displayEditValueComponent.mode).toEqual('read'); - expect(valuesSpy.v2.values.getValue).toHaveBeenCalledTimes(1); - expect(valuesSpy.v2.values.getValue).toHaveBeenCalledWith(testHostComponent.readResource.id, - 'uuid'); - expect(testHostComponent.displayEditValueComponent.displayValue.id).toEqual('newID'); - expect(testHostComponent.displayEditValueComponent.displayValueComponent.displayValue.id).toEqual('newID'); - expect(testHostComponent.displayEditValueComponent.mode).toEqual('read'); }); @@ -743,8 +756,6 @@ describe('DisplayEditComponent', () => { }); it('should display a comment button if the value has a comment', () => { - //console.log(testHostComponent.displayEditValueComponent.displayValueComponent.displayValue); - expect(testHostComponent.displayEditValueComponent.editModeActive).toBeFalsy(); expect(testHostComponent.displayEditValueComponent.shouldShowCommentToggle).toBeTruthy() @@ -888,15 +899,6 @@ describe('DisplayEditComponent', () => { } ); - // const deleteButtonDebugElement = displayEditComponentDe.query(By.css('button.delete')); - // const deleteButtonNativeElement = deleteButtonDebugElement.nativeElement; - - // expect(deleteButtonNativeElement.disabled).toBeFalsy(); - - // deleteButtonNativeElement.click(); - - // testHostFixture.detectChanges(); - const deleteButton = await rootLoader.getHarness(MatButtonHarness.with({selector: '.delete'})); await deleteButton.click(); @@ -925,7 +927,7 @@ describe('DisplayEditComponent', () => { expect(valuesSpy.v2.values.deleteValue).toHaveBeenCalledTimes(1); expect(valueEventSpy.emit).toHaveBeenCalledTimes(1); - expect(valueEventSpy.emit).toHaveBeenCalledWith(new EmitEvent(Events.ValueDeleted, deleteVal)); + expect(valueEventSpy.emit).toHaveBeenCalledWith(new EmitEvent(Events.ValueDeleted, new DeletedEventValue(deleteVal))); }); }); diff --git a/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts b/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts index b7caa7c39..292be801b 100644 --- a/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts +++ b/projects/dsp-ui/src/lib/viewer/operations/display-edit/display-edit.component.ts @@ -20,7 +20,13 @@ import { ConfirmationDialogData } from '../../../action/components/confirmation-dialog/confirmation-dialog.component'; import { DspApiConnectionToken } from '../../../core/core.module'; -import { EmitEvent, Events, ValueOperationEventService } from '../../services/value-operation-event.service'; +import { + DeletedEventValue, + EmitEvent, + Events, + UpdatedEventValues, + ValueOperationEventService +} from '../../services/value-operation-event.service'; import { ValueTypeService } from '../../services/value-type.service'; import { BaseValueComponent } from '../../values/base-value.component'; @@ -152,6 +158,10 @@ export class DisplayEditComponent implements OnInit { }) ).subscribe( (res2: ReadResource) => { + this._valueOperationEventService.emit( + new EmitEvent(Events.ValueUpdated, new UpdatedEventValues( + this.displayValue, res2.getValues(this.displayValue.property)[0]))); + this.displayValue = res2.getValues(this.displayValue.property)[0]; this.mode = 'read'; @@ -218,7 +228,7 @@ export class DisplayEditComponent implements OnInit { this._dspApiConnection.v2.values.deleteValue(updateRes as UpdateResource).pipe( mergeMap((res: DeleteValueResponse) => { // emit a ValueDeleted event to the listeners in resource-view component to trigger an update of the UI - this._valueOperationEventService.emit(new EmitEvent(Events.ValueDeleted, deleteVal)); + this._valueOperationEventService.emit(new EmitEvent(Events.ValueDeleted, new DeletedEventValue(deleteVal))); return res.result; })).subscribe(); } diff --git a/projects/dsp-ui/src/lib/viewer/services/value-operation-event.service.ts b/projects/dsp-ui/src/lib/viewer/services/value-operation-event.service.ts index 6c7ed7247..29150b21f 100644 --- a/projects/dsp-ui/src/lib/viewer/services/value-operation-event.service.ts +++ b/projects/dsp-ui/src/lib/viewer/services/value-operation-event.service.ts @@ -1,4 +1,4 @@ -import { BaseValue } from '@dasch-swiss/dsp-js'; +import { DeleteValue, ReadValue } from '@dasch-swiss/dsp-js'; import { Subject, Subscription } from 'rxjs'; import { filter, map } from 'rxjs/operators'; @@ -17,7 +17,7 @@ export class ValueOperationEventService { // Used in the listening component. // i.e. this.valueOperationEventSubscription = this._valueOperationEventService.on(Events.ValueAdded, () => doSomething()); - on(event: Events, action: (newValue) => void): Subscription { + on(event: Events, action: (value: EventValue) => void): Subscription { return this._subject$ .pipe( // Filter down based on event name to any events that are emitted out of the subject from the emit method below. @@ -28,18 +28,39 @@ export class ValueOperationEventService { } // Used in the emitting component. - // i.e. this.valueOperationEventService.emit(new EmitEvent(Events.ValueAdded)); + // i.e. this.valueOperationEventService.emit(new EmitEvent(Events.ValueAdded, new EventValues(new ReadValue())); emit(event: EmitEvent) { this._subject$.next(event); } } export class EmitEvent { - constructor(public name: any, public value?: BaseValue) { } + constructor(public name: Events, public value?: EventValue) { } } // Possible events that can be emitted. export enum Events { ValueAdded, - ValueDeleted + ValueDeleted, + ValueUpdated +} + +export abstract class EventValue { } + +export class AddedEventValue extends EventValue { + constructor(public addedValue: ReadValue) { + super(); + } +} + +export class UpdatedEventValues extends EventValue { + constructor(public currentValue: ReadValue, public updatedValue: ReadValue) { + super(); + } +} + +export class DeletedEventValue extends EventValue { + constructor(public deletedValue: DeleteValue) { + super(); + } } diff --git a/projects/dsp-ui/src/lib/viewer/services/value-type.service.ts b/projects/dsp-ui/src/lib/viewer/services/value-type.service.ts index 931f0ffa2..a0e75ca59 100644 --- a/projects/dsp-ui/src/lib/viewer/services/value-type.service.ts +++ b/projects/dsp-ui/src/lib/viewer/services/value-type.service.ts @@ -62,6 +62,20 @@ export class ValueTypeService { } } + /** + * Given the ObjectType of a PropertyDefinition, compares it to the type of the type of the provided value. + * Primarily used to check if a TextValue type is equal to one of the readonly strings in this class. + * + * @param objectType PropertyDefinition ObjectType + * @param valueType Value type (ReadValue, DeleteValue, BaseValue, etc.) + */ + compareObjectTypeWithValueType(objectType: string, valueType: string): boolean { + return objectType === this._readTextValueAsString || + objectType === this._readTextValueAsHtml || + objectType === this._readTextValueAsXml || + objectType === valueType; + } + /** * Equality checks with constants below are TEMPORARY until component is implemented. * Used so that the CRUD buttons do not show if a property doesn't have a value component. diff --git a/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.spec.ts b/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.spec.ts index 653a40248..d4f3e2ff8 100644 --- a/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.spec.ts @@ -2,7 +2,15 @@ import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIconModule } from '@angular/material/icon'; import { By } from '@angular/platform-browser'; -import { DeleteValue, MockResource, PropertyDefinition, ReadIntValue, ReadResource, ResourcesEndpointV2 } from '@dasch-swiss/dsp-js'; +import { + DeleteValue, + MockResource, + PropertyDefinition, + ReadIntValue, + ReadResource, + ReadTextValueAsString, + ResourcesEndpointV2 +} from '@dasch-swiss/dsp-js'; import { Subscription } from 'rxjs'; import { map } from 'rxjs/internal/operators/map'; import { DspApiConnectionToken } from '../../../core'; @@ -39,19 +47,21 @@ class TestParentComponent implements OnInit, OnDestroy { resourceIri = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw'; - voeSubscription: Subscription; + voeSubscriptions: Subscription[] = []; myNum = 0; constructor(public _valueOperationEventService: ValueOperationEventService) { } ngOnInit() { - this.voeSubscription = this._valueOperationEventService.on(Events.ValueAdded, () => this.myNum += 1); + this.voeSubscriptions.push(this._valueOperationEventService.on(Events.ValueAdded, () => this.myNum = 1)); + this.voeSubscriptions.push(this._valueOperationEventService.on(Events.ValueUpdated, () => this.myNum = 2)); + this.voeSubscriptions.push(this._valueOperationEventService.on(Events.ValueDeleted, () => this.myNum = 3)); } ngOnDestroy() { - if (this.voeSubscription) { - this.voeSubscription.unsubscribe(); + if (this.voeSubscriptions) { + this.voeSubscriptions.forEach(sub => sub.unsubscribe()); } } } @@ -135,21 +145,33 @@ describe('ResourceViewComponent', () => { expect(resSpy.v2.res.getResource).toHaveBeenCalledWith(testHostComponent.resourceIri); }); - it('should trigger the callback when an event is emitted', () => { + it('should trigger the correct callback when an event is emitted', () => { expect(testHostComponent.myNum).toEqual(0); voeService.emit(new EmitEvent(Events.ValueAdded)); expect(testHostComponent.myNum).toEqual(1); + + voeService.emit(new EmitEvent(Events.ValueUpdated)); + + expect(testHostComponent.myNum).toEqual(2); + + voeService.emit(new EmitEvent(Events.ValueDeleted)); + + expect(testHostComponent.myNum).toEqual(3); }); it('should unsubscribe from changes when destroyed', () => { - expect(testHostComponent.voeSubscription.closed).toBe(false); + testHostComponent.voeSubscriptions.forEach(sub => { + expect(sub.closed).toBe(false); + }); testHostFixture.destroy(); - expect(testHostComponent.voeSubscription.closed).toBe(true); + testHostComponent.voeSubscriptions.forEach(sub => { + expect(sub.closed).toBe(true); + }); }); it('should add a value to a property of a resource', () => { @@ -158,7 +180,7 @@ describe('ResourceViewComponent', () => { newReadIntValue.int = 123; newReadIntValue.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; - testHostComponent.resourceViewComponent.updateResource(newReadIntValue, false); + testHostComponent.resourceViewComponent.addValueToResource(newReadIntValue); const propArrayIntValues = testHostComponent.resourceViewComponent.resPropInfoVals.filter( propInfoValueArray => propInfoValueArray.propDef.id === newReadIntValue.property @@ -169,7 +191,7 @@ describe('ResourceViewComponent', () => { expect((propArrayIntValues[0].values[1] as ReadIntValue).int).toEqual(123); }); - it('should delete a value from a property of a resource', () => { + it('should delete an int value from a property of a resource', () => { // add new value to be deleted (so that I can ensure the id will be what I expect) const newReadIntValue = new ReadIntValue(); @@ -177,7 +199,7 @@ describe('ResourceViewComponent', () => { newReadIntValue.int = 123; newReadIntValue.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; - testHostComponent.resourceViewComponent.updateResource(newReadIntValue, false); + testHostComponent.resourceViewComponent.addValueToResource(newReadIntValue); // delete the value const valueToBeDeleted = new DeleteValue(); @@ -185,7 +207,7 @@ describe('ResourceViewComponent', () => { valueToBeDeleted.id = 'myNewReadIntId'; valueToBeDeleted.type = 'http://api.knora.org/ontology/knora-api/v2#IntValue'; - testHostComponent.resourceViewComponent.updateResource(valueToBeDeleted, true); + testHostComponent.resourceViewComponent.deleteValueFromResource(valueToBeDeleted); const propArrayIntValues = testHostComponent.resourceViewComponent.resPropInfoVals.filter( propInfoValueArray => propInfoValueArray.propDef.objectType === valueToBeDeleted.type @@ -196,6 +218,66 @@ describe('ResourceViewComponent', () => { }); + it('should delete a text value from a property of a resource', () => { + // add new value to be deleted (so that I can ensure the id will be what I expect) + const newReadTextValueAsString = new ReadTextValueAsString(); + + newReadTextValueAsString.id = 'myNewReadTextValueAsStringId'; + newReadTextValueAsString.text = 'my text'; + newReadTextValueAsString.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasText'; + + testHostComponent.resourceViewComponent.addValueToResource(newReadTextValueAsString); + + // delete the value + const valueToBeDeleted = new DeleteValue(); + + valueToBeDeleted.id = 'myNewReadTextValueAsStringId'; + valueToBeDeleted.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue'; + + testHostComponent.resourceViewComponent.deleteValueFromResource(valueToBeDeleted); + + const propArrayIntValues = testHostComponent.resourceViewComponent.resPropInfoVals.filter( + propInfoValueArray => propInfoValueArray.propDef.objectType === valueToBeDeleted.type + ); + + // expect there to be one value left after deleting the newly created value + expect(propArrayIntValues[0].values.length).toEqual(1); + + }); + + it('should update a value of a property of a resource', () => { + const newReadIntValue = new ReadIntValue(); + + newReadIntValue.id = 'myNewReadIntId'; + newReadIntValue.int = 123; + newReadIntValue.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; + + testHostComponent.resourceViewComponent.addValueToResource(newReadIntValue); + + let propArrayIntValues = testHostComponent.resourceViewComponent.resPropInfoVals.filter( + propInfoValueArray => propInfoValueArray.propDef.id === newReadIntValue.property + ); + + expect(propArrayIntValues[0].values.length).toEqual(2); + + expect((propArrayIntValues[0].values[1] as ReadIntValue).int).toEqual(123); + + const updateReadIntValue = new ReadIntValue(); + + updateReadIntValue.int = 321; + updateReadIntValue.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger'; + + testHostComponent.resourceViewComponent.updateValueInResource(newReadIntValue, updateReadIntValue); + + propArrayIntValues = testHostComponent.resourceViewComponent.resPropInfoVals.filter( + propInfoValueArray => propInfoValueArray.propDef.id === updateReadIntValue.property + ); + + expect(propArrayIntValues[0].values.length).toEqual(2); + + expect((propArrayIntValues[0].values[1] as ReadIntValue).int).toEqual(321); + }); + // TODO: currently not possible to test copy to clipboard from Material Angular // https://stackoverflow.com/questions/60337742/test-copy-to-clipboard-function diff --git a/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.ts b/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.ts index 01e157d26..42657b5ef 100644 --- a/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.ts +++ b/projects/dsp-ui/src/lib/viewer/views/resource-view/resource-view.component.ts @@ -9,7 +9,6 @@ import { } from '@angular/core'; import { ApiResponseError, - BaseValue, DeleteValue, IHasPropertyWithPropertyDefinition, KnoraApiConnection, @@ -21,7 +20,14 @@ import { } from '@dasch-swiss/dsp-js'; import { Subscription } from 'rxjs'; import { DspApiConnectionToken } from '../../../core/core.module'; -import { Events, ValueOperationEventService } from '../../services/value-operation-event.service'; +import { + AddedEventValue, + DeletedEventValue, + Events, + UpdatedEventValues, + ValueOperationEventService +} from '../../services/value-operation-event.service'; +import { ValueTypeService } from '../../services/value-type.service'; // object of property information from ontology class, properties and property values @@ -71,20 +77,27 @@ export class ResourceViewComponent implements OnInit, OnChanges, OnDestroy { systemPropDefs: SystemPropertyDefinition[] = []; // array of system properties - valueOperationEventSubscription: Subscription; + valueOperationEventSubscriptions: Subscription[] = []; // array of ValueOperationEvent subscriptions constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, - private _valueOperationEventService: ValueOperationEventService) { } + private _valueOperationEventService: ValueOperationEventService, + private _valueTypeService: ValueTypeService) { } ngOnInit() { - // subscribe to the event bus and listen for the ValueAdded event to be emitted - // when a ValueAdded event is emitted, get the resource again to display the newly created value - this.valueOperationEventSubscription = this._valueOperationEventService.on( - Events.ValueAdded, (newValue: ReadValue) => this.updateResource(newValue, false)); + // subscribe to the ValueOperationEventService and listen for an event to be emitted + this.valueOperationEventSubscriptions.push(this._valueOperationEventService.on( + Events.ValueAdded, (newValue: AddedEventValue) => + this.addValueToResource(newValue.addedValue))); + + this.valueOperationEventSubscriptions.push(this._valueOperationEventService.on( + Events.ValueUpdated, (updatedValue: UpdatedEventValues) => + this.updateValueInResource(updatedValue.currentValue, updatedValue.updatedValue))); + + this.valueOperationEventSubscriptions.push(this._valueOperationEventService.on( + Events.ValueDeleted, (deletedValue: DeletedEventValue) => + this.deleteValueFromResource(deletedValue.deletedValue))); - this.valueOperationEventSubscription = this._valueOperationEventService.on( - Events.ValueDeleted, (deletedValue: DeleteValue) => this.updateResource(deletedValue, true)); } ngOnChanges() { @@ -92,9 +105,9 @@ export class ResourceViewComponent implements OnInit, OnChanges, OnDestroy { } ngOnDestroy() { - // unsubscribe from the event bus when component is destroyed - if (this.valueOperationEventSubscription !== undefined) { - this.valueOperationEventSubscription.unsubscribe(); + // unsubscribe from the ValueOperationEventService when component is destroyed + if (this.valueOperationEventSubscriptions !== undefined) { + this.valueOperationEventSubscriptions.forEach(sub => sub.unsubscribe()); } } @@ -134,36 +147,63 @@ export class ResourceViewComponent implements OnInit, OnChanges, OnDestroy { } /** - * Update the UI to reflect updates made to property values. + * Updates the UI in the event of a new value being added to show the new value * - * @param value value to be updated inside propInfoValueArray - * @param isDeletion is the value being removed or added + * @param valueToAdd the value to add to the end of the values array of the filtered property */ - updateResource(value: BaseValue, isDeletion: boolean): void { + addValueToResource(valueToAdd: ReadValue): void { if (this.resPropInfoVals) { - if (!isDeletion) { // add new value - this.resPropInfoVals - .filter( propInfoValueArray => - propInfoValueArray.propDef.id === (value as ReadValue).property) // filter to the correct property - .map( propInfoValue => - propInfoValue.values.push((value as ReadValue))); // push new value to array - } else { // delete value - this.resPropInfoVals - .filter( propInfoValueArray => - propInfoValueArray.propDef.objectType === (value as DeleteValue).type) // filter to the correct type - .map((filteredpropInfoValueArray) => { - let index = -1; // init index to increment and use for the splice - filteredpropInfoValueArray.values.forEach( // loop through each value of the current property - val => { - index += 1; // increment index - if (val.id === (value as DeleteValue).id) { // find the value that was deleted using the value id - filteredpropInfoValueArray.values.splice(index, 1); // remove the value from the values array - } - } - ); - } - ); - } + this.resPropInfoVals + .filter(propInfoValueArray => + propInfoValueArray.propDef.id === valueToAdd.property) // filter to the correct property + .forEach(propInfoValue => + propInfoValue.values.push(valueToAdd)); // push new value to array + } else { + console.error('No properties exist for this resource'); + } + } + + /** + * Updates the UI in the event of an existing value being updated to show the updated value + * + * @param valueToReplace the value to be replaced within the values array of the filtered property + * @param updatedValue the value to replace valueToReplace with + */ + updateValueInResource(valueToReplace: ReadValue, updatedValue: ReadValue): void { + if (this.resPropInfoVals && updatedValue !== null) { + this.resPropInfoVals + .filter(propInfoValueArray => + propInfoValueArray.propDef.id === valueToReplace.property) // filter to the correct property + .forEach(filteredpropInfoValueArray => { + filteredpropInfoValueArray.values.forEach((val, index) => { // loop through each value of the current property + if (val.id === valueToReplace.id) { // find the value that should be updated using the id of valueToReplace + filteredpropInfoValueArray.values[index] = updatedValue; // replace value with the updated value + } + }); + }); + } else { + console.error('No properties exist for this resource'); + } + } + + /** + * Updates the UI in the event of an existing value being deleted + * + * @param valueToDelete the value to remove from the values array of the filtered property + */ + deleteValueFromResource(valueToDelete: DeleteValue): void { + if (this.resPropInfoVals) { + this.resPropInfoVals + .filter(propInfoValueArray => // filter to the correct type + this._valueTypeService.compareObjectTypeWithValueType(propInfoValueArray.propDef.objectType, valueToDelete.type)) + .forEach(filteredpropInfoValueArray => { + filteredpropInfoValueArray.values.forEach((val, index) => { // loop through each value of the current property + if (val.id === valueToDelete.id) { // find the value that was deleted using the id + filteredpropInfoValueArray.values.splice(index, 1); // remove the value from the values array + } + }); + } + ); } else { console.error('No properties exist for this resource'); }