How to unit test onEnter and resolve of ui-router state - javascript

I'm trying to test an Angular UI Bootstrap modal that is being called from the onEnter of the following state:
.state("profile.index.edit.services", {
url: "/edit/services/:serviceCode",
parent:"profile.index",
onEnter: ['$stateParams', '$state', '$modal',function($stateParams, $state, $modal) {
$modal.open({
templateUrl: 'profile/edit-services-modal.html',
controller: 'ProfileEditServicesController',
resolve: {
profileData: function(CacheService){
return CacheService.getItem(CacheService.Items.Profile.loadedProfile);
},
allServicesList: function(ProfileService){
return ProfileService.getAllServicesList($stateParams.serviceCode,$stateParams.orgId);
},
serviceCode: function() {
return $stateParams.serviceCode;
}
}
}).result.then(function(result) {
if (result) {
return $state.transitionTo("profile.index", { orgId: $stateParams.orgId });
}
else { // cancel
return $state.transitionTo("profile.index", { orgId: $stateParams.orgId }); // don't reload
}
}, function () {
// executes on close/cancel/ESC
return $state.transitionTo("profile.index", { orgId: $stateParams.orgId });
});
}]
})
I've been banging my ahead against this trying many different things to set the test up, but can't seem to figure it out. Any plunkers or suggestions are greatly appreciated!!!!
By the way, the state above is a descendant of these states, which I've been able to write tests for that pass:
.state('profile.index', {
url: '/:orgId',
views: {
'profile-index#profile': {
templateUrl: 'profile/view-profile.html',
controller: 'ProfileController'
},
'header#': {
templateUrl: 'common/layout/header.html',
controller: 'HeaderController'
},
'footer#':{
templateUrl: 'common/layout/footer.html',
controller: 'FooterController'
}
},
resolve: {
profileData: function(ProfileService, $stateParams){
return ProfileService.getProfile($stateParams.orgId, true);
}
}
})
.state('profile.index.edit', {
url: '',
abstract: true
})

did you ever figure this out? you should have passed a named controller, that way you could test that controller (since it would just be a function) directly instead of relying on onEnter being fired.

Related

Unable to load child states in angular

I am facing a very peculiar problem in angular1 and it might need a lot context to understand for someone to answer. I have been working on angular for around 4 weeks now but a very strange problem entangles me.
I have a left vertical menu on a page and a click on it loads the content on right side of the page.
On each click I initiate a state. In this case
$state.go("contrat-synthese", {
id: offer.refEboard
}, {
reload: true
});
The above peice of a code is inside a function which works on the click.
The state "contrat-synthese" is shown below
angular.module('engcApp').config(['$stateProvider', function($stateProvider) {
var lotSelectionState = "synthese-lot";
$stateProvider.state('contrat-synthese', {
parent: 'contrat-lots-resume',
url: '/synthese',
views: {
'content#contrat-lots-resume': {
templateUrl: 'app/entities/views/synthese/synthese.html',
},
'synthese-detail#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-details.html',
controller: 'ContratSyntheseDetailsController',
controllerAs: 'vm'
},
'synthese-lots-navigation#contrat-synthese': {
templateUrl: 'app/entities/views/common/contrat-lots-navigation.html',
controller: 'ContratLotsNavigationController',
controllerAs: 'vm',
resolve: {
afficherLotComplet: function() {
return false;
},
afficherAnneeComplet: function() {
return false;
},
afficherSelectionAnnee: function() {
return true;
},
afficherLots: function() {
return true;
},
stateNameToUse: function() {
return lotSelectionState;
}
}
},
'synthese-services#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-services.html',
controller: 'ContratSyntheseServicesController',
controllerAs: 'vm'
}
}
}).state(lotSelectionState, {
parent: "contrat-synthese",
params: {
lots: null
},
views: {
'synthese-composition-lot#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-composition.html',
controller: 'ContratSyntheseCompositionController',
controllerAs: 'vm'
},
'synthese-volumes-lot#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-volumes.html',
controller: 'ContratSyntheseVolumesController',
controllerAs: 'vm'
},
'synthese-modules-lot#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-modules.html',
controller: 'ContratSyntheseModulesController',
controllerAs: 'vm'
},
'synthese-formules-lot#contrat-synthese': {
templateUrl: 'app/entities/views/synthese/synthese-formules.html',
controller: 'ContratSyntheseFormulesController',
controllerAs: 'vm'
},
}
});
}]);
But when I call the state go on page load it is not able to load the lotSelectionState in the synthese state above.
I just don't understand why it is not able to load the states when I call it from outside.
Also child states are not loaded when I click on the link again on the left banner.
Solution I tried for the second case, I tried the following approach:
state.go("contrat-synthese", {
id: offer.refEboard
}, {
reload: true
});
However why am I not able to load the child state? I am so confused :(
Can provide and explain more if needed.

A parameter of ui-router in controller and resolve

I have configured the following ui-router.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal' },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
}])
app.controller('EditorCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.layout = "horizontal";
$scope.$watch('layout', function () {
$state.go('global.editor.panels', { layout: $scope.layout });
});
}]);
As a result, https://localhost:3000/#/new in a browser leads to (the state global.editor, then to) the state global.editor.panels.
Now, I want to add a parameter connected:
I don't want it to be shown in the url
https://localhost:3000/#/new in a browser makes connected to be false, and https://localhost:3000/#/newTRUE in a browser makes connected to be true
connected can be past into the controller EditorCtrl and PanelsCtrl
connected can be available in the resolve of global.editor; ideally, we could resolve different objects according to the value of connected.
Does anyone know how to accomplish this?
You can add resolve for new and newTRUE:
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return false;
}
},
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return true;
}
},
controller: 'EditorCtrl'
})
And in EditorCtrl (or PanelsCtrl) you can use it like:
app.controller('EditorCtrl', ['$scope', '$state', 'isConnected', function($scope, $state, isConnected) {
console.log("connected : " + isConnected); // you can save this value in Service and use it later.
...
}]);
You can use classic approach - in resolve
Or you can use hidden parameters from angular ui router.
Define params : {isConnected' : null} in your parent global state.
In:
global.newTRUE - set value in $state config
global.new - set value in $state config
global.editor.panels - set parameters in transition/go or ui-sref
definition is like this:
$stateProvider
.state('global.newTRUE', {
url : '/:newTRUE',
params : {
'isConnected' : false
}
});
}
and in controller you get in from $stateParams.
Problem with this approach is hidden parameters are loses in refresh page, except if is set default value
You can surely use the params of UI-Router states' config to not show it in URL and achieve all mentioned points.
Also, as per #2, you need connected to be false for /new and true for /newTRUE. We can do so by passing true or false as default value for those states. Something like this:
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
params: { connected: false }, // default false for /new
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
params: { connected: true }, // default true for /newTRUE
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal', connected: null },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
For #3, In order to access connected in your controllers (EditorCtrl and PanelsCtrl) you can inject $stateParams to controller and use $stateParams.connected to get it.
For #4, (This is more or less similar to achieveing #3)
Just like you get $stateParams.id, you can have $stateParams.connected as well, which you can use to resolve different objects according to the value of connected. Something like this:
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return $stateParams.connected ?
codeService.getPost($stateParams.id) :
codeService.getSomethingElse($stateParams.id)
}]
}
}
But, for that to work, make sure that you are passing connected as params when you visit global.editor state (using $state.go or ui-sref)
Hope this helps!

AngularJS ui-router navigating states with $state.params and queries

I have an app with multiple states that each have nested views. The one state has a conditional templateUrl, and based on a $state.param will show specific HTML/JS. I want to set a query on the URL of the state, so that I know which list item is being looked at when I click it. I cannot figure out how to set a url query and transition to the desired state's view.
My states:
.state('index', {
url: '/',
views: {
'#' : {
templateUrl: 'views/layout.html'
},
'top#index' : {
templateUrl: 'views/top.html',
controller: function($scope, $state) {
$scope.logOut = function() {
$state.go('login');
};
}
},
'left#index' : { templateUrl: 'views/left.html' },
'main#index' : { templateUrl: 'views/main.html' }
}
})
.state('index.list', {
url: 'list?item',
templateUrl: 'views/lists/list.html',
controller: 'ListCtrl'
})
.state('index.list.details', {
url: '/',
params: {
detail: 'overview'
},
controller: ctrl,
views: {
'details#index' : {
templateUrl: function($stateParams) {
if($stateParams.detail === 'status' ) {
ctrl = 'StatusCtrl';
return 'views/details/status.html';
} else if ($stateParams.detail === 'overview') {
ctrl = 'OverviewCtrl';
return 'views/details/overview.html';
}
}
}
},
})
In my controller for the index.list, this is where I have the list and click and populate the details view.
HTML & Js:
<div class="channel" ng-repeat="item in items" ng-click="viewListDetails(item)">
$scope.viewListDetails = function(item) {
$location.search('item', item.id);
$state.go('index.list.details', {detail: 'status'}, {reload: true})
};
My JS above runs the through the function however it does nothing! It will not set the query or transition to the desired view for that sate.
Any help is appreciated!
index.deviceList.detail is not a defined state. You probably intended to write index.list.details.

AngularJS ui router $stateChangeStart with promise inifinite loop

I'm trying to build some sort of authentication in my angular app and would like to redirect to a external URL when a user is not logged in (based on a $http.get).
Somehow I end up in an infinite loop when the event.preventDefault() is the first line in the $stateChangeStart.
I've seen multiple issues with answers on stackoverflow, saying like "place the event.preventDefault() just before the state.go in the else". But then the controllers are fired and the page is already shown before the promise is returned.
Even when I put the event.preventDefault() in the else, something odd happens:
Going to the root URL, it automatically adds the /#/ after the URL and $stateChangeStart is fired multiple times.
app.js run part:
.run(['$rootScope', '$window', '$state', 'authentication', function ($rootScope, $window, $state, authentication) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
event.preventDefault();
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
return;
} else {
$state.go(toState, toParams);
}
});
});
}]);
authentication.factory.js identity() function:
function getIdentity() {
if (_identity) {
_authenticated = true;
deferred.resolve(_identity);
return deferred.promise;
}
return $http.get('URL')
.then(function (identity) {
_authenticated = true;
_identity = identity;
return _identity;
}, function () {
_authenticated = false;
});
}
EDIT: Added the states:
$stateProvider
.state('site', {
url: '',
abstract: true,
views: {
'feeds': {
templateUrl: 'partials/feeds.html',
controller: 'userFeedsController as userFeedsCtrl'
}
},
resolve: ['$window', 'authentication', function ($window, authentication) {
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
})
}]
})
.state('site.start', {
url: '/',
views: {
'container#': {
templateUrl: 'partials/start.html'
}
}
})
.state('site.itemList', {
url: '/feed/{feedId}',
views: {
'container#': {
templateUrl: 'partials/item-list.html',
controller: 'itemListController as itemListCtrl'
}
}
})
.state('site.itemDetails', {
url: '/items/{itemId}',
views: {
'container#': {
templateUrl: 'partials/item-details.html',
controller: 'itemsController as itemsCtrl'
}
}
})
}])
If you need more info, or more pieces of code from the app.js let me know !
$stateChangeStart will not wait for your promise to be resolved before exiting. The only way to make the state wait for a promise is to use resolve within the state's options.
.config(function($stateProvider) {
$stateProvider.state('home', {
url: '/',
resolve: {
auth: function($window, authentication) {
return authentication.identity().then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
});
}
}
});
});
By returning a promise from the function, ui-router won't initialize the state until that promise is resolved.
If you have other or children states that need to wait for this, you'll need to inject auth in.
From the wiki:
The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children.

Conditionally navigate to state with angular ui-router

Currently, we have a 'Portfolio' tool in beta. Once a user logs in to the main app, if they have been given access to the beta, they can navigate to the Portfolio tool directly, without any additional login. If not, they should be redirected to a Portfolio login page (state is called portfolio.login) where they can login or contact support/sales etc. Right now I have the check in the resolve block, however $state.go('portfolio.login') seems to fetch the right partials, but doesn't render them on screen or navigate to the appropriate URL.
Code:
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
CheckLoggedIn: function ($state, loggedIn) {
var _loggedIn = loggedIn.checkUser();
if (!_loggedIn) {
$state.go('portfolio.login');
console.log('not authorized');
}
},
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
I ran in the same problem days ago. Instead of using resolve, I check if the user is logged when state changes, defining run module and listening $stateChangeStart event, then check if the current state required authentication. If so, check if the user is logged in.
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
data: {
requiredAuthentication: true
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
})
.run(run);
run.$inject = ['$rootScope','$state','loggedIn'];
function run($rootScope,$state,loggedIn){
$rootScope.$on('$stateChangeStart',function(e,toState){
if ( !(toState.data) ) return;
if ( !(toState.data.requiredAuthentication) ) return;
var _requiredAuthentication = toState.data.requiredAuthentication;
if (_requiredAuthentication && !loggedIn.checkUser() ){
e.preventDefault();
$state.go('portfolio.login', { notify: false });
console.log('not authorized');
}
return;
});
};

Categories

Resources