[New Edit Mode] Added additional tests for Seamless Edit Mode #464

This commit is contained in:
Henry 2016-01-11 12:56:27 -08:00
parent c71aa43581
commit 2c4d53883a
11 changed files with 202 additions and 85 deletions

View File

@ -50,6 +50,7 @@ define(
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
@ -82,11 +83,11 @@ define(
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getCapability", "getModel", "useCapability" ]
[ "getId", "hasCapability", "getCapability", "getModel", "useCapability" ]
);
mockNextObject = jasmine.createSpyObj(
"nextObject",
[ "getId", "getCapability", "getModel", "useCapability" ]
[ "getId", "hasCapability", "getCapability", "getModel", "useCapability" ]
);
mockObjectService.getObjects.andReturn(mockPromise({
@ -98,9 +99,13 @@ define(
mockDomainObject.useCapability.andReturn(mockPromise([
mockNextObject
]));
mockNextObject.useCapability.andReturn(undefined);
mockNextObject.getId.andReturn("next");
mockNextObject.hasCapability.andReturn(false);
mockDomainObject.getId.andReturn("mine");
mockDomainObject.hasCapability.andReturn(false);
controller = new BrowseController(
mockScope,

View File

@ -38,7 +38,7 @@
{
"key": "edit",
"implementation": "actions/EditAction.js",
"depends": [ "$location", "$q", "navigationService", "$log" ],
"depends": [ "$q", "navigationService", "$log" ],
"description": "Edit this object.",
"category": "view-control",
"glyph": "p"
@ -67,7 +67,7 @@
"implementation": "actions/SaveAction.js",
"name": "Save",
"description": "Save changes made to these objects.",
"depends": [ "$location", "navigationService" ],
"depends": [ "navigationService" ],
"priority": "mandatory"
},
{
@ -76,7 +76,7 @@
"implementation": "actions/CancelAction.js",
"name": "Cancel",
"description": "Discard changes made to these objects.",
"depends": [ "$location", "navigationService" ]
"depends": ["navigationService" ]
}
],
"policies": [

View File

@ -33,9 +33,8 @@ define(
* @memberof platform/commonUI/edit
* @implements {Action}
*/
function CancelAction($location, navigationService, context) {
function CancelAction(navigationService, context) {
this.domainObject = context.domainObject;
this.$location = $location;
this.navigationService = navigationService;
}
@ -47,7 +46,6 @@ define(
*/
CancelAction.prototype.perform = function () {
var domainObject = this.domainObject,
$location = this.$location,
navigationService = this.navigationService;
// Look up the object's "editor.completion" capability;
@ -82,7 +80,7 @@ define(
CancelAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor");
domainObject.getCapability('status').get('editing');
};
return CancelAction;

View File

@ -46,7 +46,7 @@ define(
* @constructor
* @implements {Action}
*/
function EditAction($location, $q, navigationService, $log, context) {
function EditAction($q, navigationService, $log, context) {
var domainObject = (context || {}).domainObject;
// We cannot enter Edit mode if we have no domain object to
@ -63,17 +63,20 @@ define(
}
this.domainObject = domainObject;
this.$location = $location;
this.navigationService = navigationService;
this.$q = $q;
}
EditAction.prototype.createEditableObject = function(domainObject) {
return new EditableDomainObject(domainObject, this.$q);
};
/**
* Enter edit mode.
*/
EditAction.prototype.perform = function () {
this.domainObject.getCapability('status').set('editing', true);
this.navigationService.setNavigation(new EditableDomainObject(this.domainObject, this.$q));
this.navigationService.setNavigation(this.createEditableObject(this.domainObject));
};
/**

View File

@ -34,9 +34,8 @@ define(
* @implements {Action}
* @memberof platform/commonUI/edit
*/
function SaveAction($location, navigationService, context) {
function SaveAction(navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.$location = $location;
this.navigationService = navigationService;
}
@ -49,8 +48,6 @@ define(
*/
SaveAction.prototype.perform = function () {
var domainObject = this.domainObject,
$location = this.$location,
urlService = this.urlService,
navigationService = this.navigationService;
// Invoke any save behavior introduced by the editor capability;
@ -65,10 +62,6 @@ define(
// UI, which will have been pushed atop the Browise UI.)
function returnToBrowse(nonEditableDomainObject) {
navigationService.setNavigation(nonEditableDomainObject);
/*return $location.path(urlService.urlForLocation(
"browse",
domainObject
));*/
}
return doSave().then(returnToBrowse);
@ -83,7 +76,7 @@ define(
SaveAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor");
domainObject.getCapability("status").get("editing");
};
return SaveAction;

View File

@ -104,7 +104,7 @@ define(
return saveChanges().then(function(){
domainObject.getCapability('status').set('editing', false);
return domainObject;
})
});
};
/**
@ -131,7 +131,7 @@ define(
EditorCapability.prototype.getDomainObject = function () {
return this.domainObject;
}
};
return EditorCapability;
}

View File

@ -27,10 +27,11 @@ define(
"use strict";
describe("The Cancel action", function () {
var mockLocation,
mockDomainObject,
var mockDomainObject,
mockCapabilities,
mockEditorCapability,
mockUrlService,
mockStatusCapability,
mockNavigationService,
actionContext,
action;
@ -43,45 +44,50 @@ define(
}
beforeEach(function () {
mockLocation = jasmine.createSpyObj(
"$location",
[ "path" ]
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getCapability", "hasCapability" ]
[ "getCapability" ]
);
mockEditorCapability = jasmine.createSpyObj(
"editor",
[ "save", "cancel" ]
);
mockUrlService = jasmine.createSpyObj(
"urlService",
["urlForLocation"]
mockStatusCapability = jasmine.createSpyObj(
"status",
[ "get"]
);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
["setNavigation"]
);
mockCapabilities = {
"editor": mockEditorCapability,
"status": mockStatusCapability
};
actionContext = {
domainObject: mockDomainObject
};
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
mockEditorCapability.cancel.andReturn(mockPromise(true));
mockDomainObject.getCapability.andCallFake(function(capability){
return mockCapabilities[capability];
});
action = new CancelAction(mockLocation, mockUrlService, actionContext);
mockEditorCapability.cancel.andReturn(mockPromise(mockDomainObject));
mockStatusCapability.get.andReturn(true);
action = new CancelAction( mockNavigationService, actionContext);
});
it("only applies to domain object with an editor capability", function () {
it("only applies to domain object that is being edited", function () {
expect(CancelAction.appliesTo(actionContext)).toBeTruthy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(undefined);
mockStatusCapability.get.andReturn(false);
expect(CancelAction.appliesTo(actionContext)).toBeFalsy();
});
it("invokes the editor capability's save functionality when performed", function () {
it("invokes the editor capability's cancel functionality when" +
" performed", function () {
// Verify precondition
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
action.perform();
@ -95,8 +101,8 @@ define(
it("returns to browse when performed", function () {
action.perform();
expect(mockLocation.path).toHaveBeenCalledWith(
mockUrlService.urlForLocation("browse", mockDomainObject)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(
mockDomainObject
);
});
});

View File

@ -19,27 +19,41 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/*global define,describe,it,expect,beforeEach,jasmine,spyOn*/
define(
["../../src/actions/EditAction"],
[
"../../src/actions/EditAction"
],
function (EditAction) {
"use strict";
describe("The Edit action", function () {
var mockLocation,
mockNavigationService,
var mockNavigationService,
mockLog,
mockDomainObject,
mockStatusCapability,
mockType,
actionContext,
mockCapabilities,
mockQ,
action;
function mockPromise(value) {
return {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
beforeEach(function () {
mockLocation = jasmine.createSpyObj(
"$location",
[ "path" ]
mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
[ "get", "set" ]
);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
[ "setNavigation", "getNavigation" ]
@ -50,20 +64,41 @@ define(
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
[ "getId", "getModel", "getCapability", "hasCapability" ]
);
mockType = jasmine.createSpyObj(
"type",
[ "hasFeature" ]
);
mockDomainObject.getCapability.andReturn(mockType);
mockQ = jasmine.createSpyObj(
"q",
[ "when", "all" ]
);
mockQ.when.andReturn(function(value){
return mockPromise(value);
});
mockQ.all.andReturn(mockPromise(undefined));
mockDomainObject.getCapability.andCallFake(function(capability){
return mockCapabilities[capability];
});
mockDomainObject.getModel.andReturn({});
mockDomainObject.getId.andReturn("testId");
mockStatusCapability.get.andReturn(false);
mockType.hasFeature.andReturn(true);
mockCapabilities = {
"status": mockStatusCapability,
"type": mockType
};
actionContext = { domainObject: mockDomainObject };
action = new EditAction(
mockLocation,
mockQ,
mockNavigationService,
mockLog,
actionContext
@ -77,15 +112,30 @@ define(
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
});
it("changes URL path to edit mode when performed", function () {
it("is only applicable when domain object is not in edit mode", function () {
// Indicates whether object is in edit mode
mockStatusCapability.get.andReturn(false);
expect(EditAction.appliesTo(actionContext)).toBeTruthy();
mockStatusCapability.get.andReturn(true);
expect(EditAction.appliesTo(actionContext)).toBeFalsy();
});
it("navigates to editable domain object", function () {
spyOn(action, 'createEditableObject');
action.perform();
expect(mockLocation.path).toHaveBeenCalledWith("/edit");
expect(mockNavigationService.setNavigation).toHaveBeenCalled();
expect(action.createEditableObject).toHaveBeenCalled();
});
it("ensures that the edited object is navigated-to", function () {
var navigatedObject;
action.perform();
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockDomainObject);
navigatedObject = mockNavigationService.setNavigation.mostRecentCall.args[0];
expect(navigatedObject.getId())
.toEqual(mockDomainObject.getId());
expect(navigatedObject).not.toBe(mockDomainObject);
});
it("logs a warning if constructed when inapplicable", function () {
@ -94,7 +144,7 @@ define(
// Should not have hit an exception...
new EditAction(
mockLocation,
mockQ,
mockNavigationService,
mockLog,
{}
@ -104,8 +154,6 @@ define(
expect(mockLog.warn).toHaveBeenCalled();
// And should not have had other interactions
expect(mockLocation.path)
.not.toHaveBeenCalled();
expect(mockNavigationService.setNavigation)
.not.toHaveBeenCalled();
});

View File

@ -27,10 +27,11 @@ define(
"use strict";
describe("The Save action", function () {
var mockLocation,
mockDomainObject,
var mockDomainObject,
mockNavigationService,
mockStatusCapability,
mockEditorCapability,
mockUrlService,
mockCapabilities,
actionContext,
action;
@ -43,10 +44,6 @@ define(
}
beforeEach(function () {
mockLocation = jasmine.createSpyObj(
"$location",
[ "path" ]
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getCapability", "hasCapability" ]
@ -55,30 +52,39 @@ define(
"editor",
[ "save", "cancel" ]
);
mockUrlService = jasmine.createSpyObj(
"urlService",
["urlForLocation"]
mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
["get"]
);
mockNavigationService = jasmine.createSpyObj(
"mockNavigationService",
[ "setNavigation"]
);
mockCapabilities = {
"editor": mockEditorCapability,
"status": mockStatusCapability
};
actionContext = {
domainObject: mockDomainObject
};
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
mockEditorCapability.save.andReturn(mockPromise(true));
mockDomainObject.getCapability.andCallFake(function(capability){
return mockCapabilities[capability];
});
action = new SaveAction(mockLocation, mockUrlService, actionContext);
mockEditorCapability.save.andReturn(mockPromise(mockDomainObject));
mockStatusCapability.get.andReturn(true);
action = new SaveAction(mockNavigationService, actionContext);
});
it("only applies to domain object with an editor capability", function () {
it("only applies to domain object that is being edited", function () {
expect(SaveAction.appliesTo(actionContext)).toBeTruthy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(undefined);
mockStatusCapability.get.andReturn(false);
expect(SaveAction.appliesTo(actionContext)).toBeFalsy();
});
@ -96,8 +102,8 @@ define(
it("returns to browse when performed", function () {
action.perform();
expect(mockLocation.path).toHaveBeenCalledWith(
mockUrlService.urlForLocation("browse", mockDomainObject)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(
mockDomainObject
);
});
});

View File

@ -28,6 +28,8 @@ define(
describe("The editor capability", function () {
var mockPersistence,
mockStatusCapability,
mockCapabilities,
mockEditableObject,
mockDomainObject,
mockCache,
@ -40,6 +42,14 @@ define(
"persistence",
[ "persist" ]
);
mockStatusCapability = jasmine.createSpyObj(
"status",
[ "set" ]
);
mockCapabilities = {
"persistence":mockPersistence,
"status": mockStatusCapability
};
mockEditableObject = {
getModel: function () { return model; }
};
@ -53,7 +63,9 @@ define(
);
mockCallback = jasmine.createSpy("callback");
mockDomainObject.getCapability.andReturn(mockPersistence);
mockDomainObject.getCapability.andCallFake(function(capability){
return mockCapabilities[capability];
});
model = { someKey: "some value", x: 42 };
@ -96,6 +108,19 @@ define(
});
});
it("resets the editing status on successful save", function () {
capability.save().then(mockCallback);
// Wait for promise to resolve
waitsFor(function () {
return mockCallback.calls.length > 0;
}, 250);
runs(function () {
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
});
});
it("has no interactions on cancel", function () {
capability.cancel().then(mockCallback);
@ -111,6 +136,19 @@ define(
});
});
it("resets editing status on cancel", function () {
capability.cancel().then(mockCallback);
// Wait for promise to resolve
waitsFor(function () {
return mockCallback.calls.length > 0;
}, 250);
runs(function () {
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
});
});
});
}

View File

@ -32,7 +32,9 @@ define(
mockScope,
testRepresentation,
mockDomainObject,
mockStatusCapability,
mockPersistence,
mockCapabilities,
representer;
function mockPromise(value) {
@ -57,11 +59,21 @@ define(
]);
mockPersistence =
jasmine.createSpyObj("persistence", ["persist"]);
mockStatusCapability = jasmine.createSpyObj("domainObject", [
"get"]);
mockCapabilities = {
"persistence": mockPersistence,
"status": mockStatusCapability
};
mockDomainObject.getModel.andReturn({});
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.useCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockPersistence);
mockStatusCapability.get.andReturn(true);
mockDomainObject.getCapability.andCallFake(function(capability){
return mockCapabilities[capability];
});
representer = new EditRepresenter(mockQ, mockLog, mockScope);
representer.represent(testRepresentation, mockDomainObject);
@ -98,6 +110,14 @@ define(
});
});
it("sets an 'editMode' flag on scope if the object is editable", function() {
expect(mockScope.editMode).toBe(true);
mockStatusCapability.get.andReturn(false);
representer.represent(testRepresentation, mockDomainObject);
expect(mockScope.editMode).toBeFalsy();
});
});
}