Unit Testing UI Router Nested States - javascript

I'm trying to unit test ui.router nested states and am getting the following error:
Error: Could not resolve 'client_view.notes' from state ''
I know it's something to do with how I'm setting up the states & scopes before the test. Can someone fill me in?
Routing:
.state('client_view', {
url: '/client/{id}',
templateUrl: 'static/templates/client.tpl.html',
controller: 'ClientController'
})
.state('client_view.notes', {
url: '/notes',
templateUrl: 'static/templates/client_notes.tpl.html',
controller: 'ClientNotesController',
parent: 'client_view'
})
Test:
$scope = $rootScope.$new();
client_controller = $controller('ClientController', {
$scope: $scope,
$state: $state
});
$rootScope.$apply(function () {
$state.go('client_view.notes');
});
expect($scope.active('notes')).toEqual(true);
$scope.active is a method I wrote that is present in the ClientController. The test will pass if set up properly.
Thanks!

I have been waiting to cross this bridge of route testing myself. So I dont know exactly how to handle this situation but I can try to speculate.
As the spec doesnt have access to the whole picture of from state, maybe you can use
$state.current
to set the current state before you apply the $state.go - but this is just speculation - as the error is saying i dont know where I came from.
Error: Could not resolve 'client_view.notes' from state ''
state config api

Related

How to pass params from previous state to new state in angular?

I have 10 similar boxes in my dashboard each are showing different values for a specific device. When the user clicks on each, I have to direct them to new page which shows more information about that device.
the layout of the second page for all devices is the same. I just need to update the $scope.
What is a clean angular way to achieve this, preferably not adding query to the url?
I am using $stateProvider and tried to make it work with onEnter() but couldn't yet.
$stateParams should do the trick. To use it you need to specify the parameters when routing. For example:
(function(){
'use strict';
angular
.module('app')
.config(ApplicationConfig);
//set dependencies for ApplicationConfig
ApplicationConfig.$inject = ['$stateProvider'];
function ApplicationConfig($stateProvider){
//Define route states
$stateProvider
.state('main', {
abstract: true,
url: '/main',
templateUrl: 'pages/main/main.html',
controller: 'MainCtrl',
controllerAs: 'main',
cache: true,
params: {
user: null
}
});
}
Then you use $state.go('stateName', { param: param }), for example (following the previous example):
//Inside your original controller
function goToMainPage(param) {
$state.go('main', { user: param });
}
Finally, you access the parameter inside your destination controller by doing a $stateParams.param, or, in the previous example's case, $stateParams.user.

AngularJS - Conditionally set a controller for the route

I'm using the native AngularJS router and was wondering if there is a way to assign a controller to a route conditionally. For example, let's say I have three user types:
Guest
User
System Admin
When they come to the home page, I want to be able assign a different controller based on the user type. So I would have three registered controllers: guestHomeCtrl, userHomeCtrl, systemAdminHomeCtrl.
I imagined something like this:
$routeProvider
.when( '/' , {
controller: getHomeCtrl(),
controllerAs: 'homeCtrl'
})
I know I can just pass in the string of the registered controller, but the main issue is being able to find out what type of user is logged in. I have a userService that typically keeps track of that, but it doesn't seem like I can access it from where I set up the routes. Or am I mistaken?
Any direction would help out a lot.
I would suggest using userService with a single controller. Read through the page below, specifically the resolve argument.
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
This blog post also describes how to use the resolve.
http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx
Try sending the specific function or variable you need to the controller. I used userService.User.userAccountStatus, but it really depends on the setup in your service file.
$routeProvider
.when( '/' , {
controller: HomeCtrl,
controllerAs: 'HomeCtrl',
resolve: {
userService: function(userService) {
return userService.User.userAccountStatus();
}
}
})
I use ngInject for injecting dependencies, but I assume you get the gist of giving services to a controller.
angular
.module('app')
.controller('HomeCtrl', HomeCtrl)
/** #ngInject */
function HomeCtrl($scope, $log, $state, userService) {
}
I can provide further examples if you need them.

Setting up and testing UI Router

I'm trying to use ncy-angular-breadcrumb, which uses ui routes. I have a module set up to house the ui routes:
(function () {
var app = angular.module('configurator.uiroutes', ['ui.router', 'ncy-angular-breadcrumb']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('home', {
url: '/',
ncyBreadcrumb: {
label: 'Series'
}
});
}]);
})();
I have this test to see if the route works:
describe('Configurator UI Routes', function () {
beforeEach(module('configurator.uiroutes'));
beforeEach(module('ui.router'));
var $state, $rootscope, state = '/';
// Inject and assign the $state and $rootscope services.
beforeEach(inject(function (_$state_, _$rootScope_) {
$state = _$state_;
$rootscope = _$rootScope_;
}));
//Test whether the url is correct
it('should activate the state', function () {
$state.go(state);
$rootscope.$digest();
expect($state.current.name).toBe('home');
});
});
Which produces this error:
Error: Could not resolve '/' from state ''
I'm just not getting the UI Router and either how it works, or what it's supposed to do. I just want to grab parts of the url and pass that info to ncyBreadcrumb, but I can't even get the base URL to work.
Any pointers or help would be awesome!
Edit: I've gotten the test to pass by replacing '/' with 'home' in the test. My bigger question is: is there a way to test that when the URL '/' is hit, that the state.current.name becomes 'home'. In my use of the app, this doesn't appear to be happening, and I'm hoping that the unit test can help tell me why.
This is a single page application, if that's relevant.
Error is enough self explanatory, as you asked ui-router to redirect on / state using $state.go
Basically $state.go which asks for state name rather than url.
var $state, $rootscope, state = 'home'; //<--changed from `/` to `home`

AngularJS ui-router default state for states without a URL

We have a couple of states for one page, so the states don't have any URL assigned to them. Is there a way to specify a default state for URL-less states? Maybe similar to $urlRouterProvider.otherwise('/');.
I tried going to a state in Angular's run statement or within the controller but that causes a transition superseded error, which is probably due to the fact that $state hasn't been initialized.
Below is a short example. I would like to start directly in stateA.
angular
.module('myModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('stateA', {
controller: function () {
// Do some magic related to state A
}
})
.state('stateB', {
controller: function () {
// Do some magic related to state B
}
});
}
])
.controller(['$state', '$timeout', function($state, $timeout){
// My global controller
// To set a default state I could do:
// $timout(function () {
// $state.go('stateA');
// }, 0);
// But that doesn't feel right to me.
}]);
Update:
Thanks to this answer I figured out that I have to wrap $state.go into a $timeout, to avoid the transition superseded error.
This will call a custom rule that goes to a default state without tampering with a URL.
$urlRouterProvider.otherwise(function($injector) {
var $state = $injector.get('$state');
$state.go('stateA');
});`

Dynamic directive templates using app level resolve AngularJS

Currently i am implementing a completely stateless token-based authentication with AngularJS and a REST API as backend service. This works pretty well, but about one state i am currently not aware of and wanted to ask you how to handle it.
My intend is only to save my authentication token in the local storage from the client. On initial loading from the application i want to retrieve all user information from the REST API (GET /me) and save it in the $scope from my AngularJS project.
After implementing it, i have several problems to be sure that the retrieved user information are completely resolved and could work with the informations (like the access rights from the user).
I just want you to show an example where i ran into a kind of "asynchronous problem".
angular.directive('myDirective', ['MyAsyncService', function(MyAsyncService) {
return {
restrict: 'E',
templateUrl: function(element, attributes) {
console.log(MyAsyncService.getData());
}
}
}]
At this example my asynchronous service haven't the information yet, when the directive is rendered. Also retrieve the async data in the resolve function from ngRoute/ui-router or in the run() method from AngularJS couldn't solve my problem.
So back to my question ... It is better to save some information from the user on client side to avoid some of this problems? From my point of view it would be better not to save any kind of user information (like access rights, username, id, email address, ...) due security purposes.
How you handle you the authentication and authorization in a token-based AngularJS application?
Thanks in advance, hopefully you can get me back on track.
You need to resolve() authentication - especially this type of authentication at the app level.
If you aren't using ui.router - take a look.
When we resolve() on a route what we are saying is - do not do anything else until this thing is finished doing what it's doing.
This also assumes you are familiar with promises.
Your resolve dependency will ultimately expect that this asynchronous request is returning a promise so that it may chain controller logic after the callback finishes execution.
For example:
$stateProvider
.state('myState', {
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.simple = auth;
}
});
But there's more. If you want the resolve to happen at the begining of application execution for any route - you also need to chain your state declarations:
$stateProvider
.state('app', {
abstract: true,
templateUrl: 'app/views/app.tpl.html',
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.resolvedauth = auth;
}
}
.state('app.home', {
url: '/home',
templateUrl: 'home/views/home.tpl.html',
controller: function($scope){
//Should be available.
console.log($scope.$parent.resolvedauth);
}
});
Now that your children inherit from the app level resolve, you could control the template by doing something like this:
.directive('myDirective', function() {
return {
templateUrl: function(elem, attr){
return 'myDirective.'+attr.auth+'.html';
}
};
});
And the declaration itself:
<my-directive auth="resolvedauth.status" />

Categories

Resources