diff --git a/platform/commonUI/browse/src/creation/CreationService.js b/platform/commonUI/browse/src/creation/CreationService.js index 188c384c1a..984a3dbe85 100644 --- a/platform/commonUI/browse/src/creation/CreationService.js +++ b/platform/commonUI/browse/src/creation/CreationService.js @@ -87,12 +87,12 @@ define( // as a child contained by that parent. function addToComposition(id, parent, parentPersistence) { var compositionCapability = parent.getCapability('composition'), - mutationResult = compositionCapability && + 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; } diff --git a/platform/commonUI/browse/test/creation/CreationServiceSpec.js b/platform/commonUI/browse/test/creation/CreationServiceSpec.js index bdcd752c6c..d492b51f8f 100644 --- a/platform/commonUI/browse/test/creation/CreationServiceSpec.js +++ b/platform/commonUI/browse/test/creation/CreationServiceSpec.js @@ -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(true); + }); - // 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); diff --git a/platform/commonUI/edit/test/actions/LinkActionSpec.js b/platform/commonUI/edit/test/actions/LinkActionSpec.js index 835f93740a..96ea30e2b3 100644 --- a/platform/commonUI/edit/test/actions/LinkActionSpec.js +++ b/platform/commonUI/edit/test/actions/LinkActionSpec.js @@ -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,33 +96,17 @@ 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(); }); }); } -); \ No newline at end of file +); diff --git a/platform/entanglement/src/services/LinkService.js b/platform/entanglement/src/services/LinkService.js index ae02675eeb..ab648611e1 100644 --- a/platform/entanglement/src/services/LinkService.js +++ b/platform/entanglement/src/services/LinkService.js @@ -45,7 +45,7 @@ define( if (parentCandidate.getId() === object.getId()) { return false; } - if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) { + if ((parentCandidate.getModel().composition || []).indexOf(object.getId()) !== -1) { return false; } return this.policyService.allow( diff --git a/platform/entanglement/test/services/LinkServiceSpec.js b/platform/entanglement/test/services/LinkServiceSpec.js index b9bbe62c58..7f430320a2 100644 --- a/platform/entanglement/test/services/LinkServiceSpec.js +++ b/platform/entanglement/test/services/LinkServiceSpec.js @@ -55,11 +55,18 @@ define( name: 'object' }); parentCandidate = domainObjectFactory({ - name: 'parentCandidate' + name: 'parentCandidate', + capabilities: { + composition: jasmine.createSpyObj( + 'composition', + ['invoke', 'add'] + ) + } }); validate = function () { return linkService.validate(object, parentCandidate); }; + mockPolicyService.allow.andReturn(true); }); it("does not allow invalid parentCandidate", function () { @@ -81,6 +88,23 @@ define( expect(validate()).toBe(false); }); + it("does not allow parents that contains object", function () { + object.id = 'abc'; + parentCandidate.id = 'xyz'; + parentCandidate.model.composition = ['abc']; + expect(validate()).toBe(false); + }); + + it("does not allow parents without composition", function () { + parentCandidate = domainObjectFactory({ + name: 'parentCandidate' + }); + object.id = 'abc'; + parentCandidate.id = 'xyz'; + parentCandidate.model.composition = undefined; + expect(validate()).toBe(false); + }); + describe("defers to policyService", function () { beforeEach(function () { object.id = 'abc'; @@ -121,16 +145,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 +162,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 +176,7 @@ define( mutation: { invoke: function (mutator) { mutator(parentModel); - return mutationPromise; + return new ControlledPromise(); } }, persistence: persistenceCapability, @@ -172,20 +197,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(); expect(parentObject.getCapability) .toHaveBeenCalledWith('persistence'); expect(persistenceCapability.persist).toHaveBeenCalled(); @@ -197,7 +219,7 @@ define( whenComplete = jasmine.createSpy('whenComplete'); returnPromise.then(whenComplete); - mutationPromise.resolve(); + addPromise.resolve(); persistencePromise.resolve(); compositionPromise.resolve([linkedObject]); expect(whenComplete).toHaveBeenCalledWith(linkedObject);