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
commit 70bc17c79f
18 changed files with 467 additions and 199 deletions

View File

@ -86,37 +86,18 @@ define(
// composition, so that it will subsequently appear
// as a child contained by that parent.
function addToComposition(id, parent, parentPersistence) {
var mutatationResult = parent.useCapability("mutation", function (model) {
if (Array.isArray(model.composition)) {
// Don't add if the id is already there
if (model.composition.indexOf(id) === -1) {
model.composition.push(id);
}
} else {
// This is abnormal; composition should be an array
self.$log.warn(NO_COMPOSITION_WARNING + parent.getId());
return false; // Cancel mutation
}
});
var compositionCapability = parent.getCapability('composition'),
addResult = compositionCapability &&
compositionCapability.add(id);
return self.$q.when(mutatationResult).then(function (result) {
return self.$q.when(addResult).then(function (result) {
if (!result) {
self.$log.error("Could not mutate " + parent.getId());
self.$log.error("Could not modify " + parent.getId());
return undefined;
}
return parentPersistence.persist().then(function () {
// Locate and return new Object in context of parent.
return parent
.useCapability('composition')
.then(function (children) {
var i;
for (i = 0; i < children.length; i += 1) {
if (children[i].getId() === id) {
return children[i];
}
}
});
return result;
});
});
}

View File

@ -86,7 +86,7 @@ define(
);
mockCompositionCapability = jasmine.createSpyObj(
"composition",
["invoke"]
["invoke", "add"]
);
mockContextCapability = jasmine.createSpyObj(
"context",
@ -120,6 +120,7 @@ define(
mockCompositionCapability.invoke.andReturn(
mockPromise([mockNewObject])
);
mockCompositionCapability.add.andReturn(mockPromise(true));
creationService = new CreationService(
mockPersistenceService,
@ -143,33 +144,34 @@ define(
parentModel = { composition: ["notAnyUUID"] };
creationService.createObject(model, mockParentObject);
// Invoke the mutation callback
expect(mockMutationCapability.invoke).toHaveBeenCalled();
mockMutationCapability.invoke.mostRecentCall.args[0](parentModel);
// Should have a longer composition now, with the new UUID
expect(parentModel.composition.length).toEqual(2);
// Verify that a new ID was added
expect(mockCompositionCapability.add)
.toHaveBeenCalledWith(jasmine.any(String));
});
it("warns if parent has no composition", function () {
var model = { someKey: "some value" },
parentModel = { };
creationService.createObject(model, mockParentObject);
it("provides the newly-created object", function () {
var mockDomainObject = jasmine.createSpyObj(
'newDomainObject',
['getId', 'getModel', 'getCapability']
),
mockCallback = jasmine.createSpy('callback');
// Verify precondition; no prior warnings
expect(mockLog.warn).not.toHaveBeenCalled();
// Act as if the object had been created
mockCompositionCapability.add.andCallFake(function (id) {
mockDomainObject.getId.andReturn(id);
mockCompositionCapability.invoke
.andReturn(mockPromise([mockDomainObject]));
return mockPromise(mockDomainObject);
});
// Invoke the mutation callback
expect(mockMutationCapability.invoke).toHaveBeenCalled();
mockMutationCapability.invoke.mostRecentCall.args[0](parentModel);
// Should find it in the composition
creationService.createObject({}, mockParentObject)
.then(mockCallback);
expect(mockCallback).toHaveBeenCalledWith(mockDomainObject);
// Should have a longer composition now, with the new UUID
expect(mockLog.warn).toHaveBeenCalled();
// Composition should still be undefined
expect(parentModel.composition).toBeUndefined();
});
it("warns if parent has no persistence capability", function () {
// Callbacks
var success = jasmine.createSpy("success"),
@ -185,7 +187,6 @@ define(
expect(mockLog.warn).toHaveBeenCalled();
expect(success).not.toHaveBeenCalled();
expect(failure).toHaveBeenCalled();
});
it("logs an error when mutaton fails", function () {
@ -194,7 +195,7 @@ define(
var model = { someKey: "some value" },
parentModel = { composition: ["notAnyUUID"] };
mockMutationCapability.invoke.andReturn(mockPromise(false));
mockCompositionCapability.add.andReturn(mockPromise(false));
creationService.createObject(model, mockParentObject);

View File

@ -36,20 +36,11 @@ define(
function LinkAction(context) {
this.domainObject = (context || {}).domainObject;
this.selectedObject = (context || {}).selectedObject;
this.selectedId = this.selectedObject && this.selectedObject.getId();
}
LinkAction.prototype.perform = function () {
var self = this;
// Add this domain object's identifier
function addId(model) {
if (Array.isArray(model.composition) &&
model.composition.indexOf(self.selectedId) < 0) {
model.composition.push(self.selectedId);
}
}
// Persist changes to the domain object
function doPersist() {
var persistence =
@ -59,11 +50,13 @@ define(
// Link these objects
function doLink() {
return self.domainObject.useCapability("mutation", addId)
.then(doPersist);
var composition = self.domainObject &&
self.domainObject.getCapability('composition');
return composition && composition.add(self.selectedObject)
.then(doPersist);
}
return this.selectedId && doLink();
return this.selectedObject && doLink();
};
return LinkAction;

View File

@ -54,6 +54,9 @@ define(
var row = Object.create(property.getDefinition());
row.key = index;
return row;
}).filter(function (row) {
// Only show properties which are editable
return row.control;
})
}]
};

View File

@ -31,7 +31,7 @@ define(
mockDomainObject,
mockParent,
mockContext,
mockMutation,
mockComposition,
mockPersistence,
mockType,
actionContext,
@ -67,7 +67,7 @@ define(
}
};
mockContext = jasmine.createSpyObj("context", [ "getParent" ]);
mockMutation = jasmine.createSpyObj("mutation", [ "invoke" ]);
mockComposition = jasmine.createSpyObj("composition", [ "invoke", "add" ]);
mockPersistence = jasmine.createSpyObj("persistence", [ "persist" ]);
mockType = jasmine.createSpyObj("type", [ "hasFeature" ]);
@ -75,11 +75,11 @@ define(
mockDomainObject.getCapability.andReturn(mockContext);
mockContext.getParent.andReturn(mockParent);
mockType.hasFeature.andReturn(true);
mockMutation.invoke.andReturn(mockPromise(true));
mockComposition.invoke.andReturn(mockPromise(true));
mockComposition.add.andReturn(mockPromise(true));
capabilities = {
mutation: mockMutation,
composition: mockComposition,
persistence: mockPersistence,
type: mockType
};
@ -96,30 +96,14 @@ define(
});
it("mutates the parent when performed", function () {
it("adds to the parent's composition when performed", function () {
action.perform();
expect(mockMutation.invoke)
.toHaveBeenCalledWith(jasmine.any(Function));
expect(mockComposition.add)
.toHaveBeenCalledWith(mockDomainObject);
});
it("changes composition from its mutation function", function () {
var mutator, result;
it("persists changes afterward", function () {
action.perform();
mutator = mockMutation.invoke.mostRecentCall.args[0];
result = mutator(model);
// Should not have cancelled the mutation
expect(result).not.toBe(false);
// Simulate mutate's behavior (remove can either return a
// new model or modify this one in-place)
result = result || model;
// Should have removed "test" - that was our
// mock domain object's id.
expect(result.composition).toEqual(["a", "b", "c", "test"]);
// Finally, should have persisted
expect(mockPersistence.persist).toHaveBeenCalled();
});

View File

@ -39,7 +39,7 @@ define(
return {
getValue: function (model) { return model[k]; },
setValue: function (model, v) { model[k] = v; },
getDefinition: function () { return {}; }
getDefinition: function () { return { control: 'textfield '}; }
};
});

View File

@ -63,7 +63,12 @@
"provides": "modelService",
"type": "provider",
"implementation": "models/PersistedModelProvider.js",
"depends": [ "persistenceService", "$q", "PERSISTENCE_SPACE" ]
"depends": [
"persistenceService",
"$q",
"PERSISTENCE_SPACE",
"ADDITIONAL_PERSISTENCE_SPACES"
]
},
{
"provides": "modelService",
@ -218,6 +223,17 @@
"composition": []
}
}
],
"constants": [
{
"key": "PERSISTENCE_SPACE",
"value": "mct"
},
{
"key": "ADDITIONAL_PERSISTENCE_SPACES",
"value": [],
"description": "An array of additional persistence spaces to load models from."
}
]
}
}

View File

@ -50,6 +50,66 @@ define(
this.domainObject = domainObject;
}
/**
* Add a domain object to the composition of the field.
* This mutates but does not persist the modified object.
*
* If no index is given, this is added to the end of the composition.
*
* @param {DomainObject|string} domainObject the domain object to add,
* or simply its identifier
* @param {number} [index] the index at which to add the object
* @returns {Promise.<DomainObject>} a promise for the added object
* in its new context
*/
CompositionCapability.prototype.add = function (domainObject, index) {
var self = this,
id = typeof domainObject === 'string' ?
domainObject : domainObject.getId(),
model = self.domainObject.getModel(),
composition = model.composition,
oldIndex = composition.indexOf(id);
// Find the object with the above id, used to contextualize
function findObject(objects) {
var i;
for (i = 0; i < objects.length; i += 1) {
if (objects[i].getId() === id) {
return objects[i];
}
}
}
function contextualize(mutationResult) {
return mutationResult && self.invoke().then(findObject);
}
function addIdToModel(model) {
// Pick a specific index if needed.
index = isNaN(index) ? composition.length : index;
// Also, don't put past the end of the array
index = Math.min(composition.length, index);
// Remove the existing instance of the id
if (oldIndex !== -1) {
model.composition.splice(oldIndex, 1);
}
// ...and add it back at the appropriate index.
model.composition.splice(index, 0, id);
}
// If no index has been specified already and the id is already
// present, nothing to do. If the id is already at that index,
// also nothing to do, so cancel mutation.
if ((isNaN(index) && oldIndex !== -1) || (index === oldIndex)) {
return contextualize(true);
}
return this.domainObject.useCapability('mutation', addIdToModel)
.then(contextualize);
};
/**
* Request the composition of this object.
* @returns {Promise.<DomainObject[]>} a list of all domain

View File

@ -39,23 +39,37 @@ define(
* @param {PersistenceService} persistenceService the service in which
* domain object models are persisted.
* @param $q Angular's $q service, for working with promises
* @param {string} SPACE the name of the persistence space from which
* models should be retrieved.
* @param {string} space the name of the persistence space(s)
* from which models should be retrieved.
* @param {string} spaces additional persistence spaces to use
*/
function PersistedModelProvider(persistenceService, $q, space) {
function PersistedModelProvider(persistenceService, $q, space, spaces) {
this.persistenceService = persistenceService;
this.$q = $q;
this.space = space;
this.spaces = [space].concat(spaces || []);
}
// Take the most recently modified model, for cases where
// multiple persistence spaces return models.
function takeMostRecent(modelA, modelB) {
return (!modelB || modelB.modified === undefined) ? modelA :
(!modelA || modelA.modified === undefined) ? modelB :
modelB.modified > modelA.modified ? modelB :
modelA;
}
PersistedModelProvider.prototype.getModels = function (ids) {
var persistenceService = this.persistenceService,
$q = this.$q,
space = this.space;
spaces = this.spaces;
// Load a single object model from persistence
// Load a single object model from any persistence spaces
function loadModel(id) {
return persistenceService.readObject(space, id);
return $q.all(spaces.map(function (space) {
return persistenceService.readObject(space, id);
})).then(function (models) {
return models.reduce(takeMostRecent);
});
}
// Package the result as id->model

View File

@ -51,7 +51,7 @@ define(
// so support that, but don't introduce complication of
// native promises.
function mockPromise(value) {
return {
return (value || {}).then ? value : {
then: function (callback) {
return mockPromise(callback(value));
}
@ -123,6 +123,98 @@ define(
});
it("allows domain objects to be added", function () {
var result,
testModel = { composition: [] },
mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS);
mockDomainObject.getModel.andReturn(testModel);
mockObjectService.getObjects.andReturn(mockPromise({a: mockChild}));
mockChild.getCapability.andReturn(undefined);
mockChild.getId.andReturn('a');
mockDomainObject.useCapability.andCallFake(function (key, mutator) {
if (key === 'mutation') {
mutator(testModel);
return mockPromise(true);
}
});
composition.add(mockChild).then(function (domainObject) {
result = domainObject;
});
expect(testModel.composition).toEqual(['a']);
// Should have returned the added object in its new context
expect(result.getId()).toEqual('a');
expect(result.getCapability('context')).toBeDefined();
expect(result.getCapability('context').getParent())
.toEqual(mockDomainObject);
});
it("does not re-add IDs which are already present", function () {
var result,
testModel = { composition: [ 'a' ] },
mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS);
mockDomainObject.getModel.andReturn(testModel);
mockObjectService.getObjects.andReturn(mockPromise({a: mockChild}));
mockChild.getCapability.andReturn(undefined);
mockChild.getId.andReturn('a');
mockDomainObject.useCapability.andCallFake(function (key, mutator) {
if (key === 'mutation') {
mutator(testModel);
return mockPromise(true);
}
});
composition.add(mockChild).then(function (domainObject) {
result = domainObject;
});
// Still just 'a'
expect(testModel.composition).toEqual(['a']);
// Should have returned the added object in its new context
expect(result.getId()).toEqual('a');
expect(result.getCapability('context')).toBeDefined();
expect(result.getCapability('context').getParent())
.toEqual(mockDomainObject);
});
it("can add objects at a specified index", function () {
var result,
testModel = { composition: [ 'a', 'b', 'c' ] },
mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS);
mockDomainObject.getModel.andReturn(testModel);
mockObjectService.getObjects.andReturn(mockPromise({a: mockChild}));
mockChild.getCapability.andReturn(undefined);
mockChild.getId.andReturn('a');
mockDomainObject.useCapability.andCallFake(function (key, mutator) {
if (key === 'mutation') {
mutator(testModel);
return mockPromise(true);
}
});
composition.add(mockChild, 1).then(function (domainObject) {
result = domainObject;
});
// Still just 'a'
expect(testModel.composition).toEqual(['b', 'a', 'c']);
// Should have returned the added object in its new context
expect(result.getId()).toEqual('a');
expect(result.getCapability('context')).toBeDefined();
expect(result.getCapability('context').getParent())
.toEqual(mockDomainObject);
});
});
}
);

View File

@ -32,7 +32,9 @@ define(
describe("The persisted model provider", function () {
var mockQ,
mockPersistenceService,
SPACE = "some space",
SPACE = "space0",
spaces = [ "space1" ],
modTimes,
provider;
function mockPromise(value) {
@ -51,12 +53,14 @@ define(
}
beforeEach(function () {
modTimes = {};
mockQ = { when: mockPromise, all: mockAll };
mockPersistenceService = {
readObject: function (space, id) {
return mockPromise({
space: space,
id: id
id: id,
modified: (modTimes[space] || {})[id]
});
}
};
@ -64,7 +68,8 @@ define(
provider = new PersistedModelProvider(
mockPersistenceService,
mockQ,
SPACE
SPACE,
spaces
);
});
@ -82,6 +87,24 @@ define(
});
});
it("reads object models from multiple spaces", function () {
var models;
modTimes.space1 = {
'x': 12321
};
provider.getModels(["a", "x", "zz"]).then(function (m) {
models = m;
});
expect(models).toEqual({
a: { space: SPACE, id: "a" },
x: { space: 'space1', id: "x", modified: 12321 },
zz: { space: SPACE, id: "zz" }
});
});
});
}
);

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