diff --git a/platform/core/src/actions/ActionProvider.js b/platform/core/src/actions/ActionProvider.js index 9441492964..995dd68bc3 100644 --- a/platform/core/src/actions/ActionProvider.js +++ b/platform/core/src/actions/ActionProvider.js @@ -32,7 +32,7 @@ define( // declarative bindings, as well as context, // unless the action has defined its own. if (!action.getMetadata) { - metadata = Object.create(Action.definition); + metadata = Object.create(Action.definition || {}); metadata.context = context; action.getMetadata = function () { return metadata; diff --git a/platform/core/src/actions/LoggingActionDecorator.js b/platform/core/src/actions/LoggingActionDecorator.js index d4b1281715..afabd699a6 100644 --- a/platform/core/src/actions/LoggingActionDecorator.js +++ b/platform/core/src/actions/LoggingActionDecorator.js @@ -20,13 +20,14 @@ define( // it emits a log message whenever performed. function addLogging(action) { var logAction = Object.create(action), - domainObject = - action.getMetadata().context.domainObject; + metadata = action.getMetadata() || {}, + context = metadata.context || {}, + domainObject = context.domainObject; logAction.perform = function () { $log.info([ "Performing action ", - action.getMetadata().key, + metadata.key, " upon ", domainObject && domainObject.getId() ].join("")); diff --git a/platform/core/test/actions/ActionAggregatorSpec.js b/platform/core/test/actions/ActionAggregatorSpec.js index 66ec46840d..cb06f4374e 100644 --- a/platform/core/test/actions/ActionAggregatorSpec.js +++ b/platform/core/test/actions/ActionAggregatorSpec.js @@ -9,7 +9,45 @@ define( "use strict"; describe("Action aggregator", function () { + var mockAggregators, + aggregator; + function createMockActionProvider(actions, i) { + var spy = jasmine.createSpyObj("agg" + i, [ "getActions" ]); + spy.getActions.andReturn(actions); + return spy; + } + + beforeEach(function () { + mockAggregators = [ + ["a", "b"], + ["c"], + ["d", "e", "f"] + ].map(createMockActionProvider); + aggregator = new ActionAggregator(mockAggregators); + }); + + it("consolidates results from aggregated services", function () { + expect(aggregator.getActions()).toEqual( + ["a", "b", "c", "d", "e", "f"] + ); + }); + + it("passes context along to all aggregated services", function () { + var context = { domainObject: "something" }; + + // Verify precondition + mockAggregators.forEach(function (mockAgg) { + expect(mockAgg.getActions).not.toHaveBeenCalled(); + }); + + aggregator.getActions(context); + + // All services should have been called with this context + mockAggregators.forEach(function (mockAgg) { + expect(mockAgg.getActions).toHaveBeenCalledWith(context); + }); + }); }); } ); \ No newline at end of file diff --git a/platform/core/test/actions/ActionCapabilitySpec.js b/platform/core/test/actions/ActionCapabilitySpec.js index e85b50a27b..f1cdd3a64b 100644 --- a/platform/core/test/actions/ActionCapabilitySpec.js +++ b/platform/core/test/actions/ActionCapabilitySpec.js @@ -9,6 +9,70 @@ define( "use strict"; describe("The action capability", function () { + var mockQ, + mockAction, + mockActionService, + mockDomainObject, + capability; + + beforeEach(function () { + mockAction = jasmine.createSpyObj( + "action", + [ "perform", "getMetadata" ] + ); + mockActionService = jasmine.createSpyObj( + "actionService", + [ "getActions" ] + ); + mockQ = jasmine.createSpyObj( + "$q", + [ "when" ] + ); + mockDomainObject = jasmine.createSpyObj( + "domainObject", + [ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ] + ); + + mockActionService.getActions.andReturn([mockAction, {}]); + + capability = new ActionCapability( + mockQ, + mockActionService, + mockDomainObject + ); + }); + + it("retrieves action for domain objects from the action service", function () { + // Verify precondition + expect(mockActionService.getActions).not.toHaveBeenCalled(); + + // Call getActions + expect(capability.getActions("some key")).toEqual([mockAction, {}]); + + // Verify interaction + expect(mockActionService.getActions).toHaveBeenCalledWith({ + key: "some key", + domainObject: mockDomainObject + }); + }); + + it("promises the result of performed actions", function () { + var mockPromise = jasmine.createSpyObj("promise", [ "then" ]); + mockQ.when.andReturn(mockPromise); + mockAction.perform.andReturn("the action's result"); + + // Verify precondition + expect(mockAction.perform).not.toHaveBeenCalled(); + + // Perform via capability + expect(capability.perform()).toEqual(mockPromise); + + // Verify that the action's result is what was wrapped + expect(mockQ.when).toHaveBeenCalledWith("the action's result"); + + }); + + }); } diff --git a/platform/core/test/actions/ActionProviderSpec.js b/platform/core/test/actions/ActionProviderSpec.js index 64a3a03ded..ab898f28a1 100644 --- a/platform/core/test/actions/ActionProviderSpec.js +++ b/platform/core/test/actions/ActionProviderSpec.js @@ -9,6 +9,112 @@ define( "use strict"; describe("The action provider", function () { + var actions, + actionProvider; + + function SimpleAction() { + return { perform: function () { return "simple"; } }; + } + + function CategorizedAction() { + return { perform: function () { return "categorized"; } }; + } + CategorizedAction.category = "someCategory"; + + function KeyedAction() { + return { perform: function () { return "keyed"; } }; + } + KeyedAction.key = "someKey"; + + function CategorizedKeyedAction() { + return { perform: function () { return "both"; } }; + } + CategorizedKeyedAction.key = "someKey"; + CategorizedKeyedAction.category = "someCategory"; + + function MetadataAction() { + return { + perform: function () { return "metadata"; }, + getMetadata: function () { return "custom metadata"; } + }; + } + MetadataAction.key = "metadata"; + + beforeEach(function () { + actions = [ + SimpleAction, + CategorizedAction, + KeyedAction, + CategorizedKeyedAction, + MetadataAction + ]; + actionProvider = new ActionProvider(actions); + }); + + it("exposes provided action extensions", function () { + var provided = actionProvider.getActions(); + + // Should have gotten all actions + expect(provided.length).toEqual(actions.length); + + // Verify that this was the action we expected + expect(provided[0].perform()).toEqual("simple"); + }); + + it("matches provided actions by key", function () { + var provided = actionProvider.getActions({ key: "someKey" }); + + // Only two should have matched + expect(provided.length).toEqual(2); + + // Verify that this was the action we expected + expect(provided[0].perform()).toEqual("keyed"); + }); + + it("matches provided actions by category", function () { + var provided = actionProvider.getActions({ category: "someCategory" }); + + // Only two should have matched + expect(provided.length).toEqual(2); + + // Verify that this was the action we expected + expect(provided[0].perform()).toEqual("categorized"); + }); + + it("matches provided actions by both category and key", function () { + var provided = actionProvider.getActions({ + category: "someCategory", + key: "someKey" + }); + + // Only two should have matched + expect(provided.length).toEqual(1); + + // Verify that this was the action we expected + expect(provided[0].perform()).toEqual("both"); + }); + + it("adds a getMetadata method when none is defined", function () { + var provided = actionProvider.getActions({ + category: "someCategory", + key: "someKey" + }); + + // Should be defined, even though the action didn't define this + expect(provided[0].getMetadata).toBeDefined(); + + // Should have static fields, plus context + expect(provided[0].getMetadata().context).toEqual({ + key: "someKey", + category: "someCategory" + }); + + }); + + it("does not override defined getMetadata methods", function () { + var provided = actionProvider.getActions({ key: "metadata" }); + expect(provided[0].getMetadata()).toEqual("custom metadata"); + }); }); } diff --git a/platform/core/test/actions/LoggingActionDecoratorSpec.js b/platform/core/test/actions/LoggingActionDecoratorSpec.js index 9e307487a2..5dbe869c80 100644 --- a/platform/core/test/actions/LoggingActionDecoratorSpec.js +++ b/platform/core/test/actions/LoggingActionDecoratorSpec.js @@ -9,6 +9,45 @@ define( "use strict"; describe("The logging action decorator", function () { + var mockLog, + mockAction, + mockActionService, + decorator; + + beforeEach(function () { + mockAction = jasmine.createSpyObj( + "action", + [ "perform", "getMetadata" ] + ); + mockActionService = jasmine.createSpyObj( + "actionService", + [ "getActions" ] + ); + mockLog = jasmine.createSpyObj( + "$log", + [ "error", "warn", "info", "debug" ] + ); + + mockActionService.getActions.andReturn([mockAction]); + + decorator = new LoggingActionDecorator( + mockLog, + mockActionService + ); + }); + + it("logs when actions are performed", function () { + // Verify precondition + expect(mockLog.info).not.toHaveBeenCalled(); + + // Perform an action, retrieved through the decorator + decorator.getActions()[0].perform(); + + // That should have been logged. + expect(mockLog.info).toHaveBeenCalled(); + }); + + }); }