Ui Router End to End testing - angularjs

I am trying to create an end to end test with an angular app that uses ui router.
What I want it to switch state and have it behave exactly like the "real" running application.
The issue I am having is that the state does get switched, and the state's resolve seems to get resolved correctly, but the state's controller doesn't get instantiated.
For example:
State definition
$stateProvider.state('project', {
controller: 'RootCtrl',
template: '<div>Empty</div>'
url: '/',
resolve {
anything: function() {console.log('This Gets Called!');}
}
}
Controller
controller('RootCtrl', function($scope) {
console.log('Success!');
});
Unit Test
it('should switch state', function () {
var app = this.$controller('AppCtrl', {$scope: this.$rootScope.$new()});
this.$state.go('project');
this.$httpBackend.flush();
this.$interval.flush(100);
this.$rootScope.$digest();
this.$rootScope.$apply();
// Controller never instantiated!
});

Related

Set AngularJS Global before App Initialization

I am currently working in an AngularJS code base where different routes are triggered depending on some toggle element attached to every customer. In my app, I need to route to different places twice, once, in my app.js file, where I manage my state and another time, in a controller when I use the same variable to branch.
I have tried to use a combination of manually bootstrapping and using the run method but haven't had any success.
var app = angular
.module('app', ['customer'])
.run(function (customer) {
angular.element(document).ready(function () {
customer.myMethod().then(function (enabled) {
enabled = enabled;
angular.bootstrap(document, ['app']);
});
});
});
$stateProvider
.state('home', {
url: enabled ? '/' : '/customer',
templateUrl: 'views/home.html',
controller: 'HomeCtrl',
data: {
title: 'Home'
}
})
And then in my controller, use the same variable in my init method for example:
$scope.init = function () {
if (enabled) { ... }
else { ... }
}
$scope.init();
I notice that the enabled variable eventually does get set, but it happens after the state is decided. Is there a way to set a global variable before my AngularJS app starts in order to use it anywhere?
Thanks

Refreshing Resolve Data - Ui Router

In ionic I'm resolving my data with the Ui-Router's resolve functionality before the controller is initialized. As of now I don't have to $inject my EventService into the Controller. The EventService's getEvents() method resolves the data before the controller is initialized. Everything works correctly this way, but now i'm trying to implement the Ion Refresher. I could easily refresh my $scope.events array within the controller, bloating the controller itself, because I would have to $inject the EventService into the controller, and that also means that every controller that uses the same data will have to contain logic to handle a refresh. What is the best way refresh the data outside of the controller or is that the best way?
Events State Definition and data resolution
.state('tab.events', {
url: '/events',
views: {
'tab-event': {
templateUrl: 'views/events.html',
controller: 'EventsController',
resolve: {
events: function (EventService) {
return EventService.getEvents(); //resolves data before ctrl initialized
}
}
}
}
})
Events Controller
(function() {
'use strict'
angular
.module('app.events')
.controller('EventsController', EventsController);
EventsController.$inject = ['$scope','events'];
function EventsController ($scope,events) {
$scope.events = events;
}
}
)();
Bloated Events Controller - Example
(function() {
'use strict'
angular
.module('app.events')
.controller('EventsController', EventsController);
EventsController.$inject = ['$scope','events','EventsService'];
function EventsController ($scope,events,EventsService) {
$scope.events = events;
$scope.refresh = refresh;
function refresh () {
clearCache(); //pretend method
EventsService.getEvents()
.then(function (events) {
$scope.events = events;
$scope.$broadcast('scroll.refreshComplete');
})
}
}
}
)();
Rather than bloating the controller can I refresh this data another way?
call $state.reload() which is an alias for:
$state.transitionTo($state.current, $stateParams, {
reload: true, inherit: false, notify: true
});
This will cause all your states to be "re-entered" which fetches the resolves and re-initializes the controllers.
I wish a hard refresh, which is basically what a $state.reload() does wasn't the answer. I too have this issue and would rather be able to call some method that just forces all the resolved data objects to rerun. The reload causes a page refresh, which causes nasty UI artifacts.

AngularJS watch value within service

I'm currently developing an AngularJS web application.
I have a primary view (Index), child view (Dashboard) and grandchild view (Raiding The Rails).
http://localhost:4000/#/dashboard/raiding-the-rails/1
Within the grandchild view (Raiding The Rails) I am displaying dress information relevant to the state ID /1, each dress has a specified state ID e.g /1,/2,/3.
I have a controller/service sending the state ID to a console.log (within the parent) and when viewing raiding-the-rails/1 the console.log displays {stateID: "1"}, If I change the URL to raiding-the-rails/4 the console.log doesn't update unless I refresh the page.
Also, When I completely refresh the browser the console.log spits out three objects instead of one?
I've reviewed many sites and have tried and tried again trying to figure this out, I even tried setting up a Watch service but this failed massively.
If anyone could help me out I would be highly grateful!Thank you.
App:
(function(angular, undefined){
"use strict";
var am = angular.module('virtual-fitting', ['ui.router']);
am.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: '../views/dashboard.html'
})
.state('dashboard.raidingtherails', {
url: '/raiding-the-rails',
templateUrl: '../views/dashboard.raiding-the-rails.html'
})
.state('dashboard.raidingtherails.dress', {
url: '/:id',
templateUrl: '../views/dashboard.raiding-the-rails.dress.html'
});
$urlRouterProvider.otherwise('/');
});
})(angular);
Service:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.factory('raidingService', function () {
var raidingService = {
stateID: null
};
return raidingService;
});
})(angular);
Parent Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('dashboardCtrl', function(raidingService) {
console.log(raidingService);
});
})(angular);
Child Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('raidingtherailsCtrl', function($state, $stateParams, raidingService) {
var self = this;
raidingService.stateID = $stateParams.id;
});
})(angular);
I assume it's in the parent controller that you want to watch your service?
If so, you could watch a function, like so:
$scope.$watch(function() {
return raidingService.stateId;
}, function(value) {
console.log(value);
}, true);
That should work.
MVC pattern used in any framework:
You creating Service with .get() and .set() methods. Set method is common to be used in any place you desire to operate model value and allows you to create one point that will handle changes to your model. The final move in .set() method is .$boradcast() notifying whole application about changes to your model.
Pros:
one access point to value
application is know about any changes
no need to write $watch with watching collections (holly-molly)
having access point to changes with '$on' in your controllers $scope
Cons:
easy to forget to use .set() method instead of simple assignment

Restrict access to route with routeprovider unless variable as been set

I'm in the process of learning AngularJS, working on a more in-depth ToDo app. I'm having an issue with trying to limit access to a url or "route" using angular.
When you hit my dev url on my machine (todo.ang) it brings you to todo.ang/#/home, on this view you see the categories which have todos associated to each. EG (category = cat, cat has a todo of "feed", and "play"), when you click a category I'm calling the $scope.goToCategory function (seen in my JS fiddle) which sets a variable for my firebase ref then redirects you too /#/todo. This is working correctly.
My problem is, I don't want the user to be able to access /#/todo if the todoRef variable is still undefined. But it seems like even after $scope.goToCategory is called and todoRef is set to a firebase URL, the routerprovider never gets recalled to know that todoRef has been set to a different value so it always forces you back to /#/home.
code:
var todoRef = undefined;
if (todoRef !== undefined) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl'
});
}
$scope.goToCategory = function(catId) {
test = catId;
todoRef = new Firebase("URL HERE");
$location.path('/todo');
}
I didn't include the entire file of code but if thats necessary, I can do that as well.
JSFiddle
All routes are only being set during the config phase.
what happens in your code is that 'todo' route is ignored during the initiation of ngRoute.
What you should do is to setup the route but have a resolve like so:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl',
resolve: {
todoRef: ['$q', function($q) {
return todoRef ? todoRef : $q.reject('no ref');
}]
}
});
}]);
If 'todoRef' is undefined the route is rejected.
Also you should consider moving 'todoRef' into a service and not on global scope.
You can also listen for route errors and for example redirect to home route:
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeError', function() {
$location.path('/home');
});
}]);

AngularJS: Angular UI Router load multiple templates for testing

I'm trying to perform a simple test on a helper function I've created:
function getPostId() {
return $stateParams._id;
}
And here is the test I am using, please note that getService is simply a wrapper for the Angular inject() function:
describe('wpApp Router', function() {
var $state, $stateParams, Router;
beforeEach(module('wpApp.core.router'));
beforeEach(function() {
$state = getService('$state');
$scope = getService('$rootScope');
$stateParams = getService('$stateParams');
Router = getService('Router');
$templateCache = getService('$templateCache');
$templateCache.put('/app/sections/posts/list/post-list.html', '');
});
it('should provide the current post ID', function() {
$state.go('posts.details', { _id: 1 });
$scope.$digest();
expect(Router.getPostId()).to.equal(1);
});
});
My router looks like this:
$stateProvider
.state('posts', {
url: '/posts',
controller: 'PostsListController as postsList',
templateUrl: '/app/sections/posts/list/post-list.html'
})
.state('posts.details', {
url: '/:_id',
controller: 'PostDetailsController as postDetails',
templateUrl: '/app/sections/posts/details/post-details.html'
})
I'm trying to set the current state to posts.details with an _id of 1. Then test my helper function to see if it is correctly retrieving the ID.
However, I am getting errors with the templates of the states, like the following: Unexpected request: GET /app/sections/posts/details/post-details.html
The state posts.details is a child of posts and both have their own templates, the problem is I am unable to load both into the templateCache or at least I can't figure out how to do so.
Is it possible to load both templates in order to fulfill the test?
In response to your actual question, the easiest approach to caching templates is by using something like karma-ng-html2js-preprocessor. It will cache all your application templates, and make them available to your tests as a module.
However, from what I can surmise from the code provided, this is un-necessary. Currently, your unit test is actually an integration test in that it's dependent on the routing of the 'posts.details' state in order to pass, and yet the method itself is concerned only with the $stateParams object.
So, instead, as $stateParams is in fact just a plain object, a simpler (and better) approach is just to mock it prior to your test:
beforeEach(module(function($provide) {
$provide.value('$stateParams', {
_id: 1
});
}));
and then you can test your service method in isolation from your states:
it('should provide the current post ID', function() {
expect(Router.getPostId()).toEqual(1);
);

Resources