Update specs to match composition policies

This commit is contained in:
Pete Richards 2017-02-21 15:14:35 -08:00
parent 65325b90fd
commit 2a10a2cae2
10 changed files with 124 additions and 117 deletions

View File

@ -54,8 +54,7 @@ define(
AddActionProvider.prototype.getActions = function (actionContext) { AddActionProvider.prototype.getActions = function (actionContext) {
var context = actionContext || {}, var context = actionContext || {},
key = context.key, key = context.key,
destination = context.domainObject, destination = context.domainObject;
self = this;
// We only provide Add actions, and we need a // We only provide Add actions, and we need a
// domain object to serve as the container for the // domain object to serve as the container for the
@ -66,16 +65,16 @@ define(
} }
// Introduce one create action per type // Introduce one create action per type
['timeline', 'activity'].map(function (type) { return ['timeline', 'activity'].map(function (type) {
return new AddAction( return new AddAction(
type, this.typeService.getType(type),
destination, destination,
context, context,
self.$q, this.$q,
self.dialogService, this.dialogService,
self.policyService this.policyService
); );
}); }, this);
}; };
return AddActionProvider; return AddActionProvider;

View File

@ -31,9 +31,7 @@ define(
var mockTypeService, var mockTypeService,
mockDialogService, mockDialogService,
mockPolicyService, mockPolicyService,
mockCreationPolicy, mockTypeMap,
mockCompositionPolicy,
mockPolicyMap = {},
mockTypes, mockTypes,
mockDomainObject, mockDomainObject,
mockQ, mockQ,
@ -55,49 +53,33 @@ define(
); );
mockType.hasFeature.andReturn(true); mockType.hasFeature.andReturn(true);
mockType.getName.andReturn(name); mockType.getName.andReturn(name);
mockType.getKey.andReturn(name);
return mockType; return mockType;
} }
beforeEach(function () { beforeEach(function () {
mockTypeService = jasmine.createSpyObj( mockTypeService = jasmine.createSpyObj(
"typeService", "typeService",
["listTypes"] ["getType"]
);
mockDialogService = jasmine.createSpyObj(
"dialogService",
["getUserInput"]
);
mockPolicyService = jasmine.createSpyObj(
"policyService",
["allow"]
); );
mockDialogService = {};
mockPolicyService = {};
mockDomainObject = {};
mockDomainObject = jasmine.createSpyObj( mockTypes = [
"domainObject", "timeline",
["getCapability"] "activity",
); "other"
].map(createMockType);
//Mocking getCapability because AddActionProvider uses the mockTypeMap = {};
// type capability of the destination object.
mockDomainObject.getCapability.andReturn({});
mockTypes = ["A", "B", "C"].map(createMockType);
mockTypes.forEach(function (type) { mockTypes.forEach(function (type) {
mockPolicyMap[type.getName()] = true; mockTypeMap[type.getKey()] = type;
}); });
mockCreationPolicy = function (type) { mockTypeService.getType.andCallFake(function (key) {
return mockPolicyMap[type.getName()]; return mockTypeMap[key];
}; });
mockCompositionPolicy = function () {
return true;
};
mockPolicyService.allow.andReturn(true);
mockTypeService.listTypes.andReturn(mockTypes);
provider = new AddActionProvider( provider = new AddActionProvider(
mockQ, mockQ,
@ -107,29 +89,16 @@ define(
); );
}); });
it("checks for creatability", function () { it("provides actions for timeline and activity", function () {
provider.getActions({ var actions = provider.getActions({
key: "add", key: "add",
domainObject: mockDomainObject domainObject: mockDomainObject
}); });
expect(actions.length).toBe(2);
expect(actions[0].metadata.type).toBe('timeline');
expect(actions[1].metadata.type).toBe('activity');
// Make sure it was creation which was used to check // Make sure it was creation which was used to check
expect(mockPolicyService.allow)
.toHaveBeenCalledWith("creation", mockTypes[0]);
});
it("checks for composability of type", function () {
provider.getActions({
key: "add",
domainObject: mockDomainObject
});
expect(mockPolicyService.allow).toHaveBeenCalledWith(
"composition",
jasmine.any(Object),
jasmine.any(Object)
);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('type');
}); });
}); });
} }

View File

@ -175,7 +175,7 @@ define(
expect(mockPolicyService.allow).toHaveBeenCalledWith( expect(mockPolicyService.allow).toHaveBeenCalledWith(
'composition', 'composition',
mockOtherType, mockOtherType,
mockType mockDomainObject
); );
}); });

View File

@ -39,9 +39,7 @@ define(
} }
CompositionPolicy.prototype.allow = function (parentType, child) { CompositionPolicy.prototype.allow = function (parentType, child) {
var childType = child.getCapability('type'); var parentDef = parentType.getDefinition();
var childTypeKey = childType.getKey();
var parentDef = parent.getDefinition();
// A parent without containment rules can contain anything. // A parent without containment rules can contain anything.
if (!parentDef.contains) { if (!parentDef.contains) {
@ -53,7 +51,7 @@ define(
return parentDef.contains.some(function (c) { return parentDef.contains.some(function (c) {
// Simple containment rules are supported typeKeys. // Simple containment rules are supported typeKeys.
if (typeof c === 'string') { if (typeof c === 'string') {
return c === childTypeKey; return c === child.getCapability('type').getKey();
} }
// More complicated rules require context to have all specified // More complicated rules require context to have all specified
// capabilities. // capabilities.

View File

@ -79,7 +79,7 @@ define(
expect(mockPolicyService.allow).toHaveBeenCalledWith( expect(mockPolicyService.allow).toHaveBeenCalledWith(
'composition', 'composition',
mockTypes[0], mockTypes[0],
mockTypes[1] mockDomainObjects[1]
); );
}); });

View File

@ -24,60 +24,94 @@ define(
["../src/CompositionPolicy"], ["../src/CompositionPolicy"],
function (CompositionPolicy) { function (CompositionPolicy) {
describe("Composition policy", function () { describe("Composition policy", function () {
var mockInjector, var typeA,
mockTypeService, typeB,
mockCapabilityService, typeC,
mockTypes, mockChildObject,
policy; policy;
beforeEach(function () { beforeEach(function () {
mockInjector = jasmine.createSpyObj('$injector', ['get']); typeA = jasmine.createSpyObj(
mockTypeService = jasmine.createSpyObj( 'type A-- the particular kind',
'typeService', ['getKey', 'getDefinition']
['listTypes']
); );
mockCapabilityService = jasmine.createSpyObj( typeA.getKey.andReturn('a');
'capabilityService', typeA.getDefinition.andReturn({
['getCapabilities'] contains: ['a']
);
// Both types can only contain b, let's say
mockTypes = ['a', 'b'].map(function (type) {
var mockType = jasmine.createSpyObj(
'type-' + type,
['getKey', 'getDefinition', 'getInitialModel']
);
mockType.getKey.andReturn(type);
mockType.getDefinition.andReturn({
contains: ['b']
});
mockType.getInitialModel.andReturn({});
return mockType;
}); });
mockInjector.get.andCallFake(function (name) {
return { typeB = jasmine.createSpyObj(
typeService: mockTypeService, 'type B-- anything goes',
capabilityService: mockCapabilityService ['getKey', 'getDefinition']
}[name]; );
typeB.getKey.andReturn('b');
typeB.getDefinition.andReturn({
contains: ['a', 'b']
}); });
mockTypeService.listTypes.andReturn(mockTypes); typeC = jasmine.createSpyObj(
mockCapabilityService.getCapabilities.andReturn({}); 'type C-- distinguishing and interested in telemetry',
['getKey', 'getDefinition']
);
typeC.getKey.andReturn('c');
typeC.getDefinition.andReturn({
contains: [{has: 'telemetry'}]
});
policy = new CompositionPolicy(mockInjector); mockChildObject = jasmine.createSpyObj(
'childObject',
['getCapability', 'hasCapability']
);
policy = new CompositionPolicy();
}); });
// Test basic composition policy here; test more closely at describe('enforces simple containment rules', function () {
// the unit level in ContainmentTable for 'has' support, et al
it("enforces containment rules defined by types", function () { it('allows when type matches', function () {
expect(policy.allow(mockTypes[0], mockTypes[1])) mockChildObject.getCapability.andReturn(typeA);
.toBeTruthy(); expect(policy.allow(typeA, mockChildObject))
expect(policy.allow(mockTypes[1], mockTypes[1])) .toBeTruthy();
.toBeTruthy();
expect(policy.allow(mockTypes[1], mockTypes[0])) expect(policy.allow(typeB, mockChildObject))
.toBeFalsy(); .toBeTruthy();
expect(policy.allow(mockTypes[0], mockTypes[0]))
.toBeFalsy(); mockChildObject.getCapability.andReturn(typeB);
expect(policy.allow(typeB, mockChildObject))
.toBeTruthy();
});
it('disallows when type doesn\'t match', function () {
mockChildObject.getCapability.andReturn(typeB);
expect(policy.allow(typeA, mockChildObject))
.toBeFalsy();
mockChildObject.getCapability.andReturn(typeC);
expect(policy.allow(typeA, mockChildObject))
.toBeFalsy();
});
});
describe('enforces capability-based containment rules', function () {
it('allows when object has capability', function () {
mockChildObject.hasCapability.andReturn(true);
expect(policy.allow(typeC, mockChildObject))
.toBeTruthy();
expect(mockChildObject.hasCapability)
.toHaveBeenCalledWith('telemetry');
});
it('skips when object doesn\'t have capability', function () {
mockChildObject.hasCapability.andReturn(false);
expect(policy.allow(typeC, mockChildObject))
.toBeFalsy();
expect(mockChildObject.hasCapability)
.toHaveBeenCalledWith('telemetry');
});
}); });
}); });

View File

@ -104,7 +104,7 @@ define(
expect(policyService.allow).toHaveBeenCalledWith( expect(policyService.allow).toHaveBeenCalledWith(
"composition", "composition",
parentCandidate.capabilities.type, parentCandidate.capabilities.type,
object.capabilities.type object
); );
}); });

View File

@ -114,7 +114,7 @@ define(
expect(mockPolicyService.allow).toHaveBeenCalledWith( expect(mockPolicyService.allow).toHaveBeenCalledWith(
"composition", "composition",
parentCandidate.capabilities.type, parentCandidate.capabilities.type,
object.capabilities.type object
); );
}); });

View File

@ -124,7 +124,7 @@ define(
expect(policyService.allow).toHaveBeenCalledWith( expect(policyService.allow).toHaveBeenCalledWith(
"composition", "composition",
parentCandidate.capabilities.type, parentCandidate.capabilities.type,
object.capabilities.type object
); );
}); });

View File

@ -24,18 +24,25 @@ define(
["../src/LayoutCompositionPolicy"], ["../src/LayoutCompositionPolicy"],
function (LayoutCompositionPolicy) { function (LayoutCompositionPolicy) {
describe("Layout's composition policy", function () { describe("Layout's composition policy", function () {
var mockCandidate, var mockChild,
mockCandidate,
mockContext, mockContext,
candidateType, candidateType,
contextType, contextType,
policy; policy;
beforeEach(function () { beforeEach(function () {
mockChild = jasmine.createSpyObj(
'childObject',
['getCapability']
);
mockCandidate = mockCandidate =
jasmine.createSpyObj('candidateType', ['instanceOf']); jasmine.createSpyObj('candidateType', ['instanceOf']);
mockContext = mockContext =
jasmine.createSpyObj('contextType', ['instanceOf']); jasmine.createSpyObj('contextType', ['instanceOf']);
mockChild.getCapability.andReturn(mockContext);
mockCandidate.instanceOf.andCallFake(function (t) { mockCandidate.instanceOf.andCallFake(function (t) {
return t === candidateType; return t === candidateType;
}); });
@ -49,19 +56,19 @@ define(
it("disallows folders in layouts", function () { it("disallows folders in layouts", function () {
candidateType = 'layout'; candidateType = 'layout';
contextType = 'folder'; contextType = 'folder';
expect(policy.allow(mockCandidate, mockContext)).toBe(false); expect(policy.allow(mockCandidate, mockChild)).toBe(false);
}); });
it("does not disallow folders elsewhere", function () { it("does not disallow folders elsewhere", function () {
candidateType = 'nonlayout'; candidateType = 'nonlayout';
contextType = 'folder'; contextType = 'folder';
expect(policy.allow(mockCandidate, mockContext)).toBe(true); expect(policy.allow(mockCandidate, mockChild)).toBe(true);
}); });
it("allows things other than folders in layouts", function () { it("allows things other than folders in layouts", function () {
candidateType = 'layout'; candidateType = 'layout';
contextType = 'nonfolder'; contextType = 'nonfolder';
expect(policy.allow(mockCandidate, mockContext)).toBe(true); expect(policy.allow(mockCandidate, mockChild)).toBe(true);
}); });
}); });