mirror of
https://github.com/nasa/openmct.git
synced 2025-06-10 03:11:39 +00:00
[Time Conductor] Prevent route change when time conductor values change (#1342)
* [Time Conductor] Prevent route change on setting search parameters. fixes #1341 * [Inspector] Fixed incorrect listener deregistration which was causing errors on scope destruction * Bare route is redirect to browse * [Browse] handle routing without breaking $route Manage route transitions such that route changes are properly prevented and navigation events occur while still updating the url. Resolves a number of issues where path and search updates had to be supported in a very hacky manner. https://github.com/nasa/openmct/pull/1342 * [URL] Set search without hacks Changes in previous commit allow the search parameters to be modified without accidentally triggering a page reload. https://github.com/nasa/openmct/pull/1342 * [Views] Update on location changes If the user has a bookmark or tries to change the current view of an object by specifying view=someView as a search parameter, the change would not previously take effect. This resolves that bug. https://github.com/nasa/openmct/pull/1342 * [TC] Set query params to undefined Instead of setting params to null, which would eventually result in those parameters equaling undefined, set them to undefined to skip the extra step. https://github.com/nasa/openmct/pull/1342 * [Instantiate] Instantiate objects with context Add context to instantiate objects so that they can be navigated to for editing. https://github.com/nasa/openmct/pull/1342 * [Tests] Update specs Update specs to match new expectations. * [Style] Fix style * [TC] Remove unused dependency Remove $route dependency from time conductor controller as it was not being used. Resolves review comments. https://github.com/nasa/openmct/pull/1342#pullrequestreview-11449260
This commit is contained in:
parent
b2da0cb12f
commit
45de84c183
@ -72,14 +72,13 @@ define([
|
|||||||
"extensions": {
|
"extensions": {
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"when": "/browse/:ids*",
|
"when": "/browse/:ids*?",
|
||||||
"template": browseTemplate,
|
"template": browseTemplate,
|
||||||
"reloadOnSearch": false
|
"reloadOnSearch": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"when": "",
|
"when": "",
|
||||||
"template": browseTemplate,
|
"redirectTo": "/browse/"
|
||||||
"reloadOnSearch": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
|
@ -28,8 +28,6 @@ define(
|
|||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
|
|
||||||
var ROOT_ID = "ROOT";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The BrowseController is used to populate the initial scope in Browse
|
* The BrowseController is used to populate the initial scope in Browse
|
||||||
* mode. It loads the root object from the objectService and makes it
|
* mode. It loads the root object from the objectService and makes it
|
||||||
@ -49,74 +47,21 @@ define(
|
|||||||
urlService,
|
urlService,
|
||||||
defaultPath
|
defaultPath
|
||||||
) {
|
) {
|
||||||
var path = [ROOT_ID].concat(
|
var initialPath = ($route.current.params.ids || defaultPath).split("/");
|
||||||
($route.current.params.ids || defaultPath).split("/")
|
|
||||||
);
|
|
||||||
|
|
||||||
function updateRoute(domainObject) {
|
var currentIds = $route.current.params.ids;
|
||||||
var priorRoute = $route.current,
|
|
||||||
// Act as if params HADN'T changed to avoid page reload
|
|
||||||
unlisten;
|
|
||||||
|
|
||||||
unlisten = $scope.$on('$locationChangeSuccess', function () {
|
$scope.treeModel = {
|
||||||
// Checks path to make sure /browse/ is at front
|
selectedObject: {}
|
||||||
// if so, change $route.current
|
};
|
||||||
if ($location.path().indexOf("/browse/") === 0) {
|
|
||||||
$route.current = priorRoute;
|
|
||||||
}
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
// urlService.urlForLocation used to adjust current
|
|
||||||
// path to new, addressed, path based on
|
|
||||||
// domainObject
|
|
||||||
$location.path(urlService.urlForLocation("browse", domainObject));
|
|
||||||
|
|
||||||
|
function idsForObject(domainObject) {
|
||||||
|
return urlService
|
||||||
|
.urlForLocation("", domainObject)
|
||||||
|
.replace('/', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function setScopeObjects(domainObject, navigationAllowed) {
|
// Find an object in an array of objects.
|
||||||
if (navigationAllowed) {
|
|
||||||
$scope.navigatedObject = domainObject;
|
|
||||||
$scope.treeModel.selectedObject = domainObject;
|
|
||||||
updateRoute(domainObject);
|
|
||||||
} else {
|
|
||||||
//If navigation was unsuccessful (ie. blocked), reset
|
|
||||||
// the selected object in the tree to the currently
|
|
||||||
// navigated object
|
|
||||||
$scope.treeModel.selectedObject = $scope.navigatedObject ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for updating the in-scope reference to the object
|
|
||||||
// that is currently navigated-to.
|
|
||||||
function setNavigation(domainObject) {
|
|
||||||
if (domainObject === $scope.navigatedObject) {
|
|
||||||
//do nothing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (domainObject) {
|
|
||||||
domainObject.getCapability("action").perform("navigate").then(setScopeObjects.bind(undefined, domainObject));
|
|
||||||
} else {
|
|
||||||
setScopeObjects(domainObject, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo(domainObject) {
|
|
||||||
|
|
||||||
// Check if an object has been navigated-to already...
|
|
||||||
// If not, or if an ID path has been explicitly set in the URL,
|
|
||||||
// navigate to the URL-specified object.
|
|
||||||
if (!navigationService.getNavigation() || $route.current.params.ids) {
|
|
||||||
// If not, pick a default as the last
|
|
||||||
// root-level component (usually "mine")
|
|
||||||
navigationService.setNavigation(domainObject);
|
|
||||||
$scope.navigatedObject = domainObject;
|
|
||||||
} else {
|
|
||||||
// Otherwise, just expose the currently navigated object.
|
|
||||||
$scope.navigatedObject = navigationService.getNavigation();
|
|
||||||
updateRoute($scope.navigatedObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findObject(domainObjects, id) {
|
function findObject(domainObjects, id) {
|
||||||
var i;
|
var i;
|
||||||
for (i = 0; i < domainObjects.length; i += 1) {
|
for (i = 0; i < domainObjects.length; i += 1) {
|
||||||
@ -126,63 +71,92 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to the domain object identified by path[index],
|
// helper, fetch a single object from the object service.
|
||||||
// which we expect to find in the composition of the passed
|
function getObject(id) {
|
||||||
// domain object.
|
return objectService.getObjects([id])
|
||||||
function doNavigate(domainObject, index) {
|
.then(function (results) {
|
||||||
var composition = domainObject.useCapability("composition");
|
return results[id];
|
||||||
if (composition) {
|
|
||||||
composition.then(function (c) {
|
|
||||||
var nextObject = findObject(c, path[index]);
|
|
||||||
if (nextObject) {
|
|
||||||
if (index + 1 >= path.length) {
|
|
||||||
navigateTo(nextObject);
|
|
||||||
} else {
|
|
||||||
doNavigate(nextObject, index + 1);
|
|
||||||
}
|
|
||||||
} else if (index === 1 && c.length > 0) {
|
|
||||||
// Roots are in a top-level container that we don't
|
|
||||||
// want to be selected, so if we couldn't find an
|
|
||||||
// object at the path we wanted, at least select
|
|
||||||
// one of its children.
|
|
||||||
navigateTo(c[c.length - 1]);
|
|
||||||
} else {
|
|
||||||
// Couldn't find the next element of the path
|
|
||||||
// so navigate to the last path object we did find
|
|
||||||
navigateTo(domainObject);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Similar to above case; this object has no composition,
|
|
||||||
// so navigate to it instead of subsequent path elements.
|
|
||||||
navigateTo(domainObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the root object, put it in the scope.
|
// recursively locate and return an object inside of a container
|
||||||
// Also, load its immediate children, and (possibly)
|
// via a path. If at any point in the recursion it fails to find
|
||||||
// navigate to one of them, so that navigation state has
|
// the next object, it will return the parent.
|
||||||
// a useful initial value.
|
function findViaComposition(containerObject, path) {
|
||||||
objectService.getObjects([path[0]]).then(function (objects) {
|
var nextId = path.shift();
|
||||||
$scope.domainObject = objects[path[0]];
|
if (!nextId) {
|
||||||
doNavigate($scope.domainObject, 1);
|
return containerObject;
|
||||||
|
}
|
||||||
|
return containerObject.useCapability('composition')
|
||||||
|
.then(function (composees) {
|
||||||
|
var nextObject = findObject(composees, nextId);
|
||||||
|
if (!nextObject) {
|
||||||
|
return containerObject;
|
||||||
|
}
|
||||||
|
if (!nextObject.hasCapability('composition')) {
|
||||||
|
return nextObject;
|
||||||
|
}
|
||||||
|
return findViaComposition(nextObject, path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToObject(desiredObject) {
|
||||||
|
$scope.navigatedObject = desiredObject;
|
||||||
|
$scope.treeModel.selectedObject = desiredObject;
|
||||||
|
navigationService.setNavigation(desiredObject);
|
||||||
|
currentIds = idsForObject(desiredObject);
|
||||||
|
$route.current.pathParams.ids = currentIds;
|
||||||
|
$location.path('/browse/' + currentIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToPath(path) {
|
||||||
|
return getObject('ROOT')
|
||||||
|
.then(function (root) {
|
||||||
|
return findViaComposition(root, path);
|
||||||
|
})
|
||||||
|
.then(navigateToObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getObject('ROOT')
|
||||||
|
.then(function (root) {
|
||||||
|
$scope.domainObject = root;
|
||||||
|
navigateToPath(initialPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Provide a model for the tree to modify
|
// Handle navigation events from view service. Only navigates
|
||||||
$scope.treeModel = {
|
// if path has changed.
|
||||||
selectedObject: navigationService.getNavigation()
|
function navigateDirectlyToModel(domainObject) {
|
||||||
};
|
var newIds = idsForObject(domainObject);
|
||||||
|
if (currentIds !== newIds) {
|
||||||
|
currentIds = newIds;
|
||||||
|
navigateToObject(domainObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for changes in navigation state.
|
// Listen for changes in navigation state.
|
||||||
navigationService.addListener(setNavigation);
|
navigationService.addListener(navigateDirectlyToModel);
|
||||||
|
|
||||||
// Also listen for changes which come from the tree. Changes in
|
// Also listen for changes which come from the tree. Changes in
|
||||||
// the tree will trigger a change in browse navigation state.
|
// the tree will trigger a change in browse navigation state.
|
||||||
$scope.$watch("treeModel.selectedObject", setNavigation);
|
$scope.$watch("treeModel.selectedObject", navigateDirectlyToModel);
|
||||||
|
|
||||||
|
|
||||||
|
// Listen for route changes which are caused by browser events
|
||||||
|
// (e.g. bookmarks to pages in OpenMCT) and prevent them. Instead,
|
||||||
|
// navigate to the path ourselves, which results in it being
|
||||||
|
// properly set.
|
||||||
|
$scope.$on('$routeChangeStart', function (event, route) {
|
||||||
|
if (route.$$route === $route.current.$$route &&
|
||||||
|
route.pathParams.ids !== $route.current.pathParams.ids) {
|
||||||
|
event.preventDefault();
|
||||||
|
navigateToPath(route.pathParams.ids.split('/'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Clean up when the scope is destroyed
|
// Clean up when the scope is destroyed
|
||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
navigationService.removeListener(setNavigation);
|
navigationService.removeListener(navigateDirectlyToModel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,24 +51,16 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateQueryParam(viewKey) {
|
function updateQueryParam(viewKey) {
|
||||||
var unlisten,
|
if (viewKey && $location.search().view !== viewKey) {
|
||||||
priorRoute = $route.current;
|
|
||||||
|
|
||||||
if (viewKey) {
|
|
||||||
$location.search('view', viewKey);
|
$location.search('view', viewKey);
|
||||||
unlisten = $scope.$on('$locationChangeSuccess', function () {
|
|
||||||
// Checks path to make sure /browse/ is at front
|
|
||||||
// if so, change $route.current
|
|
||||||
if ($location.path().indexOf("/browse/") === 0) {
|
|
||||||
$route.current = priorRoute;
|
|
||||||
}
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$watch('domainObject', setViewForDomainObject);
|
$scope.$watch('domainObject', setViewForDomainObject);
|
||||||
$scope.$watch('representation.selected.key', updateQueryParam);
|
$scope.$watch('representation.selected.key', updateQueryParam);
|
||||||
|
$scope.$on('$locationChangeSuccess', function () {
|
||||||
|
setViewForDomainObject($scope.domainObject);
|
||||||
|
});
|
||||||
|
|
||||||
$scope.doAction = function (action) {
|
$scope.doAction = function (action) {
|
||||||
return $scope[action] && $scope[action]();
|
return $scope[action] && $scope[action]();
|
||||||
|
@ -64,11 +64,11 @@ define(
|
|||||||
attachStatusListener(domainObject);
|
attachStatusListener(domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationListener = navigationService.addListener(attachStatusListener);
|
navigationService.addListener(attachStatusListener);
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
statusListener();
|
statusListener();
|
||||||
navigationListener();
|
navigationService.removeListener(attachStatusListener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,18 +35,17 @@ define(
|
|||||||
mockNavigationService,
|
mockNavigationService,
|
||||||
mockRootObject,
|
mockRootObject,
|
||||||
mockUrlService,
|
mockUrlService,
|
||||||
mockDomainObject,
|
mockDefaultRootObject,
|
||||||
|
mockOtherDomainObject,
|
||||||
mockNextObject,
|
mockNextObject,
|
||||||
testDefaultRoot,
|
testDefaultRoot,
|
||||||
mockActionCapability,
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
function mockPromise(value) {
|
function waitsForNavigation() {
|
||||||
return {
|
var calls = mockNavigationService.setNavigation.calls.length;
|
||||||
then: function (callback) {
|
waitsFor(function () {
|
||||||
return mockPromise(callback(value));
|
return mockNavigationService.setNavigation.calls.length > calls ;
|
||||||
}
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function instantiateController() {
|
function instantiateController() {
|
||||||
@ -68,15 +67,27 @@ define(
|
|||||||
"$scope",
|
"$scope",
|
||||||
["$on", "$watch"]
|
["$on", "$watch"]
|
||||||
);
|
);
|
||||||
mockRoute = { current: { params: {} } };
|
mockRoute = { current: { params: {}, pathParams: {} } };
|
||||||
mockLocation = jasmine.createSpyObj(
|
|
||||||
"$location",
|
|
||||||
["path"]
|
|
||||||
);
|
|
||||||
mockUrlService = jasmine.createSpyObj(
|
mockUrlService = jasmine.createSpyObj(
|
||||||
"urlService",
|
"urlService",
|
||||||
["urlForLocation"]
|
["urlForLocation"]
|
||||||
);
|
);
|
||||||
|
mockUrlService.urlForLocation.andCallFake(function (mode, object) {
|
||||||
|
if (object === mockDefaultRootObject) {
|
||||||
|
return [mode, testDefaultRoot].join('/');
|
||||||
|
}
|
||||||
|
if (object === mockOtherDomainObject) {
|
||||||
|
return [mode, 'other'].join('/');
|
||||||
|
}
|
||||||
|
if (object === mockNextObject) {
|
||||||
|
return [mode, testDefaultRoot, 'next'].join('/');
|
||||||
|
}
|
||||||
|
throw new Error('Tried to get url for unexpected object');
|
||||||
|
});
|
||||||
|
mockLocation = jasmine.createSpyObj(
|
||||||
|
"$location",
|
||||||
|
["path"]
|
||||||
|
);
|
||||||
mockObjectService = jasmine.createSpyObj(
|
mockObjectService = jasmine.createSpyObj(
|
||||||
"objectService",
|
"objectService",
|
||||||
["getObjects"]
|
["getObjects"]
|
||||||
@ -91,62 +102,78 @@ define(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockRootObject = jasmine.createSpyObj(
|
mockRootObject = jasmine.createSpyObj(
|
||||||
"domainObject",
|
"rootObjectContainer",
|
||||||
["getId", "getCapability", "getModel", "useCapability"]
|
["getId", "getCapability", "getModel", "useCapability", "hasCapability"]
|
||||||
);
|
);
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
mockDefaultRootObject = jasmine.createSpyObj(
|
||||||
"domainObject",
|
"defaultRootObject",
|
||||||
["getId", "getCapability", "getModel", "useCapability"]
|
["getId", "getCapability", "getModel", "useCapability", "hasCapability"]
|
||||||
|
);
|
||||||
|
mockOtherDomainObject = jasmine.createSpyObj(
|
||||||
|
"otherDomainObject",
|
||||||
|
["getId", "getCapability", "getModel", "useCapability", "hasCapability"]
|
||||||
);
|
);
|
||||||
mockNextObject = jasmine.createSpyObj(
|
mockNextObject = jasmine.createSpyObj(
|
||||||
"nextObject",
|
"nestedDomainObject",
|
||||||
["getId", "getCapability", "getModel", "useCapability"]
|
["getId", "getCapability", "getModel", "useCapability", "hasCapability"]
|
||||||
);
|
);
|
||||||
|
mockObjectService.getObjects.andReturn(Promise.resolve({
|
||||||
mockObjectService.getObjects.andReturn(mockPromise({
|
|
||||||
ROOT: mockRootObject
|
ROOT: mockRootObject
|
||||||
}));
|
}));
|
||||||
mockRootObject.useCapability.andReturn(mockPromise([
|
mockRootObject.useCapability.andReturn(Promise.resolve([
|
||||||
mockDomainObject
|
mockOtherDomainObject,
|
||||||
|
mockDefaultRootObject
|
||||||
]));
|
]));
|
||||||
mockDomainObject.useCapability.andReturn(mockPromise([
|
mockRootObject.hasCapability.andReturn(true);
|
||||||
|
mockDefaultRootObject.useCapability.andReturn(Promise.resolve([
|
||||||
mockNextObject
|
mockNextObject
|
||||||
]));
|
]));
|
||||||
|
mockDefaultRootObject.hasCapability.andReturn(true);
|
||||||
|
mockOtherDomainObject.hasCapability.andReturn(false);
|
||||||
mockNextObject.useCapability.andReturn(undefined);
|
mockNextObject.useCapability.andReturn(undefined);
|
||||||
|
mockNextObject.hasCapability.andReturn(false);
|
||||||
mockNextObject.getId.andReturn("next");
|
mockNextObject.getId.andReturn("next");
|
||||||
mockDomainObject.getId.andReturn(testDefaultRoot);
|
mockDefaultRootObject.getId.andReturn(testDefaultRoot);
|
||||||
|
|
||||||
mockActionCapability = jasmine.createSpyObj('actionCapability', ['perform']);
|
|
||||||
|
|
||||||
instantiateController();
|
instantiateController();
|
||||||
|
waitsForNavigation();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses composition to set the navigated object, if there is none", function () {
|
it("uses composition to set the navigated object, if there is none", function () {
|
||||||
instantiateController();
|
instantiateController();
|
||||||
|
waitsForNavigation();
|
||||||
|
runs(function () {
|
||||||
expect(mockNavigationService.setNavigation)
|
expect(mockNavigationService.setNavigation)
|
||||||
.toHaveBeenCalledWith(mockDomainObject);
|
.toHaveBeenCalledWith(mockDefaultRootObject);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("navigates to a root-level object, even when default path is not found", function () {
|
it("navigates to a root-level object, even when default path is not found", function () {
|
||||||
mockDomainObject.getId
|
mockDefaultRootObject.getId
|
||||||
.andReturn("something-other-than-the-" + testDefaultRoot);
|
.andReturn("something-other-than-the-" + testDefaultRoot);
|
||||||
instantiateController();
|
instantiateController();
|
||||||
|
|
||||||
|
waitsForNavigation();
|
||||||
|
runs(function () {
|
||||||
expect(mockNavigationService.setNavigation)
|
expect(mockNavigationService.setNavigation)
|
||||||
.toHaveBeenCalledWith(mockDomainObject);
|
.toHaveBeenCalledWith(mockDefaultRootObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
//
|
||||||
it("does not try to override navigation", function () {
|
it("does not try to override navigation", function () {
|
||||||
mockNavigationService.getNavigation.andReturn(mockDomainObject);
|
mockNavigationService.getNavigation.andReturn(mockDefaultRootObject);
|
||||||
instantiateController();
|
instantiateController();
|
||||||
expect(mockScope.navigatedObject).toBe(mockDomainObject);
|
waitsForNavigation();
|
||||||
|
expect(mockScope.navigatedObject).toBe(mockDefaultRootObject);
|
||||||
});
|
});
|
||||||
|
//
|
||||||
it("updates scope when navigated object changes", function () {
|
it("updates scope when navigated object changes", function () {
|
||||||
// Should have registered a listener - call it
|
// Should have registered a listener - call it
|
||||||
mockNavigationService.addListener.mostRecentCall.args[0](
|
mockNavigationService.addListener.mostRecentCall.args[0](
|
||||||
mockDomainObject
|
mockOtherDomainObject
|
||||||
);
|
);
|
||||||
expect(mockScope.navigatedObject).toEqual(mockDomainObject);
|
expect(mockScope.navigatedObject).toEqual(mockOtherDomainObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -166,10 +193,13 @@ define(
|
|||||||
it("uses route parameters to choose initially-navigated object", function () {
|
it("uses route parameters to choose initially-navigated object", function () {
|
||||||
mockRoute.current.params.ids = testDefaultRoot + "/next";
|
mockRoute.current.params.ids = testDefaultRoot + "/next";
|
||||||
instantiateController();
|
instantiateController();
|
||||||
|
waitsForNavigation();
|
||||||
|
runs(function () {
|
||||||
expect(mockScope.navigatedObject).toBe(mockNextObject);
|
expect(mockScope.navigatedObject).toBe(mockNextObject);
|
||||||
expect(mockNavigationService.setNavigation)
|
expect(mockNavigationService.setNavigation)
|
||||||
.toHaveBeenCalledWith(mockNextObject);
|
.toHaveBeenCalledWith(mockNextObject);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("handles invalid IDs by going as far as possible", function () {
|
it("handles invalid IDs by going as far as possible", function () {
|
||||||
// Idea here is that if we get a bad path of IDs,
|
// Idea here is that if we get a bad path of IDs,
|
||||||
@ -177,9 +207,13 @@ define(
|
|||||||
// it hits an invalid ID.
|
// it hits an invalid ID.
|
||||||
mockRoute.current.params.ids = testDefaultRoot + "/junk";
|
mockRoute.current.params.ids = testDefaultRoot + "/junk";
|
||||||
instantiateController();
|
instantiateController();
|
||||||
expect(mockScope.navigatedObject).toBe(mockDomainObject);
|
waitsForNavigation();
|
||||||
|
runs(function () {
|
||||||
|
expect(mockScope.navigatedObject).toBe(mockDefaultRootObject);
|
||||||
expect(mockNavigationService.setNavigation)
|
expect(mockNavigationService.setNavigation)
|
||||||
.toHaveBeenCalledWith(mockDomainObject);
|
.toHaveBeenCalledWith(mockDefaultRootObject);
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles compositionless objects by going as far as possible", function () {
|
it("handles compositionless objects by going as far as possible", function () {
|
||||||
@ -188,84 +222,33 @@ define(
|
|||||||
// should stop at it since remaining IDs cannot be loaded.
|
// should stop at it since remaining IDs cannot be loaded.
|
||||||
mockRoute.current.params.ids = testDefaultRoot + "/next/junk";
|
mockRoute.current.params.ids = testDefaultRoot + "/next/junk";
|
||||||
instantiateController();
|
instantiateController();
|
||||||
|
waitsForNavigation();
|
||||||
|
runs(function () {
|
||||||
expect(mockScope.navigatedObject).toBe(mockNextObject);
|
expect(mockScope.navigatedObject).toBe(mockNextObject);
|
||||||
expect(mockNavigationService.setNavigation)
|
expect(mockNavigationService.setNavigation)
|
||||||
.toHaveBeenCalledWith(mockNextObject);
|
.toHaveBeenCalledWith(mockNextObject);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("updates the displayed route to reflect current navigation", function () {
|
it("updates the displayed route to reflect current navigation", function () {
|
||||||
var mockContext = jasmine.createSpyObj('context', ['getPath']),
|
// In order to trigger a route update and not a route change,
|
||||||
mockUnlisten = jasmine.createSpy('unlisten'),
|
// the current route must be updated before location.path is
|
||||||
mockMode = "browse";
|
// called.
|
||||||
|
expect(mockRoute.current.pathParams.ids)
|
||||||
mockContext.getPath.andReturn(
|
.not
|
||||||
[mockRootObject, mockDomainObject, mockNextObject]
|
.toBe(testDefaultRoot + '/next');
|
||||||
);
|
mockLocation.path.andCallFake(function () {
|
||||||
|
expect(mockRoute.current.pathParams.ids)
|
||||||
//Return true from navigate action
|
.toBe(testDefaultRoot + '/next');
|
||||||
mockActionCapability.perform.andReturn(mockPromise(true));
|
|
||||||
|
|
||||||
mockNextObject.getCapability.andCallFake(function (c) {
|
|
||||||
return (c === 'context' && mockContext) ||
|
|
||||||
(c === 'action' && mockActionCapability);
|
|
||||||
});
|
});
|
||||||
mockScope.$on.andReturn(mockUnlisten);
|
|
||||||
// Provide a navigation change
|
|
||||||
mockNavigationService.addListener.mostRecentCall.args[0](
|
mockNavigationService.addListener.mostRecentCall.args[0](
|
||||||
mockNextObject
|
mockNextObject
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allows the path index to be checked
|
|
||||||
// prior to setting $route.current
|
|
||||||
mockLocation.path.andReturn("/browse/");
|
|
||||||
|
|
||||||
mockNavigationService.setNavigation.andReturn(true);
|
|
||||||
mockActionCapability.perform.andReturn(mockPromise(true));
|
|
||||||
|
|
||||||
// Exercise the Angular workaround
|
|
||||||
mockNavigationService.addListener.mostRecentCall.args[0]();
|
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
|
||||||
expect(mockUnlisten).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// location.path to be called with the urlService's
|
|
||||||
// urlFor function with the next domainObject and mode
|
|
||||||
expect(mockLocation.path).toHaveBeenCalledWith(
|
expect(mockLocation.path).toHaveBeenCalledWith(
|
||||||
mockUrlService.urlForLocation(mockMode, mockNextObject)
|
'/browse/' + testDefaultRoot + '/next'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("after successful navigation event sets the selected tree " +
|
|
||||||
"object", function () {
|
|
||||||
mockScope.navigatedObject = mockDomainObject;
|
|
||||||
mockNavigationService.setNavigation.andReturn(true);
|
|
||||||
|
|
||||||
mockActionCapability.perform.andReturn(mockPromise(true));
|
|
||||||
mockNextObject.getCapability.andReturn(mockActionCapability);
|
|
||||||
|
|
||||||
//Simulate a change in selected tree object
|
|
||||||
mockScope.treeModel = {selectedObject: mockDomainObject};
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
|
|
||||||
|
|
||||||
expect(mockScope.treeModel.selectedObject).toBe(mockNextObject);
|
|
||||||
expect(mockScope.treeModel.selectedObject).not.toBe(mockDomainObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("after failed navigation event resets the selected tree" +
|
|
||||||
" object", function () {
|
|
||||||
mockScope.navigatedObject = mockDomainObject;
|
|
||||||
|
|
||||||
//Return false from navigation action
|
|
||||||
mockActionCapability.perform.andReturn(mockPromise(false));
|
|
||||||
mockNextObject.getCapability.andReturn(mockActionCapability);
|
|
||||||
|
|
||||||
//Simulate a change in selected tree object
|
|
||||||
mockScope.treeModel = {selectedObject: mockDomainObject};
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
|
|
||||||
|
|
||||||
expect(mockScope.treeModel.selectedObject).not.toBe(mockNextObject);
|
|
||||||
expect(mockScope.treeModel.selectedObject).toBe(mockDomainObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -29,7 +29,6 @@ define(
|
|||||||
var mockScope,
|
var mockScope,
|
||||||
mockLocation,
|
mockLocation,
|
||||||
mockRoute,
|
mockRoute,
|
||||||
mockUnlisten,
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
// Utility function; look for a $watch on scope and fire it
|
// Utility function; look for a $watch on scope and fire it
|
||||||
@ -51,9 +50,7 @@ define(
|
|||||||
"$location",
|
"$location",
|
||||||
["path", "search"]
|
["path", "search"]
|
||||||
);
|
);
|
||||||
mockUnlisten = jasmine.createSpy("unlisten");
|
mockLocation.search.andReturn({});
|
||||||
|
|
||||||
mockScope.$on.andReturn(mockUnlisten);
|
|
||||||
|
|
||||||
controller = new BrowseObjectController(
|
controller = new BrowseObjectController(
|
||||||
mockScope,
|
mockScope,
|
||||||
@ -69,10 +66,6 @@ define(
|
|||||||
// Allows the path index to be checked
|
// Allows the path index to be checked
|
||||||
// prior to setting $route.current
|
// prior to setting $route.current
|
||||||
mockLocation.path.andReturn("/browse/");
|
mockLocation.path.andReturn("/browse/");
|
||||||
|
|
||||||
// Exercise the Angular workaround
|
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
|
||||||
expect(mockUnlisten).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the active view from query parameters", function () {
|
it("sets the active view from query parameters", function () {
|
||||||
|
@ -68,7 +68,13 @@ define(
|
|||||||
this.instantiateFn = this.instantiateFn ||
|
this.instantiateFn = this.instantiateFn ||
|
||||||
this.$injector.get("instantiate");
|
this.$injector.get("instantiate");
|
||||||
|
|
||||||
return this.instantiateFn(model, id);
|
var newObject = this.instantiateFn(model, id);
|
||||||
|
|
||||||
|
this.contextualizeFn = this.contextualizeFn ||
|
||||||
|
this.$injector.get("contextualize");
|
||||||
|
|
||||||
|
|
||||||
|
return this.contextualizeFn(newObject, this.domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,7 @@ define(
|
|||||||
var mockInjector,
|
var mockInjector,
|
||||||
mockIdentifierService,
|
mockIdentifierService,
|
||||||
mockInstantiate,
|
mockInstantiate,
|
||||||
|
mockContextualize,
|
||||||
mockIdentifier,
|
mockIdentifier,
|
||||||
mockNow,
|
mockNow,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
@ -36,6 +37,7 @@ define(
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
|
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
|
||||||
mockInstantiate = jasmine.createSpy("instantiate");
|
mockInstantiate = jasmine.createSpy("instantiate");
|
||||||
|
mockContextualize = jasmine.createSpy("contextualize");
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
'identifierService',
|
'identifierService',
|
||||||
['parse', 'generate']
|
['parse', 'generate']
|
||||||
@ -50,8 +52,10 @@ define(
|
|||||||
);
|
);
|
||||||
|
|
||||||
mockInjector.get.andCallFake(function (key) {
|
mockInjector.get.andCallFake(function (key) {
|
||||||
return key === 'instantiate' ?
|
return {
|
||||||
mockInstantiate : undefined;
|
'instantiate': mockInstantiate,
|
||||||
|
'contextualize': mockContextualize
|
||||||
|
}[key];
|
||||||
});
|
});
|
||||||
mockIdentifierService.parse.andReturn(mockIdentifier);
|
mockIdentifierService.parse.andReturn(mockIdentifier);
|
||||||
mockIdentifierService.generate.andReturn("some-id");
|
mockIdentifierService.generate.andReturn("some-id");
|
||||||
@ -72,7 +76,7 @@ define(
|
|||||||
expect(instantiation.invoke).toBe(instantiation.instantiate);
|
expect(instantiation.invoke).toBe(instantiation.instantiate);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses the instantiate service to create domain objects", function () {
|
it("uses instantiate and contextualize to create domain objects", function () {
|
||||||
var mockDomainObj = jasmine.createSpyObj('domainObject', [
|
var mockDomainObj = jasmine.createSpyObj('domainObject', [
|
||||||
'getId',
|
'getId',
|
||||||
'getModel',
|
'getModel',
|
||||||
@ -81,6 +85,9 @@ define(
|
|||||||
'hasCapability'
|
'hasCapability'
|
||||||
]), testModel = { someKey: "some value" };
|
]), testModel = { someKey: "some value" };
|
||||||
mockInstantiate.andReturn(mockDomainObj);
|
mockInstantiate.andReturn(mockDomainObj);
|
||||||
|
mockContextualize.andCallFake(function (x) {
|
||||||
|
return x;
|
||||||
|
});
|
||||||
expect(instantiation.instantiate(testModel))
|
expect(instantiation.instantiate(testModel))
|
||||||
.toBe(mockDomainObj);
|
.toBe(mockDomainObj);
|
||||||
expect(mockInstantiate)
|
expect(mockInstantiate)
|
||||||
@ -88,6 +95,8 @@ define(
|
|||||||
someKey: "some value",
|
someKey: "some value",
|
||||||
modified: mockNow()
|
modified: mockNow()
|
||||||
}, jasmine.any(String));
|
}, jasmine.any(String));
|
||||||
|
expect(mockContextualize)
|
||||||
|
.toHaveBeenCalledWith(mockDomainObj, mockDomainObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -96,6 +96,19 @@ define(
|
|||||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a url search param setter in place of $location.search(...)
|
||||||
|
*
|
||||||
|
* Invokes $location.search(...) but prevents an Angular route
|
||||||
|
* change from occurring as a consequence which will cause
|
||||||
|
* controllers to reload and strangeness to ensue.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TimeConductorController.prototype.setParam = function (name, value) {
|
||||||
|
this.$location.search(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -185,8 +198,8 @@ define(
|
|||||||
this.setFormFromBounds(bounds);
|
this.setFormFromBounds(bounds);
|
||||||
if (this.conductorViewService.mode() === 'fixed') {
|
if (this.conductorViewService.mode() === 'fixed') {
|
||||||
//Set bounds in URL on change
|
//Set bounds in URL on change
|
||||||
this.$location.search(SEARCH.START_BOUND, bounds.start);
|
this.setParam(SEARCH.START_BOUND, bounds.start);
|
||||||
this.$location.search(SEARCH.END_BOUND, bounds.end);
|
this.setParam(SEARCH.END_BOUND, bounds.end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -299,8 +312,8 @@ define(
|
|||||||
this.conductorViewService.deltas(deltas);
|
this.conductorViewService.deltas(deltas);
|
||||||
|
|
||||||
//Set Deltas in URL on change
|
//Set Deltas in URL on change
|
||||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
this.setParam(SEARCH.START_DELTA, deltas.start);
|
||||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
this.setParam(SEARCH.END_DELTA, deltas.end);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -315,23 +328,23 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
|
TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
|
||||||
//Set mode in URL on change
|
//Set mode in URL on change
|
||||||
this.$location.search(SEARCH.MODE, newModeKey);
|
this.setParam(SEARCH.MODE, newModeKey);
|
||||||
|
|
||||||
if (newModeKey !== oldModeKey) {
|
if (newModeKey !== oldModeKey) {
|
||||||
this.conductorViewService.mode(newModeKey);
|
this.conductorViewService.mode(newModeKey);
|
||||||
this.setFormFromMode(newModeKey);
|
this.setFormFromMode(newModeKey);
|
||||||
|
|
||||||
if (newModeKey === "fixed") {
|
if (newModeKey === "fixed") {
|
||||||
this.$location.search(SEARCH.START_DELTA, null);
|
this.setParam(SEARCH.START_DELTA, undefined);
|
||||||
this.$location.search(SEARCH.END_DELTA, null);
|
this.setParam(SEARCH.END_DELTA, undefined);
|
||||||
} else {
|
} else {
|
||||||
this.$location.search(SEARCH.START_BOUND, null);
|
this.setParam(SEARCH.START_BOUND, undefined);
|
||||||
this.$location.search(SEARCH.END_BOUND, null);
|
this.setParam(SEARCH.END_BOUND, undefined);
|
||||||
|
|
||||||
var deltas = this.conductorViewService.deltas();
|
var deltas = this.conductorViewService.deltas();
|
||||||
if (deltas) {
|
if (deltas) {
|
||||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
this.setParam(SEARCH.START_DELTA, deltas.start);
|
||||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
this.setParam(SEARCH.END_DELTA, deltas.end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,7 +376,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
|
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
|
||||||
//Set time system in URL on change
|
//Set time system in URL on change
|
||||||
this.$location.search(SEARCH.TIME_SYSTEM, newTimeSystem.metadata.key);
|
this.setParam(SEARCH.TIME_SYSTEM, newTimeSystem.metadata.key);
|
||||||
|
|
||||||
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
|
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
|
||||||
this.setFormFromTimeSystem(newTimeSystem);
|
this.setFormFromTimeSystem(newTimeSystem);
|
||||||
|
@ -37,6 +37,7 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
"$watch",
|
"$watch",
|
||||||
"$on"
|
"$on"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
||||||
mockTimeConductor = jasmine.createSpyObj(
|
mockTimeConductor = jasmine.createSpyObj(
|
||||||
"TimeConductor",
|
"TimeConductor",
|
||||||
@ -258,9 +259,7 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
return mockTimeSystem;
|
return mockTimeSystem;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the mode on scope", function () {
|
|
||||||
controller = new TimeConductorController(
|
controller = new TimeConductorController(
|
||||||
mockScope,
|
mockScope,
|
||||||
mockWindow,
|
mockWindow,
|
||||||
@ -270,7 +269,9 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
mockTimeSystemConstructors,
|
mockTimeSystemConstructors,
|
||||||
mockFormatService
|
mockFormatService
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the mode on scope", function () {
|
||||||
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
||||||
controller.setMode(mode);
|
controller.setMode(mode);
|
||||||
|
|
||||||
@ -278,16 +279,6 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sets available time systems on scope when mode changes", function () {
|
it("sets available time systems on scope when mode changes", function () {
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockTimeSystemConstructors,
|
|
||||||
mockFormatService
|
|
||||||
);
|
|
||||||
|
|
||||||
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
||||||
controller.setMode(mode);
|
controller.setMode(mode);
|
||||||
|
|
||||||
@ -303,16 +294,6 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
end: 10
|
end: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockTimeSystemConstructors,
|
|
||||||
mockFormatService
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.setBounds(formModel);
|
controller.setBounds(formModel);
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
||||||
});
|
});
|
||||||
@ -327,16 +308,6 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
endDelta: deltas.end
|
endDelta: deltas.end
|
||||||
};
|
};
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockTimeSystemConstructors,
|
|
||||||
mockFormatService
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.setDeltas(formModel);
|
controller.setDeltas(formModel);
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
||||||
});
|
});
|
||||||
@ -357,22 +328,7 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockTimeSystems = [
|
controller.timeSystems = [timeSystem];
|
||||||
// Wrap as constructor function
|
|
||||||
function () {
|
|
||||||
return timeSystem;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockTimeSystems,
|
|
||||||
mockFormatService
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.selectTimeSystemByKey('testTimeSystem');
|
controller.selectTimeSystemByKey('testTimeSystem');
|
||||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user