[Layout] Layout rebuilds after resize/reposition - Fixed potential race condition #32

This commit is contained in:
Henry 2015-11-25 18:53:37 -08:00
parent 424953c894
commit db7224486c
2 changed files with 48 additions and 18 deletions

View File

@ -45,7 +45,8 @@ define(
* @param {Scope} $scope the controller's Angular scope * @param {Scope} $scope the controller's Angular scope
*/ */
function LayoutController($scope) { function LayoutController($scope) {
var self = this; var self = this,
callbackCount = 0;
// Update grid size when it changed // Update grid size when it changed
function updateGridSize(layoutGrid) { function updateGridSize(layoutGrid) {
@ -91,23 +92,26 @@ define(
e.preventDefault(); 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 //Will fetch fully contextualized composed objects, and populate
// scope with them. // scope with them.
function refreshComposition(ids) { function refreshComposition() {
return getComposition($scope.domainObject) //Keep a track of how many composition callbacks have been made
.then(composeView) var thisCount = ++callbackCount;
.then(function(ids){self.layoutPanels(ids);});
$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 // End drag; we don't want to put $scope into this

View File

@ -33,7 +33,8 @@ define(
testConfiguration, testConfiguration,
controller, controller,
mockCompositionCapability, mockCompositionCapability,
mockComposition; mockComposition,
mockCompositionObjects;
function mockPromise(value){ function mockPromise(value){
return { return {
@ -67,6 +68,7 @@ define(
testModel = {}; testModel = {};
mockComposition = ["a", "b", "c"]; mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = { testConfiguration = {
panels: { panels: {
@ -77,7 +79,7 @@ define(
} }
}; };
mockCompositionCapability = mockPromise(mockComposition.map(mockDomainObject)); mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject"); mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel; mockScope.model = testModel;
@ -107,6 +109,30 @@ 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 () { it("provides styles for frames, from configuration", function () {
mockScope.$watchCollection.mostRecentCall.args[1](); mockScope.$watchCollection.mostRecentCall.args[1]();
expect(controller.getFrameStyle("a")).toEqual({ expect(controller.getFrameStyle("a")).toEqual({