How to mock $window.Notification - angularjs

I am still learning the ropes when it comes to unit testing with angular. I have an angular service that I use to create HTML5 notifications. Code is similar to the following:
(function() {
'use strict';
angular
.module('blah')
.factory('OffPageNotification', offPageNotificationFactory);
function offPageNotificationFactory($window) {
//Request permission for HTML5 notifications as soon as we can
if($window.Notification && $window.Notification.permission !== 'denied') {
$window.Notification.requestPermission(function (status) { });
}
function OffPageNotification () {
var self = Object.create(OffPageNotification.prototype);
self.visibleNotification = null;
return self;
}
OffPageNotification.prototype.startNotification = function (options) {
var self = this;
self.options = options;
if(self.options.showHtml5Notification && (!self.options.onlyShowIfPageIsHidden || $window.document.hidden)) {
if($window.Notification && $window.Notification.permission !== 'denied') {
self.visibleNotification = new $window.Notification('Notification', {
body: self.options.notificationText,
icon: self.options.notificationIcon
});
}
}
};
.
.
.
return new OffPageNotification();
}
})();
I am attempting to write unit tests for this but am unsure how to mock $window.Notification so it can be used as both a constructor...
self.visibleNotification = new $window.Notification(....)
and also contain properties
if($window.Notification && $window.Notification.permission !== 'denied')
and methods....
$window.Notification.requestPermission(
An example of something I have tried is:
describe('startNotification', function() {
beforeEach(function() {
var mockNotification = function (title, options) {
this.title = title;
this.options = options;
this.requestPermission = sinon.stub();
};
mockNotification.prototype.permission = 'granted';
mockWindow = {
Notification: new mockNotification('blah', {}),
document: {hidden: true}
};
inject(function (_OffPageNotification_) {
OffPageNotification = _OffPageNotification_;
});
});
it('should display a html5 notification if the relevant value is true in the options, and permission has been granted', function(){
var options = {
showHtml5Notification: true,
onlyShowIfPageIsHidden: true
};
OffPageNotification.startNotification(options);
});
});
I get an error saying '$window.Notification is not a constructor' with this setup and I understand why (I am passing in an instantiated version of the mockNotification). But if I set mockWindow.Notification = mockNotification then I get an error when it calls requestPermission since this is undefined.
Any help is appreciated

Notification should be a constructor. And it should have static properties and methods.
All of the relevant properties of mockNotification are instance properties, while they should be static:
function MockNotification() {}
MockNotification.title = title;
MockNotification.options = options;
MockNotification.requestPermission = sinon.stub();
mockWindow = {
Notification: MockNotification,
document: {hidden: true}
};

Related

angularjs unit testing unable to inject a service

I have a really simple service:
'use strict';
angular.module('sapphire.orders').service('deliveryDatesService', service);
function service() {
return {
clearAddressReason: clearAddressReason,
getMinDate: getMinDate
};
//////////////////////////////////////////////////
function clearAddressReason(model, dateReasons, reasons) {
if (model.manualAddress) {
model.overrideDates = true;
reasons.date = dateReasons.filter(function (item) {
return item.value === 'D4';
})[0];
} else {
reasons.address = null;
if (!reasons.date || reasons.date.value === 'D4') {
reasons.date = null;
model.overrideDates = false;
}
}
};
function getMinDate(model) {
var now = new Date();
// If we are not overriding dates, set to today
if (!model.overrideDates) return now;
// If dates are overriden, then the min date is today + daysToDispatch
return new Date(now.setDate(now.getDate() + model.daysToDispatch));
};
};
It has no dependencies, so I want to test the methods.
So I have tried to create a spec like this:
'use strict';
describe('Service: deliveryDatesService', function () {
beforeEach(module('sapphire.orders'));
var service,
reasons,
dateReasons;
beforeEach(inject(function (deliveryDatesService) {
console.log(deliveryDatesService);
service = deliveryDatesService;
reasons = {};
dateReasons = [{ value: 'D4' }];
}));
it('can create an instance of the service', function () {
expect(service).toBeDefined();
});
it('if manual delivery address is true, then override dates should be true', function () {
var model = { manualDeliveryDate: true };
service.clearAddressReason(model, dateReasons, reasons);
expect(model.overrideDates).toBe(true);
});
it('if manual delivery address is false, then override dates should be false', function () {
var model = { manualDeliveryDate: false };
service.clearAddressReason(model, dateReasons, reasons);
expect(model.overrideDates).toBe(false);
});
it('minimum date cannot be less than today', function () {
var model = { };
var minDate = service.getMinDate(model);
var now = new Date();
expect(minDate).toBeGreaterThan(now);
});
});
But my service is always undefined. Can someone tell me what I am doing wrong please?
Update
So, it turns out this is to do with one or more services interfering somehow.
In my karma.conf.js I had declared all my bower applications and then this:
'src/app/app.module.js',
'src/app/**/*module.js',
'src/app/**/*constants.js',
'src/app/**/*service.js',
'src/app/**/*routes.js',
'src/app/**/*.js',
'test/spec/**/*.js'
I created a test service in the root of my scripts directory and then created a spec file to see if it was created. It moaned at me about a reference error in a file that was not related at all. It moaned about this bit of code:
angular.module('sapphire.core').factory('options', service);
function service($rootScope) {
return {
get: get,
save: save
};
//////////////////////////////////////////////////
function get() {
if (Modernizr.localstorage) {
var storageData = angular.fromJson(localStorage.options);
if (storageData) {
return angular.fromJson(storageData);
}
}
return {
background: {
enabled: true,
enableSnow: true,
opacity: 0.6
}
};
};
function save(options) {
if (Modernizr.localstorage) {
localStorage.options = angular.toJson(options);
$rootScope.$options = get();
}
};
};
stating that Modernizr is not defined.
I changed the code to this:
angular.module('sapphire.core').factory('options', service);
function service($rootScope) {
return {
get: get,
save: save
};
//////////////////////////////////////////////////
function get() {
if (typeof Modernizr == 'object' && Modernizr.localstorage) {
var storageData = angular.fromJson(localStorage.options);
if (storageData) {
return angular.fromJson(storageData);
}
}
return {
background: {
enabled: true,
enableSnow: true,
opacity: 0.6
}
};
};
function save(options) {
if (typeof Modernizr == 'object' && Modernizr.localstorage) {
localStorage.options = angular.toJson(options);
$rootScope.$options = get();
}
};
};
and it started working. But my other test was not.
So I changed my references in karma.conf.js to this:
'src/app/app.module.js',
'src/app/orders/orders.module.js',
'src/app/orders/shared/*.js',
'test/spec/**/*.js'
and it started working.
That leads me to believe there is something wrong with my application somewhere. Maybe another reference like Modernizr. I still have an outstanding question though. How can services that are not dependant on another service interfere?
I think it's worth noting that each service, controller, directive is in it's own file and they all follow this structure:
(function () {
'use strict';
angular.module('sapphire.core').factory('options', service);
function service($rootScope) {
return {
get: get,
save: save
};
//////////////////////////////////////////////////
function get() {
if (typeof Modernizr == 'object' && Modernizr.localstorage) {
var storageData = angular.fromJson(localStorage.options);
if (storageData) {
return angular.fromJson(storageData);
}
}
return {
background: {
enabled: true,
enableSnow: true,
opacity: 0.6
}
};
};
function save(options) {
if (typeof Modernizr == 'object' && Modernizr.localstorage) {
localStorage.options = angular.toJson(options);
$rootScope.$options = get();
}
};
};
})();
I am wondering that because I wrap them in anonymous functions that execute themselves, is that what is causing this problem?
* Solution *
So in the end I found out exactly what was causing this issue. It was indeed to do with the file in karma.conf.js. I had told it to load all files and somewhere in there was something it didn't like.
After a bit of playing I finally found what it was and thought I would share it just in case someone else gets here.
The issue was routes. I am using ui.router and it appears that having them in your tests fail.
I changed my files section to this:
'src/app/app.module.js',
'src/app/**/*module.js',
'src/app/**/*constants.js',
'src/app/**/*service.js',
'src/app/**/*controller.js',
//'src/app/**/*routes.js',
'test/spec/**/*.js'
As you can see I have a routes file(s) commented out. If I bring them back in, everything fails.
I think your inner deliveryDatesService variable is hiding the external one.
To avoid that, you can try puting underscores around the inner service variable as per the spec on the website below:
https://docs.angularjs.org/api/ngMock/function/angular.mock.inject
Look for
'Resolving References (Underscore Wrapping)'
on that page.
Then your code would look like this:
beforeEach(inject(function (_deliveryDatesService_) {
console.log(_deliveryDatesService_);
service = _deliveryDatesService_;
reasons = {};
dateReasons = [{ value: 'D4' }];
}));
Also you need to make sure that all required files and directories are declared in karma.conf.js so that the test framework can use them.

Where to handle returning data from Angular service?

I try to handle an Angularjs 1 project by using classes, I have created class as the following
angular.module('Models').factory('Project', function (API, $rootScope) {
var Project = function() {
return new Project.fn.init();
}
// instance methods
Project.fn = Project.prototype = {
init: function() {
this.id = null,
this.name = ""
},
setId: function(id) {
this.id = id;
},
setName: function(name) {
this.name = name;
},...
Now I set the obj attr. in the same class as following:
Project.getAll = function(scopeProjects) {
return API.getAll(this).then(function(response) {
var allProjects = response.data.body;
allProjects.forEach(function (project) {
var obj = new Project();
obj.setId(project.id);
obj.setName(project.name);
scopeProjects.push(obj);
})
}, function(error) {
console.log("Fail : ", error);
});
}
my question is where to use my setters?
Do I have to handle this by returning the data to controllers or the way I use here is the best one?

Backbone Modal with Q.promise issue

We have a method (onOpenNotitiesClicked) for showing a Modal view for entering notes. We have implemented the Backbone Modal plugin for this (https://github.com/awkward/backbone.modal).
There are two situations:
There are not yet notes in the backend: initialize and render the
modal
There are already notes in the backend => first collect them and
then pass the notes to the modal (initialize) and then render
In the first situation it works fine! The modal is shown.
In the second situation, the modal is not shown.
I have debugged both situations and in both situations, alle methods are executed and in the elements, I see the HTML of the modal view.
I suspect this looses some data during the Q/promise data get, but I can't see what/where/how/why....
Anyone any idea what I am doing wrong? Or what I am missing?
The creation of the modal:
onOpenNotitieClicked: function (event) {
var $element, taak, taakId, id, options = {};
$element = this.$(event.currentTarget).closest("li");
id = $element.data("id");
taakId = $element.data("taak-id");
taak = this.getCurrentTask(event);
options.taakKey = id;
options.taakId = taakId;
options.heeftNotities = taak.heeftNotities;
options.datacontroller = this.datacontroller;
this.notitiesOptions = options;
// this.renderNotitieModal(options);
if (taak.heeftNotities) {
this.getActiviteitNotities(taakId).then(_.bind(this.onSuccessGetNotities, this), _.bind(this.onErrorGetNotities, this));
} else {
this.renderNotitieModal(this.notitiesOptions);
}
},
In case there are notes to be collected:
getActiviteitNotities: function (taakId) {
return this.datacontroller.getNotities(taakId);
},
onSuccessGetNotities: function (notities) {
this.notitiesOptions.notities = notities;
this.renderNotitieModal(this.notitiesOptions);
},
onErrorGetNotities: function () {
this.renderNotitieModal(this.notitiesOptions);
},
To get the notes from the backend, Q/promises is used.
getNotities: function (taakId, refresh, klantorderId) {
return Q.promise(function (resolve, reject) {
var options = {};
if (!this.notitiesCollection || this.taakId !== taakId || refresh) {
delete this.notitiesCollection;
this.notitiesCollection = this.createCollection("NotitiesCollection", {
id: this.taakId,
resource: this.NOTITIES_RESOURCE
});
if (taakId) {
this.taakId = taakId;
options = {
data: {
parentId: this.taakId
}
};
} else if (klantorderId) {
options = {
data: {
klantorderId: klantorderId
}
};
}
resolve(this.notitiesCollection.fetch(options));
} else if (this.notitiesCollection) {
resolve(this.notitiesCollection.toJSON());
} else {
reject("ERROR");
}
}.bind(this));
},
Notities.js (the modal view):
(function () {
"use strict";
App.Bewaking.modals.BewakingNotitieModal = Backbone.Modal.extend({
template: JST.bewaking_notitie_modal, //jshint ignore:line
title: "Notities",
formatter: new App.Main.helpers.Formatter(),
events: {
"click #save-notitie": "onSaveNotitieClicked"
},
initialize: function (options) {
this.taakId = options.taakId;
this.taakKey = options.taakKey;
this.datacontroller = options.datacontroller;
this.notities = options.notities;
},
afterRender: function () {
console.log("afterRender");
this.$notitieModal = this.$("#notitieModal");
this.$nieuweNotitie = this.$("#nieuwe-notitie");
this.$notitieErrorTekst = this.$("#notitie-error-tekst");
this.$notitieModal.on("shown.bs.modal", function () {
this.$nieuweNotitie.focus();
}.bind(this));
},
render: function () {
console.log(this.notities);
this.$el.html(this.template({
formatter: this.formatter,
notities: this.notities
}));
return this;
}
});
}());

AngularJS local storage - initialize app retrieving local-stored data

I'm pretty new to angular and I'm trying to avoid losing items added on a simple cart application when the user refreshes the page.
I'm using angularLocalStorage (https://github.com/agrublev/angularLocalStorage) but don't know how to retrieve it back the content.
My lines:
var myApp = angular.module('ionicApp', ['ionic','angularLocalStorage']);
myApp.factory('prodottiData', function($http) {
return {
getFooOldSchool: function(callback) {
$http.get('http://192.168.1.128/hongkongapp/?json=get_recent_posts&post_type=product&custom_fields=all').success(callback);
}
}
});
myApp.factory('DataService', function() {
var myCart = new shoppingCart("AngularStore");
return {
cart : myCart
};
});
myApp.controller('MyController', function MyController ($scope, storage, $ionicSideMenuDelegate, prodottiData, DataService, $sce) {
$scope.toggleLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mainMenu').toggleLeft();
};
$scope.toggleMySecondMenuLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mySecondMenu').toggleLeft();
};
//adding menu data to the scope object
prodottiData.getFooOldSchool(function(data) {
$scope.menu = data;
});
//adding the cart to the scope object
$scope.cart = DataService.cart;
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
images = $scope.menu;
$scope.showloader = function(){
$scope.shownImage = this.post.thumbnail_images.full.url;
$scope.itemDesc = this.post.content;
$scope.itemPrice = this.post.custom_fields._price[0];
$scope.productName = this.post.title;
$scope.skuProdotto = this.post.id;
}
});
Now, if I check local storage on the console I can see something is really stored, but I miss the way to re-populate the cart at startup.
Any help would be great!
why not just using browser local storage ?
you can add it to your services.js as a new service and just used that.
var storeService = myAppServices.factory('storeService', function() {
var service =
{
setClientData:function(client_details)
{
window.localStorage.setItem( "client_data", JSON.stringify(client_details) );
client_data = client_details;
},
getClientData:function()
{
if (client_data == null)
{
client_data = JSON.parse(window.localStorage.getItem("client_data"));
}
return client_data;
}
}
var client_data = null;
return service;
});
From the documentation, to retrieve, it's storage.get('key')
So, to check after refresh:
if (storage.get('someKey')){
$scope.retrieved_value = storage.get('someKey');
}else{
// whatever
}
You can use localStorage instead windows.localStorage.
if(typeof(Storage)!=="undefined")
{
// Code for localStorage/sessionStorage.
var hello = "Hello World!!";
localStorage.setItem("hello",hello);
// get string
console.log(localStorage.getItem("hello")); // will return 'Hello World!!'
var me = {name:'abel',age:26,gender:'male'};
localStorage.setItem("user", JSON.stringify(me));
//fetch object
console.log(localStorage.getItem("user")); // will return {"name":"myname","age":99,"gender":"myGender"}
var objetos = JSON.parse(localStorage.getItem("user"));
console.log(objetos.name);
}
else
{
// Sorry! No Web Storage support..
}

Variables between factories in angular.js

I'm currently learning angular and have hit a roadblock.
The second factory (shown below) makes an http request like this: http://example.com/api/get_post/?post_id=7129&callback=JSON_CALLBACK');
I want the post ID to be a variable. So depending on which blog title is clicked, I can pass the correct variable into that http request.
In other words, I guess I want to take a result from the first factory (blogAPIservice) and use it in the second factory.
Makes sense??
<!-- FACTORIES -->
angular.module('blogApp.services',[])
.factory('blogAPIservice',function($http) {
var blogAPI = [];
var blogs = $http.jsonp('http://example.com/api/get_recent_posts/?count=10&callback=JSON_CALLBACK');
blogs.success(function(data) {
$.each(data.posts, function(i, blog) {
var fromNow = moment(blog.date).fromNow();
blogAPI.push({
url: blog.url,
title: blog.title,
excerpt: blog.excerpt,
date : fromNow,
id: blog.id
})
});
});
var factory = {};
factory.getBlogs = function () {
return blogAPI;
};
return factory;
})
.factory('singlePostService',function($http) {
var singleAPI = [];
var postID = '7129';
var singlePost = $http.jsonp('http://example.com/api/get_post/?post_id=7129&callback=JSON_CALLBACK');
singlePost.success(function(data) {
singleAPI.push({
title: data.post.title,
content: data.post.content
})
});
var factory = {};
factory.getSinglePost = function () {
return singleAPI;
};
return factory;
})
And here are the controllers:
angular.module('blogApp.controllers', [])
.controller('resultsController',function($scope, blogAPIservice) {
$scope.keywordFilter = null;
$scope.blogs = [];
init();
function init() {
$scope.blogs = blogAPIservice.getBlogs();
}
function grabID() {
$(this).attr('rel');
}
})
.controller('singlePostController',function($scope, singlePostService) {
$scope.keywordFilter = null;
$scope.singlePost = [];
init();
function init() {
$scope.singlePost = singlePostService.getSinglePost();
}
})
And finally the markup:
<li ng-repeat="blog in blogs">
{{ blog.title }}
</li>
You can inject the first service into the second one like this:
.factory('singlePostService',function($http, blogAPIservice) {
//Do something with blogAPIservice
}
For more information about depenency injection read the docs

Resources