mirror of
https://github.com/nasa/openmct.git
synced 2024-12-28 08:58:52 +00:00
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:
commit
dcfcfa74bb
@ -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"
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -196,7 +196,7 @@
|
||||
{
|
||||
"key": "label",
|
||||
"templateUrl": "templates/label.html",
|
||||
"uses": [ "type" ],
|
||||
"uses": [ "type", "location" ],
|
||||
"gestures": [ "drag", "menu", "info" ]
|
||||
},
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -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(
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -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",
|
||||
|
87
platform/entanglement/src/capabilities/LocationCapability.js
Normal file
87
platform/entanglement/src/capabilities/LocationCapability.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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')
|
||||
|
78
platform/entanglement/test/ControlledPromise.js
Normal file
78
platform/entanglement/test/ControlledPromise.js
Normal 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;
|
||||
|
||||
}
|
||||
);
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -5,5 +5,6 @@
|
||||
"services/CopyService",
|
||||
"services/LinkService",
|
||||
"services/MoveService",
|
||||
"services/LocationService"
|
||||
"services/LocationService",
|
||||
"capabilities/LocationCapability"
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user