I have a page where everything works fine. However, my test for this page's controller FeeRuleCtrl, after it tests the code of said controller, goes on and starts testing the controller of a different state. Here's my app.js:
$stateProvider
.state('root', {
url: "/",
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/root') %>",
controller: 'RootCtrl',
resolve: {
feeSuites: function(FeeSuiteCrud, FeeSuite){
console.log('here');
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule', {
abstract: true,
controller: 'FeeRuleCtrl',
template: "<ui-view/>",
resolve: {
feeTypes: function(FeeSuiteCrud, FeeType){
var feeCrud = new FeeSuiteCrud(FeeType)
var promise = feeCrud.query();
return promise.then(function(response){
return response;
})
},
feeSuites: function(FeeSuiteCrud, FeeSuite){
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule.new', {
url: '/new',
controller: 'NewCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Add a New Fee Rule' }
})
.state('fee-rule.edit', {
url: "/edit/:id",
controller: 'EditCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Edit Fee Rule' },
resolve: {
feeRule: function(FeeSuiteCrud, FeeRule, $stateParams){
var feeCrud = new FeeSuiteCrud(FeeRule);
var promise = feeCrud.get($stateParams.id)
return promise.then(function(response){
return response;
});
}
}
});
I have an abstract state, fee-rule, because both the new and edit states share most of the same functionality.
When I go to the page's address, <host>/admin/fee_suites/new, I inspect the network tab and there are 4 server calls made:
api/v3/fee_types
api/v3/fee_suites
api/v3/fee_suites/8?association=fee_rules
api/v3/fee_types/9?association=fee_parameters
The first 2 are resolves in the fee-rule state. I take care of this like so in the test:
beforeEach(function(){
module(function($provide){
$provide.factory('feeSuites', function(FeeSuite){
feeSuite = new FeeSuite({
id: 8,
site_id: 9,
active: true
});
return [feeSuite];
});
$provide.factory('feeTypes', function(FeeType){
feeType = new FeeType({
id: 9,
name: 'Carrier Quotes',
value: 'carrier_quotes'
});
return [feeType];
});
});
inject(function($injector){
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
$controller("FeeRuleCtrl", {
'$scope': scope
});
});
});
The last 2 server calls are made inside FeeRuleCtrl. I test them like so:
beforeEach(function(){
var JSONResponse = {"master":[{"id":29,"fee_suite_id":8,"fee_parameter_id":1,"name":"American Express Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":30,"fee_suite_id":8,"fee_parameter_id":2,"name":"Discover Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":31,"fee_suite_id":8,"fee_parameter_id":3,"name":"MasterCard Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":32,"fee_suite_id":8,"fee_parameter_id":4,"name":"Visa Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_suites/8?association=fee_rules').respond(JSONResponse);
JSONResponse = {"master":[{"id":25,"fee_type_id":9,"name":"UPS Published Quote","value":"ups_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":26,"fee_type_id":9,"name":"FedEx Published Quote","value":"fedex_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":27,"fee_type_id":9,"name":"UPS Negotiated Quote","value":"ups_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":28,"fee_type_id":9,"name":"FedEx Negotiated Quote","value":"fedex_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_types/9?association=fee_parameters').respond(JSONResponse);
$httpBackend.flush();
});
it('should set currentFeeRuleNum', function(){
expect(scope.FeeSuite.currentFeeRuleNum).toEqual(4);
});
When I run my test I get the following error:
Error: Unexpected request: GET /api/v3/fee_suites/
I know it is coming from root state's resolve function feeSuites because the test also prints to the console log the word 'here'.
I cannot figure out why it seems like the test doesn't stop and starts testing the RootCtrl in the root state. Could it have anything to do with the fact that state fee-rule is abstract? Also NewCtrl is defined but it is empty.
After some more googling with different keywords, turns out in my test I need to mock the $state variable inside FeeRuleCtrl. That fixed the problem.
Related
I have 2 roles: one is admin, this another is normal user. Admin can navigate to item-detail page, and normal user can not. Hence, I store user's role in "globals" cookies when they are login. I get their role from cookieStore to check which one can navigate to item-detail page. This function works well. However, I have no idea how to write a test for $cookieStore in checkUserRole function:
angular.module('config', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider)
{
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('login',
{
url: '/login',
templateUrl: 'login-page.html',
controller: 'LoginController'
})
.state('index',
{
url: '/index',
templateUrl: 'bridge.html'
})
.state('item-detail',
{
url: '/index/item-detail/:Name',
templateUrl: 'item-detail.html',
controller: 'myCtrl',
resolve:
{
checkUserRole: function($cookieStore)
{
if($cookieStore.get('globals').currentUser.userRole === 'user')
{
return state.go('index');
}
}
}
});
});
And, here is my test case:
describe('config', function()
{
var $scope, $state, $cookieStore, userRole;
beforeEach(function()
{
module('config', function($provide)
{
$provide.value('$cookieStore', { get: 'globals' });
});
inject(function($injector, $templateCache)
{
$scope = $injector.get('$rootScope');
$state = $injector.get('$state');
$cookieStore = $injector.get('$cookieStore');
$templateCache.put('login-page.html', '');
$templateCache.put('bridge.html', '');
$templateCache.put('item-detail.html', '');
});
});
it('home page', function()
{
$scope.$apply();
expect($state.current.name).toBe('login');
expect($state.current.templateUrl).toBe('login-page.html');
expect($state.current.controller).toBe('LoginController');
});
it('login page', function()
{
$scope.$apply(function()
{
$state.go('login');
});
expect($state.current.name).toBe('login');
expect($state.current.templateUrl).toBe('login-page.html');
expect($state.current.controller).toBe('LoginController');
});
it('items page', function()
{
$scope.$apply(function()
{
$state.go('index');
});
expect($state.current.name).toBe('index');
expect($state.current.templateUrl).toBe('bridge.html');
});
it('item-detail page', function()
{
spyOn($cookieStore, 'get').and.callFake(function()
{
return 'user';
});
expect($cookieStore.get('globals')).toBe('user');
$scope.$apply(function()
{
$state.go('item-detail');
});
expect($state.current.name).toBe('item-detail');
expect($state.current.templateUrl).toBe('item-detail.html');
expect($state.current.controller).toBe('myCtrl');
expect($state.href('item-detail', { Name: 'lumia-950'})).toEqual('#/index/item-detail/lumia-950');
});
});
My question is: How can I write a test for $cookieStore.get('globals').currentUser.userRole? or how can I mock it to test what if user's role is user?.
I don't know what version of angular you're using but $cookieStore is now deprecated, prefer using $cookies instead (see doc).
There are at least 3 ways to proceed :
Using Jasmine with Angular $cookie (from v1.4.x) :
describe('config module', function(){
var $cookies;
beforeEach(function(){
angular.mock.module('config');
angular.mock.inject(function(_$cookies_){
$cookies = _$cookies_;
});
});
it('should have a "globals" cookie with "user" value', function(){
var globals = $cookies.getObject('globals');
expect(globals.currentUser.userRole).toBe('user');
});
});
Using Jasmine with pure JavaScript :
describe('config module', function(){
it('should have a "globals" cookie with "user" value', function(){
var globals = document.cookie.replace(/(?:(?:^|.*;\s*)globals\s*\=\s*([^;]*).*$)|^.*$/, "$1");
globals = JSON.parse(globals);
expect(globals.currentUser.userRole).toBe('user');
});
});
Using Jasmine with Protractor (e2e test) :
describe('config module', function(){
it('should have a "globals" cookie with "user" value', function(){
var globals = JSON.parse(browser.manage().getCookie('globals'));
expect(globals.currentUser.userRole).toBe('user');
});
});
Here Protractor gives us browser global variable to handle browser related behavior.
Note that JSON.parse() is used to unserialized the cookie string value and parse it to an usable JavaScript object.
example :
var obj = JSON.parse('{ "foo" : "bar" }');
console.log(obj.foo); //print 'bar' in the console
IMPORTANT:
In your application, use only 1.4.7 version of Angular libraries with the Angular 1.4.7 Core.
Let me know if it helps.
I'm trying to mock the resolve functions inside the $state of my ui-router file, but I can't seem to get it to work. Here's my router code:
$stateProvider
.state('state.of.page', {
url: '/url-to-page',
template: require('./page-template.html'),
controller: 'PageCtrl',
controllerAs: 'page',
resolve: {
/**
* Cloning of page object in case user leaves page
* without saving
*/
pageObjClone: ['pageObj', function (pageObj) {
return angular.copy(pageObj);
}],
pageTemplate: ['template', function (template) {
return template;
}]
}
Here is my Jasmine code. I'm currently getting the error 'fn' is not a function when I run the test.
'use strict';
describe('my jasmine $state test', function() {
var $state;
var $injector;
var stateName = 'state.of.page';
var stateObj;
beforeEach(function() {
angular.mock.module('MyApp');
inject(function(_$rootScope_, _$state_, _$injector_) {
$state = _$state_;
$injector = _$injector_;
stateObj = {
customerObjClone: ['customerObj', function (customerObj) {}],
template: ['template', function (template) {}]
};
});
});
it('should resolve data', function() {
var state = $state.get(stateName);
expect($injector.invoke($state.resolve)).toBe('stateObj');
});
});
Thanks for any help.
...
var pageObj, template;
beforeEach(function() {
angular.mock.module('MyApp');
inject(function(..., _pageObj_, _template_) {
...
pageObj = _pageObj_;
template = _template_;
});
});
it('should resolve data', function() {
var state = $state.get(stateName);
expect($injector.invoke(state.resolve.pageObjClone)).toEqual(pageObj);
expect($injector.invoke(state.resolve.pageTemplate)).toBe(template);
});
Depending on how complex resolvers and their dependencies are, the dependencies may be mocked and injected into resolver, e.g.
expect($injector.invoke(state.resolve.pageTemplate, null, { template: mockedTemplate }))
.toBe(mockedTemplate);
I'm initializing ControllerAs on all my Routes using the UI Router. The issue i'm having is when i click on Edit record, it takes me to another page which also passes the ID from the current page to fetch the record from the service and data bind the html fields.
In my controller class. When i write
var vm = this; //DOES NOT DATABIND
var vm = $scope; // THIS WORKS, FIELDS GET POPULATED.
Not sure what I'm doing wrong. The first line of code should work because i'm using ControllerAs right ?
Can someone please advise.
Thank you
//UI ROUTER
.state('dashboard.editUser',
{
url: '/editUser/:UserId',
templateUrl: 'app/components/admin/user/editUser.html',
controller: 'editUserController',
controllerAs: 'tF',
ncyBreadcrumb:
{
parent: 'dashboard',
label: 'Edit User'
},
});
//EDIT CONTROLLER
(function () {
var injectParams = ['$stateParams', '$state', 'userService'];
function editUserController($stateParams, $state, userService) {
var vm = this;
//load user
var loadUser = function () {
var userId = $stateParams.UserId;
vm.user = null;
userService.GetUserDetail(userId)
.success(function (data) {
vm.user = data;
})
.error(function (error) {
vm.status = 'Error retrieving data! ' + error.message;
});
};
loadUser();
};
editUserController.$inject = injectParams;
angular
.module('fatApp.userModule')
.controller('editUserController', editUserController);
}());
I want to make sure I get the data from firebase before my app is loaded. I am using this code but it's not working: the program never reach the code deferred.resolve(); and the appCtrl does not run...
Any idea?
appCtrl :
.controller('AppCtrl', function($scope, $ionicModal, $timeout,$firebaseAuth,$state,myWordsListServ) {
$scope.myWordsList = myWordsListServ.contacts;
console.log("myWordsList" , $scope.myWordsList);
})
myWordsListServ:
.factory('myWordsListServ', function($firebase, $q) {
return {
contacts: null,
promiseToHaveMyLists: function() {
var deferred = $q.defer();
var firebaseMainRef = new Firebase("https://myApp.firebaseio.com");
var authData = firebaseMainRef.getAuth();
if(authData){
this.contacts = $firebase(new Firebase("https://myApp.firebaseio.com/users/" + authData.uid));
this.contacts.$on('value', function(loadedData) {
deferred.resolve();
});
}
else{
deferred.resolve();
}
return deferred.promise;
}
};
})
part of my app.js:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl',
resolve : {
myWordsList : function(myWordsListServ) {
return myWordsListServ.promiseToHaveMyLists();
}
},
})
You seem to be using a pre-1.0 version of AngularFire:
this.contacts = $firebase(new Firebase("https://myApp.firebaseio.com/users/" + authData.uid));
this.contacts.$on('value', function(loadedData) {
deferred.resolve();
});
The $firebase class was an intermediate object and (if I recall correctly) would never find events. What you're probably looking to do is:
this.contacts = $firebase(new Firebase(...)).$asObject();
This returns a $firebaseObject which does fire events.
You might want to upgrade to AngularFire 1.0, in which case you'd get the same result with:
this.contacts = $firebaseObject(new Firebase(...));
I have a controller & view which computes and displays a specific element. When I click a button on the view, I want to open another which shows more information about the item.
What is the best way to pass the element to another controller for display?
Many thanks
You could build a service which get data from one controller and passing it to the other, like so:
.factory('shareData', function(){
var finalData;
return {
sendData: function(data) {
finalData = data;
},
getData: function() {
return finalData;
}
}
});
Another option you have is to create a shared varraible in your main controller('app' controller in ionic)
.controller('app', function($scope){
$scope.data;
})
.controller('app.controller1', function($scope){
$scope.data = "Hello World";
})
.controller('app.controller2', function($scope){
console.log($scope.data); //Hello World
});
Yet another option besides those included by Matan Gubkin is including the array in the $rootScope: this way you will be able to access it from everywhere.
#Matan Gubkin idea is close, but you should use the service to get the specific item that you desire.
.factory('shareData', function(){
var finalData = ["Hello World",'more data'];
return {
sendData: function(data) {
finalData = data;
},
getData: function() {
return finalData;
},
getItem:function(_id) {
return finalData[_id];
}
}
});
then in setting up your controllers
.controller('app.controller', function($scope){
})
.controller('app.controller-list', function($scope,shareData){
$scope.data = shareData.getData();
})
.controller('app.controller-detail', function($scope,$stateParams,shareData){
// use stateParams to get index
console.log(shareData.getItem($stateParams.index)); //
});
setting up your routes
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
controller: 'app.controller'
})
.state('app.controller-list', {
url: "/list",
})
.state('app.controller-detail', {
url: "/detail/:index", // <-- pass in index as stateParam
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/app/list');
});