Merge remote-tracking branch 'github-open/open97' into open-master

This commit is contained in:
Pete Richards
2015-09-23 13:44:48 -07:00
18 changed files with 467 additions and 199 deletions

View File

@@ -32,7 +32,8 @@ define(
* @private
*/
/**
* Change the composition of the specified objects.
* Change the composition of the specified objects. Note that this
* should only be invoked after successfully validating.
*
* @param {DomainObject} domainObject the domain object to
* move, copy, or link.
@@ -43,7 +44,8 @@ define(
* @method platform/entanglement.AbstractComposeService#perform
*/
/**
* Check if one object can be composed into another.
* Check if this composition change is valid for these objects.
*
* @param {DomainObject} domainObject the domain object to
* move, copy, or link.
* @param {DomainObject} parent the domain object whose composition

View File

@@ -64,6 +64,12 @@ define(
return self.perform(domainObject, parent);
}
if (!this.validate(domainObject, parent)) {
throw new Error(
"Tried to copy objects without validating first."
);
}
if (domainObject.hasCapability('composition')) {
model.composition = [];
}

View File

@@ -45,6 +45,9 @@ define(
if (parentCandidate.getId() === object.getId()) {
return false;
}
if (!parentCandidate.hasCapability('composition')) {
return false;
}
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false;
}
@@ -56,26 +59,18 @@ define(
};
LinkService.prototype.perform = function (object, parentObject) {
function findChild(children) {
var i;
for (i = 0; i < children.length; i += 1) {
if (children[i].getId() === object.getId()) {
return children[i];
}
}
if (!this.validate(object, parentObject)) {
throw new Error(
"Tried to link objects without validating first."
);
}
return parentObject.useCapability('mutation', function (model) {
if (model.composition.indexOf(object.getId()) === -1) {
model.composition.push(object.getId());
}
}).then(function () {
return parentObject.getCapability('persistence').persist();
}).then(function getObjectWithNewContext() {
return parentObject
.useCapability('composition')
.then(findChild);
});
return parentObject.getCapability('composition').add(object)
.then(function (objectInNewContext) {
return parentObject.getCapability('persistence')
.persist()
.then(function () { return objectInNewContext; });
});
};
return LinkService;

View File

@@ -82,6 +82,12 @@ define(
}
}
if (!this.validate(object, parentObject)) {
throw new Error(
"Tried to move objects without validating first."
);
}
return this.linkService
.perform(object, parentObject)
.then(relocate)

View File

@@ -41,19 +41,23 @@ define(
}
describe("CopyService", function () {
var policyService;
beforeEach(function () {
policyService = jasmine.createSpyObj(
'policyService',
['allow']
);
});
describe("validate", function () {
var policyService,
copyService,
var copyService,
object,
parentCandidate,
validate;
beforeEach(function () {
policyService = jasmine.createSpyObj(
'policyService',
['allow']
);
copyService = new CopyService(
null,
null,
@@ -126,6 +130,16 @@ define(
copyResult,
copyFinished;
beforeEach(function () {
creationService = jasmine.createSpyObj(
'creationService',
['createObject']
);
createObjectPromise = synchronousPromise(undefined);
creationService.createObject.andReturn(createObjectPromise);
policyService.allow.andReturn(true);
});
describe("on domain object without composition", function () {
beforeEach(function () {
object = domainObjectFactory({
@@ -142,13 +156,7 @@ define(
composition: []
}
});
creationService = jasmine.createSpyObj(
'creationService',
['createObject']
);
createObjectPromise = synchronousPromise(undefined);
creationService.createObject.andReturn(createObjectPromise);
copyService = new CopyService(null, creationService);
copyService = new CopyService(null, creationService, policyService);
copyResult = copyService.perform(object, newParent);
copyFinished = jasmine.createSpy('copyFinished');
copyResult.then(copyFinished);
@@ -180,7 +188,8 @@ define(
});
describe("on domainObject with composition", function () {
var childObject,
var newObject,
childObject,
compositionCapability,
compositionPromise;
@@ -216,6 +225,17 @@ define(
composition: compositionCapability
}
});
newObject = domainObjectFactory({
name: 'object',
id: 'abc2',
model: {
name: 'some object',
composition: []
},
capabilities: {
composition: compositionCapability
}
});
newParent = domainObjectFactory({
name: 'newParent',
id: '456',
@@ -223,13 +243,10 @@ define(
composition: []
}
});
creationService = jasmine.createSpyObj(
'creationService',
['createObject']
);
createObjectPromise = synchronousPromise(undefined);
createObjectPromise = synchronousPromise(newObject);
creationService.createObject.andReturn(createObjectPromise);
copyService = new CopyService(mockQ, creationService);
copyService = new CopyService(mockQ, creationService, policyService);
copyResult = copyService.perform(object, newParent);
copyFinished = jasmine.createSpy('copyFinished');
copyResult.then(copyFinished);
@@ -266,6 +283,38 @@ define(
});
});
describe("on invalid inputs", function () {
beforeEach(function () {
object = domainObjectFactory({
name: 'object',
capabilities: {
type: { type: 'object' }
}
});
newParent = domainObjectFactory({
name: 'parentCandidate',
capabilities: {
type: { type: 'parentCandidate' }
}
});
});
it("throws an error", function () {
var copyService =
new CopyService(mockQ, creationService, policyService);
function perform() {
copyService.perform(object, newParent);
}
spyOn(copyService, "validate");
copyService.validate.andReturn(true);
expect(perform).not.toThrow();
copyService.validate.andReturn(false);
expect(perform).toThrow();
});
});
});
});
}

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,beforeEach,it,jasmine,expect */
/*global define,describe,beforeEach,it,jasmine,expect,spyOn */
define(
[
@@ -41,6 +41,7 @@ define(
'policyService',
['allow']
);
mockPolicyService.allow.andReturn(true);
linkService = new LinkService(mockPolicyService);
});
@@ -55,7 +56,13 @@ define(
name: 'object'
});
parentCandidate = domainObjectFactory({
name: 'parentCandidate'
name: 'parentCandidate',
capabilities: {
composition: jasmine.createSpyObj(
'composition',
['invoke', 'add']
)
}
});
validate = function () {
return linkService.validate(object, parentCandidate);
@@ -81,6 +88,18 @@ define(
expect(validate()).toBe(false);
});
it("does not allow parents without composition", function () {
parentCandidate = domainObjectFactory({
name: 'parentCandidate'
});
object.id = 'abc';
parentCandidate.id = 'xyz';
parentCandidate.hasCapability.andCallFake(function (c) {
return c !== 'composition';
});
expect(validate()).toBe(false);
});
describe("defers to policyService", function () {
beforeEach(function () {
object.id = 'abc';
@@ -121,16 +140,16 @@ define(
linkedObject,
parentModel,
parentObject,
mutationPromise,
compositionPromise,
persistencePromise,
addPromise,
compositionCapability,
persistenceCapability;
beforeEach(function () {
mutationPromise = new ControlledPromise();
compositionPromise = new ControlledPromise();
persistencePromise = new ControlledPromise();
addPromise = new ControlledPromise();
persistenceCapability = jasmine.createSpyObj(
'persistenceCapability',
['persist']
@@ -138,9 +157,10 @@ define(
persistenceCapability.persist.andReturn(persistencePromise);
compositionCapability = jasmine.createSpyObj(
'compositionCapability',
['invoke']
['invoke', 'add']
);
compositionCapability.invoke.andReturn(compositionPromise);
compositionCapability.add.andReturn(addPromise);
parentModel = {
composition: []
};
@@ -151,7 +171,7 @@ define(
mutation: {
invoke: function (mutator) {
mutator(parentModel);
return mutationPromise;
return new ControlledPromise();
}
},
persistence: persistenceCapability,
@@ -172,20 +192,17 @@ define(
});
it("modifies parent model composition", function () {
expect(parentModel.composition.length).toBe(0);
it("adds to the parent's composition", function () {
expect(compositionCapability.add).not.toHaveBeenCalled();
linkService.perform(object, parentObject);
expect(parentObject.useCapability).toHaveBeenCalledWith(
'mutation',
jasmine.any(Function)
);
expect(parentModel.composition).toContain('xyz');
expect(compositionCapability.add)
.toHaveBeenCalledWith(object);
});
it("persists parent", function () {
linkService.perform(object, parentObject);
expect(mutationPromise.then).toHaveBeenCalled();
mutationPromise.resolve();
expect(addPromise.then).toHaveBeenCalled();
addPromise.resolve(linkedObject);
expect(parentObject.getCapability)
.toHaveBeenCalledWith('persistence');
expect(persistenceCapability.persist).toHaveBeenCalled();
@@ -197,11 +214,23 @@ define(
whenComplete = jasmine.createSpy('whenComplete');
returnPromise.then(whenComplete);
mutationPromise.resolve();
addPromise.resolve(linkedObject);
persistencePromise.resolve();
compositionPromise.resolve([linkedObject]);
expect(whenComplete).toHaveBeenCalledWith(linkedObject);
});
it("throws an error when performed on invalid inputs", function () {
function perform() {
linkService.perform(object, parentObject);
}
spyOn(linkService, 'validate');
linkService.validate.andReturn(true);
expect(perform).not.toThrow();
linkService.validate.andReturn(false);
expect(perform).toThrow();
});
});
});
}

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,beforeEach,it,jasmine,expect */
/*global define,describe,beforeEach,it,jasmine,expect,spyOn */
define(
[
'../../src/services/MoveService',
@@ -40,58 +40,57 @@ define(
var moveService,
policyService,
object,
objectContextCapability,
currentParent,
parentCandidate,
linkService;
beforeEach(function () {
objectContextCapability = jasmine.createSpyObj(
'objectContextCapability',
[
'getParent'
]
);
object = domainObjectFactory({
name: 'object',
id: 'a',
capabilities: {
context: objectContextCapability,
type: { type: 'object' }
}
});
currentParent = domainObjectFactory({
name: 'currentParent',
id: 'b'
});
objectContextCapability.getParent.andReturn(currentParent);
parentCandidate = domainObjectFactory({
name: 'parentCandidate',
model: { composition: [] },
id: 'c',
capabilities: {
type: { type: 'parentCandidate' }
}
});
policyService = jasmine.createSpyObj(
'policyService',
['allow']
);
linkService = new MockLinkService();
policyService.allow.andReturn(true);
moveService = new MoveService(policyService, linkService);
});
describe("validate", function () {
var object,
objectContextCapability,
currentParent,
parentCandidate,
validate;
var validate;
beforeEach(function () {
objectContextCapability = jasmine.createSpyObj(
'objectContextCapability',
[
'getParent'
]
);
object = domainObjectFactory({
name: 'object',
id: 'a',
capabilities: {
context: objectContextCapability,
type: { type: 'object' }
}
});
currentParent = domainObjectFactory({
name: 'currentParent',
id: 'b'
});
objectContextCapability.getParent.andReturn(currentParent);
parentCandidate = domainObjectFactory({
name: 'parentCandidate',
model: { composition: [] },
id: 'c',
capabilities: {
type: { type: 'parentCandidate' }
}
});
validate = function () {
return moveService.validate(object, parentCandidate);
};
@@ -145,14 +144,15 @@ define(
describe("perform", function () {
var object,
newParent,
actionCapability,
var actionCapability,
locationCapability,
locationPromise,
newParent,
moveResult;
beforeEach(function () {
newParent = parentCandidate;
actionCapability = jasmine.createSpyObj(
'actionCapability',
['perform']
@@ -175,7 +175,9 @@ define(
name: 'object',
capabilities: {
action: actionCapability,
location: locationCapability
location: locationCapability,
context: objectContextCapability,
type: { type: 'object' }
}
});
@@ -194,6 +196,18 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("throws an error when performed on invalid inputs", function () {
function perform() {
moveService.perform(object, newParent);
}
spyOn(moveService, "validate");
moveService.validate.andReturn(true);
expect(perform).not.toThrow();
moveService.validate.andReturn(false);
expect(perform).toThrow();
});
describe("when moving an original", function () {
beforeEach(function () {
locationCapability.getContextualLocation