Merge branch 'master' of https://github.com/nasa/openmctweb into open72

Conflicts:
	platform/commonUI/general/res/css/theme-espresso.css
This commit is contained in:
slhale 2015-08-20 13:25:39 -07:00
commit dcfcfa74bb
23 changed files with 571 additions and 160 deletions

View File

@ -28,6 +28,7 @@
"start": "node app.js",
"test": "karma start --single-run",
"jshint": "jshint platform example || exit 0",
"watch": "karma start",
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs",
"docs": "npm run jsdoc ; npm run otherdoc"

View File

@ -69,8 +69,8 @@
{
"key": "grid-item",
"templateUrl": "templates/items/grid-item.html",
"uses": [ "type", "action" ],
"gestures": [ "info","menu" ]
"uses": [ "type", "action", "location" ],
"gestures": [ "info", "menu" ]
},
{
"key": "object-header",
@ -88,12 +88,12 @@
{
"key": "navigationService",
"implementation": "navigation/NavigationService.js"
},
},
{
"key": "creationService",
"implementation": "creation/CreationService.js",
"depends": [ "persistenceService", "$q", "$log" ]
}
}
],
"actions": [
{

View File

@ -27,12 +27,18 @@
<mct-include key="_checkbox"></mct-include>
</div>
<div class='right abs'>
<div class='ui-symbol icon alert hidden' onclick="alert('Not yet functional. When this is visible, it means that this object needs to be updated. Clicking will allow that action via a dialog.');">!</div>
<div class='ui-symbol icon l-icon-alert'></div>
<div class='ui-symbol icon profile' title="Shared">P</div>
</div>
</div>
<div class='item-main abs'>
<div class='ui-symbol icon lg abs item-type'>{{type.getGlyph()}}</div>
<div class='ui-symbol icon lg item-type'>
{{type.getGlyph()}}
<span
class="ui-symbol icon l-icon-link" title="This object is a link"
ng-show="location.isLink()"
></span>
</div>
<div class='ui-symbol icon abs item-open'>}</div>
</div>
<div class='bottom-bar bar abs'>
@ -44,4 +50,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -93,6 +93,12 @@ define(
});
}
// Store the location of an object relative to it's parent.
function addLocationToModel(modelId, model, parent) {
model.location = parent.getId();
return model;
}
// Create a new domain object with the provided model as a
// member of the specified parent's composition
function createObject(model, parent) {
@ -112,6 +118,7 @@ define(
return $q.when(
uuid()
).then(function (id) {
model = addLocationToModel(id, model, parent);
return doPersist(persistence.getSpace(), id, model);
}).then(function (id) {
return addToComposition(id, parent, persistence);

View File

@ -38,6 +38,7 @@ define(
mockMutationCapability,
mockPersistenceCapability,
mockCompositionCapability,
mockContextCapability,
mockCapabilities,
creationService;
@ -87,10 +88,15 @@ define(
"composition",
["invoke"]
);
mockContextCapability = jasmine.createSpyObj(
"context",
["getPath"]
);
mockCapabilities = {
mutation: mockMutationCapability,
persistence: mockPersistenceCapability,
composition: mockCompositionCapability
composition: mockCompositionCapability,
context: mockContextCapability
};
mockPersistenceService.createObject.andReturn(
@ -103,6 +109,7 @@ define(
mockParentObject.useCapability.andCallFake(function (key, value) {
return mockCapabilities[key].invoke(value);
});
mockParentObject.getId.andReturn('parentId');
mockPersistenceCapability.persist.andReturn(
mockPromise(true)
@ -194,7 +201,16 @@ define(
expect(mockLog.error).toHaveBeenCalled();
});
it("stores location on new domainObjects", function () {
var model = { name: "my model" },
objectPromise = creationService.createObject(
model,
mockParentObject
);
expect(model.location).toBe('parentId');
});
});
}
);
);

View File

@ -196,7 +196,7 @@
{
"key": "label",
"templateUrl": "templates/label.html",
"uses": [ "type" ],
"uses": [ "type", "location" ],
"gestures": [ "drag", "menu", "info" ]
},
{

View File

@ -172,11 +172,11 @@ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu,
/*********************************************** FORM ELEMENTS */
/*
@mixin invokeMenu($baseColor: $colorBodyFg) {
$c: $baseColor;
color: $c;
&:hover {
color: lighten($c, $ltGamma);
}
$c: $baseColor;
color: $c;
&:hover {
color: lighten($c, $ltGamma);
}
}
*/
/*****************************************************************************
@ -1049,27 +1049,27 @@ mct-container {
/*.s-limit-upr,
.s-limit-lwr {
$a: 0.5;
$l: 30%;
white-space: nowrap;
&:before {
display: inline-block;
font-family: symbolsfont;
font-size: 0.85em;
font-style: normal !important;
margin-right: $interiorMarginSm;
vertical-align: middle;
}
$a: 0.5;
$l: 30%;
white-space: nowrap;
&:before {
display: inline-block;
font-family: symbolsfont;
font-size: 0.85em;
font-style: normal !important;
margin-right: $interiorMarginSm;
vertical-align: middle;
}
}
.s-limit-upr {
&.s-limit-yellow { @include limit($colorLimitYellow, "\0000ed"); }
&.s-limit-red { @include limit($colorLimitRed, "\0000eb"); }
&.s-limit-yellow { @include limit($colorLimitYellow, "\0000ed"); }
&.s-limit-red { @include limit($colorLimitRed, "\0000eb"); }
}
.s-limit-lwr {
&.s-limit-yellow { @include limit($colorLimitYellow, "\0000ec"); }
&.s-limit-red { @include limit($colorLimitRed, "\0000ee"); }
&.s-limit-yellow { @include limit($colorLimitYellow, "\0000ec"); }
&.s-limit-red { @include limit($colorLimitRed, "\0000ee"); }
}*/
/* line 35, ../sass/_limits.scss */
[class*="s-limit"] {
@ -1772,11 +1772,11 @@ table {
/* line 132, ../sass/controls/_buttons.scss */
.icon-btn.pause-play,
.s-icon-btn.pause-play {
/* &.paused {
.icon {
@include pulse(500ms);
}
}*/ }
/* &.paused {
.icon {
@include pulse(500ms);
}
}*/ }
/* line 138, ../sass/controls/_buttons.scss */
.icon-btn.pause-play .icon:before,
.s-icon-btn.pause-play .icon:before {
@ -1899,32 +1899,32 @@ a.l-btn span {
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*.control {
// UNUSED?
&.view-control {
.icon {
display: inline-block;
margin: -1px 5px 1px 2px;
vertical-align: middle;
&.triangle-down {
margin: 2px 2px -2px 0px;
}
}
// UNUSED?
&.view-control {
.icon {
display: inline-block;
margin: -1px 5px 1px 2px;
vertical-align: middle;
&.triangle-down {
margin: 2px 2px -2px 0px;
}
}
.label {
display: inline-block;
font-size: 11px;
vertical-align: middle;
}
.label {
display: inline-block;
font-size: 11px;
vertical-align: middle;
}
.toggle {
@include border-radius(3px);
display: inline-block;
padding: 1px 6px 4px 4px;
&:hover {
background: rgba(white, 0.1);
}
}
}
.toggle {
@include border-radius(3px);
display: inline-block;
padding: 1px 6px 4px 4px;
&:hover {
background: rgba(white, 0.1);
}
}
}
}*/
/* line 51, ../sass/controls/_controls.scss */
.accordion {
@ -2161,23 +2161,23 @@ label.checkbox.custom {
border-top: 1px solid #575757;
color: #999;
display: inline-block;
/* height: $h;
line-height: $h;
&.dropdown {
padding-left: $p;
padding-right: $p;
}*/
/* &.context-available {
// An element like the invoke-menu triangle;
// Indicates that this element has a dropdown menu available;
// Currently unused
$c: $colorKey;
color: $c;
padding: 0 5px;
&:hover {
color: lighten($c, 10%);
}
}*/ }
/* height: $h;
line-height: $h;
&.dropdown {
padding-left: $p;
padding-right: $p;
}*/
/* &.context-available {
// An element like the invoke-menu triangle;
// Indicates that this element has a dropdown menu available;
// Currently unused
$c: $colorKey;
color: $c;
padding: 0 5px;
&:hover {
color: lighten($c, 10%);
}
}*/ }
/* line 162, ../sass/_mixins.scss */
.btn-menu:not(.disabled):hover {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzYzNjM2MyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzU3NTc1NyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
@ -2566,7 +2566,7 @@ label.checkbox.custom {
box-sizing: border-box;
border-top: 1px solid #737373;
color: #d9d9d9;
line-height: 1.4rem;
line-height: 1.5rem;
padding: 3px 10px 3px 30px;
white-space: nowrap; }
/* line 46, ../sass/controls/_menus.scss */

View File

@ -124,8 +124,8 @@ ul.tree {
transition: background-color 0.25s;
display: block;
font-size: 0.8em;
height: 1.4rem;
line-height: 1.4rem;
height: 1.5rem;
line-height: 1.5rem;
margin-bottom: 3px;
position: relative; }
/* line 39, ../sass/tree/_tree.scss */

View File

@ -146,7 +146,7 @@ $controlDisabledOpacity: 0.3;
$formLabelW: 20%;
$formInputH: 22px;
$formRowCtrlsH: 14px;
$menuLineH: 1.4rem;
$menuLineH: 1.5rem;
$scrollbarTrackSize: 10px;
$scrollbarTrackColorBg: rgba(#000, 0.4);
$btnStdH: 25px;

View File

@ -22,7 +22,13 @@
<span class="label s-label">
<span class='ui-symbol icon type-icon'>
{{type.getGlyph()}}
<span class='ui-symbol icon alert hidden'>!</span>
<span
class='ui-symbol icon l-icon-link'
ng-show="location.isLink()"
></span>
<span class='ui-symbol icon l-icon-alert'></span>
</span>
<span class='title-label'>
{{model.name}}
</span>
<span class='title-label'>{{model.name}}</span>
</span>

View File

@ -22,29 +22,29 @@
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span
class="tree-item menus-to-left"
ng-class="{selected: treeNode.isSelected()}"
>
class="tree-item menus-to-left"
ng-class="{selected: treeNode.isSelected()}"
>
<span
class='ui-symbol view-control'
ng-click="toggle.toggle(); treeNode.trackExpansion()"
ng-if="model.composition !== undefined"
>
class='ui-symbol view-control'
ng-click="toggle.toggle(); treeNode.trackExpansion()"
ng-if="model.composition !== undefined"
>
{{toggle.isActive() ? "v" : ">"}}
</span>
<mct-representation
key="'label'"
mct-object="domainObject"
ng-model="ngModel"
ng-click="ngModel.selectedObject = domainObject"
>
key="'label'"
mct-object="domainObject"
ng-model="ngModel"
ng-click="ngModel.selectedObject = domainObject"
>
</mct-representation>
</span>
<span
class="tree-item-subtree"
ng-show="toggle.isActive()"
ng-if="model.composition !== undefined"
>
class="tree-item-subtree"
ng-show="toggle.isActive()"
ng-if="model.composition !== undefined"
>
<mct-representation key="'subtree'"
ng-model="ngModel"

View File

@ -42,8 +42,12 @@ define(
* @constructor
*/
function RootModelProvider(roots, $q, $log) {
// Pull out identifiers to used as ROOT's
var ids = roots.map(function (root) { return root.id; }),
// Pull out identifiers to used as ROOT's, while setting locations.
var ids = roots.map(function (root) {
if (!root.model) { root.model = {}; }
root.model.location = 'ROOT';
return root.id;
}),
baseProvider = new StaticModelProvider(roots, $q, $log);
function addRoot(models) {
@ -77,4 +81,4 @@ define(
return RootModelProvider;
}
);
);

View File

@ -79,6 +79,12 @@ define(
expect(captured.b.someProperty).toEqual("Some Value B");
});
it("provides models with a location", function () {
provider.getModels(["a", "b"]).then(capture);
expect(captured.a.location).toBe('ROOT');
expect(captured.b.location).toBe('ROOT');
});
it("does not provide models which are not in extension declarations", function () {
provider.getModels(["c"]).then(capture);
@ -96,4 +102,4 @@ define(
});
}
);
);

View File

@ -37,6 +37,12 @@
"controllers": [
],
"capabilities": [
{
"key": "location",
"name": "Location Capability",
"description": "Provides a capability for retrieving the location of an object based upon it's context.",
"implementation": "capabilities/LocationCapability"
}
],
"services": [
{
@ -44,7 +50,7 @@
"name": "Move Service",
"description": "Provides a service for moving objects",
"implementation": "services/MoveService.js",
"depends": ["policyService", "linkService"]
"depends": ["policyService", "linkService", "$q"]
},
{
"key": "linkService",

View File

@ -0,0 +1,87 @@
/*global define */
define(
function () {
"use strict";
/**
* The location capability allows a domain object to know its current
* parent, and also know its original parent. When a domain object's
* current parent is its original parent, the object is considered an
* original, otherwise it's a link.
*
* @constructor
*/
function LocationCapability(domainObject) {
this.domainObject = domainObject;
return this;
}
/**
* Set the primary location (the parent id) of the current domain
* object.
*
* @param {String} location the primary location to persist.
* @returns {Promise} a promise that is resolved when the operation
* completes.
*/
LocationCapability.prototype.setPrimaryLocation = function (location) {
var capability = this;
return this.domainObject.useCapability(
'mutation',
function (model) {
model.location = location;
}
).then(function () {
return capability.domainObject
.getCapability('persistence')
.persist();
});
};
/**
* Returns the contextual location of the current domain object. Only
* valid for domain objects that have a context capability.
*
* @returns {String} the contextual location of the object; the id of
* its parent.
*/
LocationCapability.prototype.getContextualLocation = function () {
var context = this.domainObject.getCapability("context");
if (!context) {
return;
}
return context.getParent().getId();
};
/**
* Returns true if the domainObject is a link, false if it's an
* original.
*
* @returns {Boolean}
*/
LocationCapability.prototype.isLink = function () {
var model = this.domainObject.getModel();
return model.location !== this.getContextualLocation();
};
/**
* Returns true if the domainObject is an original, false if it's a
* link.
*
* @returns {Boolean}
*/
LocationCapability.prototype.isOriginal = function () {
return !this.isLink();
};
function createLocationCapability(domainObject) {
return new LocationCapability(domainObject);
}
return createLocationCapability;
}
);

View File

@ -66,6 +66,17 @@ define(
}
}).then(function () {
return parentObject.getCapability('persistence').persist();
}).then(function getObjectWithNewContext() {
return parentObject
.useCapability('composition')
.then(function (children) {
var i;
for (i = 0; i < children.length; i += 1) {
if (children[i].getId() === object.getId()) {
return children[i];
}
}
});
});
}
};

View File

@ -25,13 +25,13 @@
define(
function () {
"use strict";
/**
* MoveService provides an interface for moving objects from one
* location to another. It also provides a method for determining if
* an object can be copied to a specific location.
*/
function MoveService(policyService, linkService) {
function MoveService(policyService, linkService, $q) {
return {
/**
* Returns `true` if `object` can be moved into
@ -69,6 +69,25 @@ define(
perform: function (object, parentObject) {
return linkService
.perform(object, parentObject)
.then(function (objectInNewContext) {
var newLocationCapability = objectInNewContext
.getCapability('location'),
oldLocationCapability = object
.getCapability('location');
if (!newLocationCapability ||
!oldLocationCapability) {
return;
}
if (oldLocationCapability.isOriginal()) {
return newLocationCapability.setPrimaryLocation(
newLocationCapability
.getContextualLocation()
);
}
})
.then(function () {
return object
.getCapability('action')

View File

@ -0,0 +1,78 @@
/*global define,spyOn */
define(
function () {
/**
* An instrumented promise implementation for better control of promises
* during tests.
*
*/
function ControlledPromise() {
this.resolveHandlers = [];
this.rejectHandlers = [];
spyOn(this, 'then').andCallThrough();
}
/**
* Resolve the promise, passing the supplied value to all resolve
* handlers.
*/
ControlledPromise.prototype.resolve = function(value) {
this.resolveHandlers.forEach(function(handler) {
handler(value);
});
};
/**
* Reject the promise, passing the supplied value to all rejection
* handlers.
*/
ControlledPromise.prototype.reject = function(value) {
this.rejectHandlers.forEach(function(handler) {
handler(value);
});
};
/**
* Standard promise.then, returns a promise that support chaining.
* TODO: Need to support resolve/reject handlers that return promises.
*/
ControlledPromise.prototype.then = function (onResolve, onReject) {
var returnPromise = new ControlledPromise();
if (onResolve) {
this.resolveHandlers.push(function(resolveWith) {
var chainResult = onResolve(resolveWith);
if (chainResult && chainResult.then) {
// chainResult is a promise, resolve when it resolves.
chainResult.then(function(pipedResult) {
return returnPromise.resolve(pipedResult);
});
} else {
returnPromise.resolve(chainResult);
}
});
}
if (onReject) {
this.rejectHandlers.push(function(rejectWith) {
var chainResult = onReject(rejectWith);
if (chainResult && chainResult.then) {
chainResult.then(function(pipedResult) {
returnPromise.reject(pipedResult);
});
} else {
returnPromise.reject(chainResult);
}
});
}
return returnPromise;
};
return ControlledPromise;
}
);

View File

@ -0,0 +1,94 @@
/*global define,describe,it,expect,beforeEach,jasmine */
define(
[
'../../src/capabilities/LocationCapability',
'../DomainObjectFactory',
'../ControlledPromise'
],
function (LocationCapability, domainObjectFactory, ControlledPromise) {
describe("LocationCapability", function () {
describe("instantiated with domain object", function () {
var locationCapability,
persistencePromise,
mutationPromise,
domainObject;
beforeEach(function () {
domainObject = domainObjectFactory({
capabilities: {
context: {
getParent: function() {
return domainObjectFactory({id: 'root'});
}
},
persistence: jasmine.createSpyObj(
'persistenceCapability',
['persist']
),
mutation: jasmine.createSpyObj(
'mutationCapability',
['invoke']
)
}
});
persistencePromise = new ControlledPromise();
domainObject.capabilities.persistence.persist.andReturn(
persistencePromise
);
mutationPromise = new ControlledPromise();
domainObject.capabilities.mutation.invoke.andCallFake(
function (mutator) {
return mutationPromise.then(function () {
mutator(domainObject.model);
});
}
);
locationCapability = new LocationCapability(domainObject);
});
it("returns contextual location", function () {
expect(locationCapability.getContextualLocation())
.toBe('root');
});
it("knows when the object is an original", function () {
domainObject.model.location = 'root';
expect(locationCapability.isOriginal()).toBe(true);
expect(locationCapability.isLink()).toBe(false);
});
it("knows when the object is a link.", function () {
domainObject.model.location = 'different-root';
expect(locationCapability.isLink()).toBe(true);
expect(locationCapability.isOriginal()).toBe(false);
});
it("can persist location", function () {
var persistResult = locationCapability
.setPrimaryLocation('root'),
whenComplete = jasmine.createSpy('whenComplete');
persistResult.then(whenComplete);
expect(domainObject.model.location).not.toBeDefined();
mutationPromise.resolve();
expect(domainObject.model.location).toBe('root');
expect(whenComplete).not.toHaveBeenCalled();
expect(domainObject.capabilities.persistence.persist)
.toHaveBeenCalled();
persistencePromise.resolve();
expect(whenComplete).toHaveBeenCalled();
});
});
});
}
);

View File

@ -25,9 +25,10 @@
define(
[
'../../src/services/LinkService',
'../DomainObjectFactory'
'../DomainObjectFactory',
'../ControlledPromise'
],
function (LinkService, domainObjectFactory) {
function (LinkService, domainObjectFactory, ControlledPromise) {
"use strict";
describe("LinkService", function () {
@ -50,7 +51,6 @@ define(
validate;
beforeEach(function () {
object = domainObjectFactory({
name: 'object'
});
@ -118,20 +118,29 @@ define(
describe("perform", function () {
var object,
linkedObject,
parentModel,
parentObject,
mutationPromise,
compositionPromise,
persistencePromise,
compositionCapability,
persistenceCapability;
beforeEach(function () {
mutationPromise = jasmine.createSpyObj(
'promise',
['then']
);
mutationPromise = new ControlledPromise();
compositionPromise = new ControlledPromise();
persistencePromise = new ControlledPromise();
persistenceCapability = jasmine.createSpyObj(
'persistenceCapability',
['persist']
);
persistenceCapability.persist.andReturn(persistencePromise);
compositionCapability = jasmine.createSpyObj(
'compositionCapability',
['invoke']
);
compositionCapability.invoke.andReturn(compositionPromise);
parentModel = {
composition: []
};
@ -145,7 +154,8 @@ define(
return mutationPromise;
}
},
persistence: persistenceCapability
persistence: persistenceCapability,
composition: compositionCapability
}
});
@ -154,7 +164,11 @@ define(
id: 'xyz'
});
parentObject.getCapability.andReturn(persistenceCapability);
linkedObject = domainObjectFactory({
name: 'object-link',
id: 'xyz'
});
});
@ -171,12 +185,23 @@ define(
it("persists parent", function () {
linkService.perform(object, parentObject);
expect(mutationPromise.then).toHaveBeenCalled();
mutationPromise.then.calls[0].args[0]();
mutationPromise.resolve();
expect(parentObject.getCapability)
.toHaveBeenCalledWith('persistence');
expect(persistenceCapability.persist).toHaveBeenCalled();
});
it("returns object representing new link", function () {
var returnPromise, whenComplete;
returnPromise = linkService.perform(object, parentObject);
whenComplete = jasmine.createSpy('whenComplete');
returnPromise.then(whenComplete);
mutationPromise.resolve();
persistencePromise.resolve();
compositionPromise.resolve([linkedObject]);
expect(whenComplete).toHaveBeenCalledWith(linkedObject);
});
});
});
}

View File

@ -23,7 +23,10 @@
/*global define,jasmine */
define(
function () {
[
'../ControlledPromise'
],
function (ControlledPromise) {
"use strict";
/**
@ -47,7 +50,7 @@ define(
* var whenLinked = jasmine.createSpy('whenLinked');
* linkService.perform(object, parentObject).then(whenLinked);
* expect(whenLinked).not.toHaveBeenCalled();
* linkService.perform.mostRecentCall.resolve('someArg');
* linkService.perform.mostRecentCall.promise.resolve('someArg');
* expect(whenLinked).toHaveBeenCalledWith('someArg');
* ```
*/
@ -62,33 +65,19 @@ define(
]
);
mockLinkService.perform.andCallFake(function () {
var performPromise,
callExtensions,
spy;
mockLinkService.perform.andCallFake(function (object, newParent) {
var performPromise = new ControlledPromise();
performPromise = jasmine.createSpyObj(
'performPromise',
['then']
);
this.perform.mostRecentCall.promise = performPromise;
this.perform.calls[this.perform.calls.length - 1].promise =
performPromise;
callExtensions = {
promise: performPromise,
resolve: function (resolveWith) {
performPromise.then.calls.forEach(function (call) {
call.args[0](resolveWith);
});
return performPromise.then(function (overrideObject) {
if (overrideObject) {
return overrideObject;
}
};
spy = this.perform;
Object.keys(callExtensions).forEach(function (key) {
spy.mostRecentCall[key] = callExtensions[key];
spy.calls[spy.calls.length - 1][key] = callExtensions[key];
return object;
});
return performPromise;
});
return mockLinkService;

View File

@ -25,9 +25,15 @@ define(
[
'../../src/services/MoveService',
'../services/MockLinkService',
'../DomainObjectFactory'
'../DomainObjectFactory',
'../ControlledPromise'
],
function (MoveService, MockLinkService, domainObjectFactory) {
function (
MoveService,
MockLinkService,
domainObjectFactory,
ControlledPromise
) {
"use strict";
describe("MoveService", function () {
@ -140,8 +146,11 @@ define(
describe("perform", function () {
var object,
parentObject,
actionCapability;
newParent,
actionCapability,
locationCapability,
locationPromise,
moveResult;
beforeEach(function () {
actionCapability = jasmine.createSpyObj(
@ -149,24 +158,34 @@ define(
['perform']
);
locationCapability = jasmine.createSpyObj(
'locationCapability',
[
'isOriginal',
'setPrimaryLocation',
'getContextualLocation'
]
);
locationPromise = new ControlledPromise();
locationCapability.setPrimaryLocation
.andReturn(locationPromise);
object = domainObjectFactory({
name: 'object',
capabilities: {
action: actionCapability
action: actionCapability,
location: locationCapability
}
});
parentObject = domainObjectFactory({
name: 'parentObject'
});
moveService.perform(object, parentObject);
moveResult = moveService.perform(object, newParent);
});
it("links object to parentObject", function () {
it("links object to newParent", function () {
expect(linkService.perform).toHaveBeenCalledWith(
object,
parentObject
newParent
);
});
@ -175,12 +194,48 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("removes object when link is completed", function () {
linkService.perform.mostRecentCall.resolve();
expect(object.getCapability)
.toHaveBeenCalledWith('action');
expect(actionCapability.perform)
.toHaveBeenCalledWith('remove');
describe("when moving an original", function () {
beforeEach(function () {
locationCapability.getContextualLocation
.andReturn('new-location');
locationCapability.isOriginal.andReturn(true);
linkService.perform.mostRecentCall.promise.resolve();
});
it("updates location", function () {
expect(locationCapability.setPrimaryLocation)
.toHaveBeenCalledWith('new-location');
});
describe("after location update", function () {
beforeEach(function () {
locationPromise.resolve();
});
it("removes object from parent", function () {
expect(actionCapability.perform)
.toHaveBeenCalledWith('remove');
});
});
});
describe("when moving a link", function () {
beforeEach(function () {
locationCapability.isOriginal.andReturn(false);
linkService.perform.mostRecentCall.promise.resolve();
});
it("does not update location", function () {
expect(locationCapability.setPrimaryLocation)
.not
.toHaveBeenCalled();
});
it("removes object from parent", function () {
expect(actionCapability.perform)
.toHaveBeenCalledWith('remove');
});
});
});

View File

@ -5,5 +5,6 @@
"services/CopyService",
"services/LinkService",
"services/MoveService",
"services/LocationService"
"services/LocationService",
"capabilities/LocationCapability"
]