Skip to content

Commit

Permalink
Determine from ontology if a property is read-only (#233)
Browse files Browse the repository at this point in the history
* feature (viewer): determine from ontology if a property is read-only

* tests (viewer): move service related tests into service specs

* tests (viewer): adapt tests since system props cannot be added by the user

* test (viewer): add test for standoff link value to value service isReadonly method

* tests (viewer): add tests for method PropertyViewComponent.addValueIsAllowed

* tests (viewer): add spy for value service to display edit comp. specs

* tests (viewer): remove fdescribe
  • Loading branch information
tobiasschweizer committed Nov 24, 2020
1 parent 8616908 commit c524912
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 71 deletions.
Expand Up @@ -31,6 +31,7 @@ import {
ReadTimeValue,
ReadUriValue,
ReadValue,
ResourcePropertyDefinition,
UpdateIntValue,
UpdateResource,
UpdateValue,
Expand Down Expand Up @@ -348,6 +349,8 @@ describe('DisplayEditComponent', () => {

const userServiceSpy = jasmine.createSpyObj('UserService', ['getUser']);

const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValueTypeOrClass', 'isReadOnly']);

TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
Expand Down Expand Up @@ -394,6 +397,10 @@ describe('DisplayEditComponent', () => {
{
provide: MatDialogRef,
useValue: {}
},
{
provide: ValueService,
useValue: valueServiceSpy
}
]
})
Expand All @@ -418,6 +425,26 @@ describe('DisplayEditComponent', () => {
}
);

const valueServiceSpy = TestBed.inject(ValueService);

// actual ValueService
// mocking the service's behaviour would duplicate the actual implementation
const valueService = new ValueService();

// spy for getValueTypeOrClass
(valueServiceSpy as jasmine.SpyObj<ValueService>).getValueTypeOrClass.and.callFake(
(value: ReadValue) => {
return valueService.getValueTypeOrClass(value);
}
);

// spy for isReadOnly
(valueServiceSpy as jasmine.SpyObj<ValueService>).isReadOnly.and.callFake(
(typeOrClass: string, value: ReadValue, propDef: ResourcePropertyDefinition) => {
return valueService.isReadOnly(typeOrClass, value, propDef);
}
);

testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
Expand All @@ -429,12 +456,33 @@ describe('DisplayEditComponent', () => {

it('should choose the apt component for a plain text value in the template', () => {

const valueServiceSpy = TestBed.inject(ValueService);

testHostComponent.assignValue('http://0.0.0.0:3333/ontology/0001/anything/v2#hasText');
testHostFixture.detectChanges();

expect(testHostComponent.displayEditValueComponent.displayValueComponent instanceof TestTextValueAsStringComponent).toBe(true);
expect(testHostComponent.displayEditValueComponent.displayValueComponent.displayValue instanceof ReadTextValueAsString).toBe(true);
expect(testHostComponent.displayEditValueComponent.displayValueComponent.mode).toEqual('read');

// make sure the value service has been called as expected on initialization

expect(valueServiceSpy.getValueTypeOrClass).toHaveBeenCalledTimes(1);
expect(valueServiceSpy.getValueTypeOrClass).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'http://api.knora.org/ontology/knora-api/v2#TextValue'
}));

expect(valueServiceSpy.isReadOnly).toHaveBeenCalledTimes(1);
expect(valueServiceSpy.isReadOnly).toHaveBeenCalledWith(
'ReadTextValueAsString',
jasmine.objectContaining({
type: 'http://api.knora.org/ontology/knora-api/v2#TextValue'
}),
jasmine.objectContaining({
id: 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasText'
})
);

});

it('should choose the apt component for an XML value in the template', () => {
Expand Down Expand Up @@ -517,6 +565,7 @@ describe('DisplayEditComponent', () => {

const inputVal: ReadTextValueAsHtml = new ReadTextValueAsHtml();

inputVal.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasRichtext';
inputVal.hasPermissions = 'CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser';
inputVal.userHasPermission = 'CR';
inputVal.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';
Expand Down Expand Up @@ -698,54 +747,6 @@ describe('DisplayEditComponent', () => {

});

describe('methods getValueType and isReadOnly', () => {
let hostCompDe;
let displayEditComponentDe;
let valueService;

beforeEach(() => {
testHostComponent.assignValue('http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger');
testHostFixture.detectChanges();

expect(testHostComponent.displayEditValueComponent).toBeTruthy();

hostCompDe = testHostFixture.debugElement;
displayEditComponentDe = hostCompDe.query(By.directive(DisplayEditComponent));

valueService = TestBed.inject(ValueService);

});

it('should return the type of a integer value as not readonly', () => {
expect(valueService.getValueTypeOrClass(testHostComponent.displayEditValueComponent.displayValue)).toEqual(Constants.IntValue);

expect(valueService.isReadOnly(Constants.IntValue, testHostComponent.displayEditValueComponent.displayValue)).toBe(false);
});

it('should return the class of a html text value as readonly', () => {

const htmlTextVal = new ReadTextValueAsHtml();
htmlTextVal.type = Constants.TextValue;

expect(valueService.getValueTypeOrClass(htmlTextVal)).toEqual('ReadTextValueAsHtml');

expect(valueService.isReadOnly('ReadTextValueAsHtml', htmlTextVal)).toBe(true);

});

it('should return the type of a plain text value as not readonly', () => {

const plainTextVal = new ReadTextValueAsString();
plainTextVal.type = Constants.TextValue;

expect(valueService.getValueTypeOrClass(plainTextVal)).toEqual('ReadTextValueAsString');

expect(valueService.isReadOnly('ReadTextValueAsString', plainTextVal)).toBe(false);

});

});

describe('change from display to edit mode', () => {
let hostCompDe;
let displayEditComponentDe;
Expand Down Expand Up @@ -928,6 +929,7 @@ describe('DisplayEditComponent', () => {
it('should not display the edit button', () => {
const inputVal: ReadTextValueAsHtml = new ReadTextValueAsHtml();

inputVal.property = 'http://0.0.0.0:3333/ontology/0001/anything/v2#hasRichtext';
inputVal.hasPermissions = 'CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser';
inputVal.userHasPermission = 'CR';
inputVal.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';
Expand Down
Expand Up @@ -12,6 +12,7 @@ import {
ReadResource,
ReadUser,
ReadValue,
ResourcePropertyDefinition,
UpdateResource,
UpdateValue,
WriteValueResponse
Expand Down Expand Up @@ -114,7 +115,7 @@ export class DisplayEditComponent implements OnInit {
private _valueOperationEventService: ValueOperationEventService,
private _dialog: MatDialog,
private _userService: UserService,
private _valueService: ValueService,) {
private _valueService: ValueService) {
}

ngOnInit() {
Expand All @@ -134,7 +135,17 @@ export class DisplayEditComponent implements OnInit {

this.valueTypeOrClass = this._valueService.getValueTypeOrClass(this.displayValue);

this.readOnlyValue = this._valueService.isReadOnly(this.valueTypeOrClass, this.displayValue);
// get the resource property definition
const resPropDef = this.parentResource.entityInfo.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter(
(propDef: ResourcePropertyDefinition) => propDef.id === this.displayValue.property
);

if (resPropDef.length !== 1) {
// this should never happen because we always have the property info for the given value
throw new Error('Resource Property Definition could not be found: ' + this.displayValue.property);
}

this.readOnlyValue = this._valueService.isReadOnly(this.valueTypeOrClass, this.displayValue, resPropDef[0]);

// prevent getting info about system user (standoff link values are managed by the system)
if (this.displayValue.attachedToUser !== 'http://www.knora.org/ontology/knora-admin#SystemUser') {
Expand Down
73 changes: 63 additions & 10 deletions projects/dsp-ui/src/lib/viewer/services/value.service.spec.ts
Expand Up @@ -3,10 +3,11 @@ import { TestBed } from '@angular/core/testing';
import {
KnoraDate, KnoraPeriod,
MockResource, ReadDateValue,
ReadIntValue,
ReadIntValue, ReadLinkValue,
ReadTextValueAsHtml,
ReadTextValueAsString,
ReadTextValueAsXml
ReadTextValueAsXml,
ResourcePropertyDefinition
} from '@dasch-swiss/dsp-js';
import { ValueService } from './value.service';

Expand Down Expand Up @@ -114,34 +115,74 @@ describe('ValueService', () => {

describe('isReadOnly', () => {

it('should not mark a ReadIntValue as ReadOnly', () => {

const readIntValue = new ReadIntValue();
readIntValue.type = 'http://api.knora.org/ontology/knora-api/v2#IntValue';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(readIntValue);
expect(service.isReadOnly(valueClass, readIntValue, resPropDef)).toBeFalsy();

});

it('should not mark ReadTextValueAsString as ReadOnly', () => {
const readTextValueAsString = new ReadTextValueAsString();
readTextValueAsString.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(readTextValueAsString);
expect(service.isReadOnly(valueClass, readTextValueAsString)).toBeFalsy();
expect(service.isReadOnly(valueClass, readTextValueAsString, resPropDef)).toBeFalsy();
});

it('should mark ReadTextValueAsHtml as ReadOnly', () => {
const readTextValueAsHtml = new ReadTextValueAsHtml();
readTextValueAsHtml.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(readTextValueAsHtml);
expect(service.isReadOnly(valueClass, readTextValueAsHtml)).toBeTruthy();
expect(service.isReadOnly(valueClass, readTextValueAsHtml, resPropDef)).toBeTruthy();
});

it('should not mark ReadTextValueAsXml with standard mapping as ReadOnly', () => {
const readTextValueAsXml = new ReadTextValueAsXml();
readTextValueAsXml.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';
readTextValueAsXml.mapping = 'http://rdfh.ch/standoff/mappings/StandardMapping';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(readTextValueAsXml);
expect(service.isReadOnly(valueClass, readTextValueAsXml)).toBeFalsy();
expect(service.isReadOnly(valueClass, readTextValueAsXml, resPropDef)).toBeFalsy();
});

it('should mark ReadTextValueAsXml with custom mapping as ReadOnly', () => {
const readTextValueAsXml = new ReadTextValueAsXml();
readTextValueAsXml.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue';
readTextValueAsXml.mapping = 'http://rdfh.ch/standoff/mappings/CustomMapping';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(readTextValueAsXml);
expect(service.isReadOnly(valueClass, readTextValueAsXml)).toBeTruthy();
expect(service.isReadOnly(valueClass, readTextValueAsXml, resPropDef)).toBeTruthy();
});

it('should mark a standoff link value as ReadOnly', () => {
const readStandoffLinkValue = new ReadLinkValue();
readStandoffLinkValue.type = 'http://api.knora.org/ontology/knora-api/v2#LinkValue';

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = false;

const valueClass = service.getValueTypeOrClass(readStandoffLinkValue);
expect(service.isReadOnly(valueClass, readStandoffLinkValue, resPropDef)).toBeTruthy();
});

it('should mark ReadDateValue with unsupported era as ReadOnly', done => {
Expand All @@ -152,8 +193,11 @@ describe('ValueService', () => {

date.date = new KnoraDate('GREGORIAN', 'BCE', 2019, 5, 13);

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(date);
expect(service.isReadOnly(valueClass, date)).toBeTruthy();
expect(service.isReadOnly(valueClass, date, resPropDef)).toBeTruthy();

done();

Expand All @@ -169,8 +213,11 @@ describe('ValueService', () => {

date.date = new KnoraDate('GREGORIAN', 'CE', 2019, 5, 13);

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(date);
expect(service.isReadOnly(valueClass, date)).toBeFalsy();
expect(service.isReadOnly(valueClass, date, resPropDef)).toBeFalsy();

done();

Expand All @@ -186,8 +233,11 @@ describe('ValueService', () => {

date.date = new KnoraDate('GREGORIAN', 'CE', 2019, 5);

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(date);
expect(service.isReadOnly(valueClass, date)).toBeTruthy();
expect(service.isReadOnly(valueClass, date, resPropDef)).toBeTruthy();

done();

Expand All @@ -203,8 +253,11 @@ describe('ValueService', () => {

date.date = new KnoraDate('GREGORIAN', 'CE', 2019, 5, 1);

const resPropDef = new ResourcePropertyDefinition();
resPropDef.isEditable = true;

const valueClass = service.getValueTypeOrClass(date);
expect(service.isReadOnly(valueClass, date)).toBeFalsy();
expect(service.isReadOnly(valueClass, date, resPropDef)).toBeFalsy();

done();

Expand Down
10 changes: 9 additions & 1 deletion projects/dsp-ui/src/lib/viewer/services/value.service.ts
Expand Up @@ -133,8 +133,16 @@ export class ValueService {
*
* @param valueTypeOrClass the type or class of the given value.
* @param value the given value.
* @param propertyDef the given values property definition.
*/
isReadOnly(valueTypeOrClass: string, value: ReadValue): boolean {
isReadOnly(valueTypeOrClass: string, value: ReadValue, propertyDef: ResourcePropertyDefinition): boolean {

// if value is not editable in general from the ontology,
// flag it as read-only
if (!propertyDef.isEditable) {
return true;
}

// only texts complying with the standard mapping can be edited using CKEditor.
const xmlValueNonStandardMapping
= valueTypeOrClass === this._readTextValueAsXml
Expand Down

0 comments on commit c524912

Please sign in to comment.