Jasmine testing AngularJS $on - javascript

I want to test my typescript angular code with jasmine, but I get this error when I'm running it.
TypeError: 'undefined' is not an object (evaluating 'scope.LessonCtrl.statistic')
I'm trying to test this code:
export class LessonCtrl {
scope: angular.IScope;
statistic: Statistic;
constructor($scope) {
this.scope = $scope;
this.statistic = new Statistic();
this.scope.$on("timer-stopped", function(event, data) {
var scope: any = event.currentScope;
scope.LessonCtrl.statistic.calculateTypingSpeed(data.millis);
scope.LessonCtrl.statistic.setTime(data.millis);
});
}
}
With this:
var scope, rootScope, lessonCtrl;
beforeEach(() => inject(($rootScope) => {
scope = $rootScope.$new();
rootScope = $rootScope;
lessonCtrl = new Controllers.LessonCtrl(scope);
}));
it('on test', () => {
rootScope.$broadcast('timer-stopped', [{millis: 1000}]);
expect(true).toBe(true); // i will expect something else but i have errors
});
Can anyone help me with this?

You assign statistic to the context (this), not scope.LessonCtrl. By using an arrow function the context will be preserved inside the .$on callback
Arrow functions capture the this value of the enclosing context...
export class LessonCtrl {
scope: angular.IScope;
statistic: Statistic;
constructor($scope) {
this.scope = $scope;
this.statistic = new Statistic();
this.scope.$on("timer-stopped", (event, data) => {
var scope: any = event.currentScope;
this.statistic.calculateTypingSpeed(data.millis);
this.statistic.setTime(data.millis);
});
}
}

Related

Angularjs testing - Initialize controller with binding

I have a controller like this.
app.controller('MyCtrl', function() {
let ctrl = this;
if (ctrl.contract) {
ctrl.contract.typeId = ctrl.contract.type.id;
} else {
ctrl.contract = {
name: 'test'
};
}
....
It can either have, or not have contract bound to it.
Problem arises in my test
describe('MyCtrl', () => {
let ctrl;
beforeEach(angular.mock.module('mymodule'));
beforeEach(angular.mock.module('ui.router'));
describe('Update contract', () => {
beforeEach(inject((_$controller_) => {
ctrl = _$controller_('MyCtrl', {}, {
contract: {
type: {
id: 2
}
}
});
}));
it('should set the contract.typeId to be the id of the type of the contract that was sent in', () => {
expect(ctrl.contract.typeId).toBe(ctrl.contract.type.id);
});
})
});
I pass in a contract object, which means it should go into the first if in my controller and set the typeId.
But it always goes into the else no matter what I do.
How can I make sure the controller don't run or start before all my variables are bound to it?
I believe that the issue is that you need to get the $controller service directly and also pass a valid scope (not an empty object)
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl', {
$scope: scope,
}, {
contract: {
type: {
id: 2
}
}
});
}));

how to resolve my unit test issue

I am trying to build unit test for my case
I have something in my controller like
$scope.getDetail = function(name) {
//other codes
//using testFactory to get details
testFactory.getDetail(name).then(function(detail){
console.log(detail)
})
//other codes
}
Factory file
var factory = {};
factory.getDetail = function(name) {
//calculate…etc
return details;
}
return factory;
Unit test file
describe('controller', function () {
var testCtrl, scope, testFactory;
beforeEach(module('testApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _testFactory_) {
scope = _$rootScope_.$new();
testFactory = _testFactory_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
};
var name = 'test';
spyOn(testFactory, 'getDetail').and.callFake(function(name) {
return 'success';
});
it('should get details', function() {
var result = testFactory.getDetail(name);
expect(result).toBeTruthy();
})
}));
I am getting
undefined' is not a function (evaluating 'testFactory.getDetail(name)' error.
Can anyone help me to solve this? Not sure what went wrong. Thanks a lot!

Testing $loaded in Karma

I'm working with firebase and angular and I have this function in my controller and I have a test written for it which is passing but I find the test doesn't cover the $loaded portion, How do I test the $loaded part. I used $loaded because firebase returns an object which contains a promise.
Here is the function in my controller
$scope.find = function() {
var uid = $rootScope.currentUser ? ($stateParams.uid || $rootScope.currentUser.uid) : $stateParams.uid;
if (uid) {
console.log(uid, 'uid');
var fellow = User.find(uid);
if (fellow) {
console.log(fellow, 'fellow');
fellow.$loaded(function(data) {
$scope.fellow = data;
$scope.uploadedResult = $scope.fellow.videoUrl;
console.log(data.level, 'level');
if(data.level){
$scope.level = Levels.find(data.level);
}
});
}
}
};
Take Note User is a service that has find method that I'm injecting in my controller .
Here's the test I have which is passing
describe('matsi.controller test', function() {
var User,
scope,
ctrl;
beforeEach(inject(function($controller, $rootScope, $cookies, $injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
Fellow = $injector.get('Fellow');
Log = $injector.get('Log');
MailService = $injector.get('MailService');
stateParams = $injector.get('$stateParams');
rootScope = $injector.get('$rootScope');
Levels = $injector.get('Levels');
User = $injector.get('User');
$location = $injector.get('$location');
rootScope.currentUser = {
uid: 'uid',
fullName: 'Happy fellow'
};
ctrl = $controller('FellowCtrl', {
$scope: scope,
$rootScope: scope
});
$cookies.rootRef = 'https://brilliant-heat-9512.firebaseio.com/';
}));
});
it('should expect find to have been called', function() {
scope.currentUser = {
uid: 'uid'
};
spyOn(User, 'find');
scope.find();
expect(User.find).toHaveBeenCalled();
});
});

Angular Testing - promise not returning data

I can't see why vm.chartData in my HomeCtrl never gets populated with the data i've mocked to it in the beforeEach(). the console.log(scope.vm.chartData) returns undefined even while the other scope vars like graphLoading are defined and changed properly.
describe('HomeCtrl', function () {
var controller, scope, myService, q, $timeout;
beforeEach(module('dashboardApp'));
beforeEach(inject(function ($controller, $rootScope, $q, _$timeout_) {
controller = $controller;
scope = $rootScope.$new();
$timeout = _$timeout_;
myService = jasmine.createSpyObj('Chart', ['get']);
q = $q;
}));
describe('when returning promises', function () {
beforeEach(function () {
myService.get.and.returnValue(q.when( { result:
'Stuff'
}));
controller('HomeCtrl as vm', { $scope: scope, Chart: myService });
scope.$apply();
});
it('test dirty graph init', function () {
expect(scope.vm.graphLoading).toBe(true);
scope.vm.dirtyTestGraph();
scope.$digest();
$timeout.flush();
expect(scope.vm.graphLoading).toBe(false);
console.log(scope.vm.chartData);
});
});
});
relevent code from homectrl
vm.dirtyTestGraph = function() {
vm.graphTitle = 'Deposit Amount';
$timeout(function(){
Chart.get( { interval:'3h', type:'_type:deposit',
from:1416960000000, to:Date.now() } )
.then(function(chart){
vm.graphLoading = false;
vm.chartData = chart.data;
});
}, 2000);
};
and here is the return value of Chart.get in the Chart factory
return $q.all([chartData])
.then(function(data){
var graphData = data[0].data.facets[0].entries;
var newData = [];
graphData.forEach(function(element){
var newElem = {
time: element.time,
deposits: element.total.toFixed(2)
};
newData.push(newElem);
});
return new Chart(newData);
});
Your controller code is looking for a data property in the object within the promise returned by Chart.get:
vm.chartData = chart.data;
But your test's stub is returning an object without a data property:
myService.get.and.returnValue(q.when({
result: 'Stuff'
}));
So vm.chartData gets assigned with undefined.

AngularJS watching service variable from directive, how to correctly implement factory?

I want to watch angular factory variable from inside directive, and act upon change.
I must be missing something fundamental from Javascript, but can someone explain, why approach (1) using inline object works, and approach (2) using prototyping does not?
Does prototype somehow hide user variable scope from angular $watch?
How can i make this code more clean?
(1):
Plunkr demo
angular.module('testApp', [
])
.factory('myUser', [function () {
var userService = {};
var user = {id : Date.now()};
userService.get = function() {
return user;
};
userService.set = function(newUser) {
user = newUser;
};
return userService;
}])
.directive('userId',['myUser',function(myUser) {
return {
restrict : 'A',
link: function(scope, elm, attrs) {
scope.$watch(myUser.get, function(newUser) {
if(newUser) {
elm.text(newUser.id);
}
});
}
};
}])
.controller('ChangeCtrl', ['myUser', '$scope',function(myUser, $scope) {
$scope.change = function() {
myUser.set({id: Date.now()});
};
}]);
(2):
Plunkr demo
angular.module('testApp', [
])
.factory('myUser', [function () {
var user = {id : Date.now()};
var UserService = function(initial) {
this.user = initial;
}
UserService.prototype.get = function() {
return this.user;
};
UserService.prototype.set = function(newUser) {
this.user = newUser;
};
return new UserService(user);
}])
.directive('userId',['myUser',function(myUser) {
return {
restrict : 'A',
link: function(scope, elm, attrs) {
scope.$watch(myUser.get, function(newUser) {
//this watch does not fire
if(newUser) {
elm.text(newUser.id);
}
});
}
};
}])
.controller('ChangeCtrl', ['myUser', '$scope',function(myUser, $scope) {
$scope.change = function() {
myUser.set({id: Date.now()});
};
}]);
Case 1:
myUser.get function reference is called without a context (return user), and the user object is returned as a closure variable.
Case 2:
myUser.get function reference is called without a context (return this.user), and so this.user only just don't throw an error because you are not in "strict mode" where this is pointing to the window object, thus resulting in this.user being just undefined.
What you actually missed is the fact that giving myUser.get as a watcher check function is giving a reference to a function, which will not be applied to myUser as a context when used by the watcher.
As i remember angular watches only properties belonging to the object.
The watch function does this by checking the property with hasOwnProperty

Categories