Merged from Master

This commit is contained in:
Henry
2015-12-07 20:42:09 -08:00
53 changed files with 1798 additions and 481 deletions

View File

@@ -45,43 +45,8 @@ define(
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) {
var self = this;
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
// persistence from watchers during drags).
function shallowCopy(obj, keys) {
var copy = {};
keys.forEach(function (k) {
copy[k] = obj[k];
});
return copy;
}
/**
* Compute panel positions based on the layout's object model.
* Defined as member function to facilitate testing.
* @private
*/
LayoutController.prototype.layoutPanels = function layoutPanels (ids) {
var configuration = $scope.configuration || {};
// Pull panel positions from configuration
self.rawPositions =
shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
self.positions = {};
// Update width/height that we are tracking
self.gridSize =
($scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(function (id, index) {
self.populatePosition(id, index);
});
};
var self = this,
callbackCount = 0;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
@@ -127,23 +92,26 @@ define(
e.preventDefault();
}
function getComposition(domainObject){
return domainObject.useCapability('composition');
}
function composeView (composition){
$scope.composition = composition;
return composition.map(function (object) {
return object.getId();
}) || [];
}
//Will fetch fully contextualized composed objects, and populate
// scope with them.
function refreshComposition() {
return getComposition($scope.domainObject)
.then(composeView)
.then(self.layoutPanels);
//Keep a track of how many composition callbacks have been made
var thisCount = ++callbackCount;
$scope.domainObject.useCapability('composition').then(function(composition){
var ids;
//Is this callback for the most recent composition
// request? If not, discard it. Prevents race condition
if (thisCount === callbackCount){
ids = composition.map(function (object) {
return object.getId();
}) || [];
$scope.composition = composition;
self.layoutPanels(ids);
}
});
}
// End drag; we don't want to put $scope into this
@@ -176,7 +144,7 @@ define(
$scope.$watch("model.layoutGrid", updateGridSize);
// Update composed objects on screen, and position panes
$scope.$watch("model.composition", refreshComposition);
$scope.$watchCollection("model.composition", refreshComposition);
// Position panes where they are dropped
$scope.$on("mctDrop", handleDrop);
@@ -282,6 +250,43 @@ define(
}
};
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
// persistence from watchers during drags).
function shallowCopy(obj, keys) {
var copy = {};
keys.forEach(function (k) {
copy[k] = obj[k];
});
return copy;
}
/**
* Compute panel positions based on the layout's object model.
* Defined as member function to facilitate testing.
* @private
*/
LayoutController.prototype.layoutPanels = function (ids) {
var configuration = this.$scope.configuration || {},
self = this;
// Pull panel positions from configuration
this.rawPositions =
shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
this.positions = {};
// Update width/height that we are tracking
this.gridSize =
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(function (id, index) {
self.populatePosition(id, index);
});
};
/**
* End the active drag gesture. This will update the
* view configuration.

View File

@@ -33,7 +33,8 @@ define(
testConfiguration,
controller,
mockCompositionCapability,
mockComposition;
mockComposition,
mockCompositionObjects;
function mockPromise(value){
return {
@@ -57,7 +58,7 @@ define(
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$watch", "$on", "commit" ]
[ "$watch", "$watchCollection", "$on", "commit" ]
);
mockEvent = jasmine.createSpyObj(
'event',
@@ -67,6 +68,7 @@ define(
testModel = {};
mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = {
panels: {
@@ -77,7 +79,7 @@ define(
}
};
mockCompositionCapability = mockPromise(mockComposition.map(mockDomainObject));
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel;
@@ -91,14 +93,14 @@ define(
// Model changes will indicate that panel positions
// may have changed, for instance.
it("watches for changes to composition", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
"model.composition",
jasmine.any(Function)
);
});
it("Retrieves updated composition from composition capability", function () {
mockScope.$watch.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
"composition"
);
@@ -107,8 +109,32 @@ define(
);
});
it("Is robust to concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c", "d"],
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
firstCompositionCB,
secondCompositionCB;
spyOn(mockCompositionCapability, "then");
mockScope.$watchCollection.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
firstCompositionCB = mockCompositionCapability.then.calls[0].args[0];
secondCompositionCB = mockCompositionCapability.then.calls[1].args[0];
//Resolve promises in reverse order
secondCompositionCB(secondMockCompositionObjects);
firstCompositionCB(mockCompositionObjects);
//Expect the promise call that was initiated most recently to
// be the one used to populate scope, irrespective of order that
// it was eventually resolved
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
it("provides styles for frames, from configuration", function () {
mockScope.$watch.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
expect(controller.getFrameStyle("a")).toEqual({
top: "320px",
left: "640px",
@@ -121,7 +147,7 @@ define(
var styleB, styleC;
// b and c do not have configured positions
mockScope.$watch.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
styleB = controller.getFrameStyle("b");
styleC = controller.getFrameStyle("c");
@@ -138,7 +164,7 @@ define(
it("allows panels to be dragged", function () {
// Populate scope
mockScope.$watch.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
// Verify precondtion
expect(testConfiguration.panels.b).not.toBeDefined();
@@ -157,7 +183,7 @@ define(
it("invokes commit after drag", function () {
// Populate scope
mockScope.$watch.mostRecentCall.args[1]();
mockScope.$watchCollection.mostRecentCall.args[1]();
// Do a drag
controller.startDrag("b", [1, 1], [0, 0]);
@@ -218,7 +244,7 @@ define(
// White-boxy; we know which watch is which
mockScope.$watch.calls[0].args[1](testModel.layoutGrid);
mockScope.$watch.calls[1].args[1](testModel.composition);
mockScope.$watchCollection.calls[0].args[1](testModel.composition);
styleB = controller.getFrameStyle("b");

View File

@@ -146,6 +146,7 @@ define(
if (canvas.width !== canvas.offsetWidth ||
canvas.height !== canvas.offsetHeight) {
doDraw(scope.draw);
scope.$apply();
}
}
@@ -181,7 +182,7 @@ define(
canvas.addEventListener("webglcontextlost", fallbackFromWebGL);
// Check for resize, on a timer
activeInterval = $interval(drawIfResized, 1000);
activeInterval = $interval(drawIfResized, 1000, 0, false);
// Watch "draw" for external changes to the set of
// things to be drawn.

View File

@@ -45,8 +45,10 @@ define(
jasmine.createSpy("$interval");
mockLog =
jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
mockScope =
jasmine.createSpyObj("$scope", ["$watchCollection", "$on"]);
mockScope = jasmine.createSpyObj(
"$scope",
["$watchCollection", "$on", "$apply"]
);
mockElement =
jasmine.createSpyObj("element", ["find", "html"]);
mockInterval.cancel = jasmine.createSpy("cancelInterval");
@@ -152,7 +154,9 @@ define(
// Should track canvas size in an interval
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Number)
jasmine.any(Number),
0,
false
);
// Verify pre-condition