Unit testing angular directive - very stuck - angularjs

I am having a ton of trouble testing my directive.
Right now I am stuck with these two errors
Test 'pbImagePicker template:should render HTML based on scope correctly' failed
TypeError: 'undefined' is not a function (evaluating 'element.click')
Test 'pbImagePicker element.click():should assign data from resolved promise when clicked' failed
TypeError: 'undefined' is not a function (evaluating 'scope.$new(true)')
Directive:
angular.module('pb.campaigns.directives')
.directive('pbImagePicker', ['$window', '$document', function ($window, $document) {
return {
restrict: "E",
template: '<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />',
scope: {
fileId: '=pbFileId',
accountId: '=pbAccountId',
defaultSrc: '#pbDefaultSrc',
width: '#pbWidth',
height: '#pbHeight'
},
controller: 'pbImagePickerController',
link: function (scope, element, attrs) {
scope.$watch('defaultSrc', function (value) {
if (value !== undefined) {
scope.imageSource = value;
}
});
element.click(function () {
scope.pickImage(scope.accountId).then(function (image) {
scope.imageSource = image.storageUrl;
scope.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
}
};
}]);
I am also trying to test the element.click by using the triggerHandler but im not having any luck. Testing the link: $watch function has also proven to be impossible for me. Has anyone ever done this? I have found simular examples but none that I seem to be able to get working
this is what I currently have:
describe('pbImagePicker', function () {
beforeEach(module('pb.campaigns.controllers'));
beforeEach(module('pb.campaigns.directives'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var compile;
var scope;
var mockModal = {};
// var element = {
// click: function () { }
//};
beforeEach(inject(function ($compile, $rootScope) {
compile = $compile;
scope = $rootScope.$new();
}));
describe('template', function () {
it('should render HTML based on scope correctly', function () {
//scope = {
scope.fileId = '43253';
scope.accountId = '3874';
scope.imageSource = 'http://www.gravatar.com/avatar/12345?s=40&d=identicon';
scope.width = '250';
scope.height = '250';
//};
var html = angular.element('<pb-image-picker data-pb-default-src="{{ campaign.sponsorSmallImageUrl || \'/content/img/placeholder-sm.jpg\' }}" data-pb-file-id="campaign.sponsorSmallImageId" data-pb-account-id="currentAccountId" data-pb-height="55"></pb-image-picker>');
compiled = compile(html)(scope);
compiled.triggerHandler('click');
scope.$digest();
expect(scope.width).toEqual('250');
expect(scope.accountId).toEqual('3874');
expect(element.hasClass('img-rounded')).toBe(true);
expect(element.attr('width')).toEqual('250');
expect(element.attr('height')).toEqual('250');
expect(element.attr('alt')).toMatch('Image Picker');
//or even expect it to match the scope value
expect(element.attr('width')).toEqual(scope.width);
});
});
describe('element.click()', function () {
var image;
beforeEach(inject(function ($q) {
image = {
storageUrl: 'http://www.pressboard.com/avatar/7453',
fileId: 6432342
};
scope.pickImage = function (accountId) {
var defer = $q.defer();
defer.resolve(image);
return defer.promise;
};
scope = {
fileId: '43253',
accountId: '3874',
imageSource: 'http://www.gravatar.com/avatar/12345?s=40&d=identicon',
width: '250',
height: '250'
};
}));
it('should assign data from resolved promise when clicked', function() {
var element = compile('<pb-image-picker data-pb-default-src="{{ campaign.sponsorSmallImageUrl || \'/content/img/placeholder-sm.jpg\' }}" data-pb-file-id="campaign.sponsorSmallImageId" data-pb-account-id="currentAccountId" data-pb-height="55"></pb-image-picker>')(scope);
element.triggerHandler('click');
scope.$digest();
expect(scope.imageSource).toEqual(image.storageUrl);
expect(scope.fileId).toEqual(image.fileId);
});
});
});
the pickImage function is defined in my controller as
$scope.pickImage = function (accountId) {
var modalOptions = {
templateUrl: '/app/campaigns/directives/ImagePicker.html',
controller: 'pbImagePickerModalController',
resolve: {
accountId: function () {
return accountId;
}
}
};
var modalInstance = $modal.open(modalOptions);
return modalInstance.result;
};

Related

Very challenging directive - looking for guidance on how to unit test

I just realized that none of the tests I have written for my directive are working in a way that actually tests anything so I am rewriting them. Problem is I cant figure out a way to test the link functions.
If anyone has a suggestion I would really really appreciate it.
Thanks for looking
angular.module('pb.webSites.directives')
.directive('pbOrganizationImagePicker', [ function () {
return {
restrict: "E",
template: '<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />',
scope: {
fileId: '=pbFileId',
defaultSrc: '#pbDefaultSrc',
width: '#pbWidth',
height: '#pbHeight'
},
controller: 'pbOrganizationImagePickerController',
link: function (scope, element, attrs) {
scope.$watch('defaultSrc', function (value) {
if (value !== undefined) {
scope.imageSource = value;
}
});
element.on('click', function () {
scope.pickImage().then(function (image) {
scope.imageSource = image.storageUrl;
scope.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
}
};
}]);
Someone asked what I was doing so far to test this directive. Right now the link sections tests are a mess but I thought I would post it here in case anyone else was curious.
describe('pbOrganizationImagePicker', function () {
beforeEach(module('pb.webSites.controllers'));
beforeEach(module('pb.webSites.directives'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var compile;
var scope;
var html = angular.element('<pb-organization-image-picker data-pb-default-src="{{ webSite.profileImageUrl || \'/content/img/placeholder-lg.jpg\' }}" data-pb-file-id="webSite.profileImageId" data-pb-width="200"></pb-organization-image-picker>');
beforeEach(inject(function ($compile, $rootScope) {
compile = $compile
scope = $rootScope.$new();
}));
beforeEach(inject(function ($q, $injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('/app/webSites/directives/OrganizationImagePicker.html').respond(200, '');
scopeObject = {
profileImageUrl: 'http://www.gravatar.com/avatar/12345?s=40&d=identicon',
profileImageId: 54634
};
scope.webSite = {
profileImageId: 6436
};
scope.pickImage = function () {
var defer = $q.defer();
defer.resolve(scopeObject);
return defer.promise;
};
}));
describe('template with profileImageUrl', function () {
beforeEach(function () {
scope.webSite.profileImageUrl = 'http://www.gravatar.com/avatar/12345?s=40&d=identicon';
element = compile(html)(scope);
});
it('should render HTML based on scope correctly', function () {
expect(element.isolateScope().width).toEqual('200');
expect(element.isolateScope().fileId).toEqual(scope.webSite.profileImageId);
expect(element.isolateScope().defaultSrc).toEqual(scope.webSite.profileImageUrl);
});
});
describe('template without profileImageUrl', function () {
beforeEach(function () {
element = compile(html)(scope);
});
it('use default URL if there is no profileImageUrl', function () {
expect(element.isolateScope().defaultSrc).toEqual('/content/img/placeholder-lg.jpg');
});
});
describe('element.click()', function () {
beforeEach(function () {
var html = angular.element('<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />');
});
it('should assign value to scope variables', function () {
template = compile(html)(scope);
template.triggerHandler('click');
template.scope().$apply();
scope.pickImage();
scope.$digest();
expect(scope.imageSource).toEqual(scopeObject.profileImageUrl);
expect(template.isolateScope().fileId).toEqual(scopeObject.profileImageId);
});
});
describe('$watch', function () {
beforeEach(function () {
element.isolateScope().defaultSrc = 'foobar';
element = compile(html)(scope);
});
it('should have expected watchers added to the child scope', function () {
expect(element.isolateScope().$$watchers[0].exp).toBe('defaultSrc');
});
it('$watch should watch for changes on defaultSrc', function () {
element.isolateScope().defaultSrc = "barfoo";
element.isolateScope().$apply();
expect(scope.imageSource).toEqual('barfoo');
});
});
});

angular modal ui bootstrap unit test - undefined is not a function

APP
(function() {
'use strict';
function ArticlesController($templateCache, $modal, articlesData, Articles, $state) {
var articles = this;
articles.data = articlesData.data;
console.log($modal);//Give me LOG: Object{open: function () { ... }}
articles.open = function (article) {
var modalInstance = $modal.open({ // The unit test point to this line
template: $templateCache.get('articles/templates/modalDestroy.html'),
controller: 'ArticleDestroyController',
controllerAs: 'article',
size: 'sm',
resolve: {
articleData: function(){
return article;
}
}
});
modalInstance.result.then(function (article) {
Articles.destroy(article._id).then(function(response) {
var data = [];
angular.forEach(articles.data, function(value, key) {
if( value._id !== article._id){
this.push(value);
}
}, data);
articles.data = data;
})
.catch(function(response) {
console.log(response);
});
});
};
}
function ArticleDestroyController($modalInstance, articleData, Articles) {
var article = this;
article.data = articleData;
article.ok = function () {
$modalInstance.close(articleData);
};
article.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
angular.module('articles.controllers', [])
.controller('ArticlesController', ArticlesController)
.controller('ArticleDestroyController', ArticleDestroyController);
})();
TEST
'use strict';
/* global describe, beforeEach, it, before, after, afterEach, inject, expect, spyOn */
describe('Unit: ArticlesController', function() {
var $rootScope, $scope, $controller;
beforeEach(function() {
module('articles');
});
var fakeModal = {
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
beforeEach(inject(function($modal) {
spyOn($modal, 'open').and.callFake(fakeModal);
}));
beforeEach(inject(function(_$rootScope_,_$controller_, $modal){
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
var articlesData = {data:[{title:'my title',content:'my content'}]};
$controller('ArticlesController as articles', {$scope: $scope, $modal: $modal, articlesData:articlesData});
}));
it('articles.data should create an array with at least one article object ', function() {
expect($scope.articles.data.length).toBe(1);
$scope.articles.open($scope.articles.data[0]);
});
});
When I run gulp unit
I've got
TypeError: undefined is not a function
at ArticlesController.articles.open
point to line 9
var modalInstance = $modal.open
but if I try to check $modal it gives me
console.log($modal);//Give me LOG: Object{open: function () { ... }}
Do you see what's the problem ?
spyOn($modal, "open").and.callFake(function() {
return fakeModal;
});
Ran into this exact issue at work and resolved it in this way based on the documentation for Jasmine 2.0(http://jasmine.github.io/2.0/introduction.html).

element.click in directive unit test not assigning value to variables

I am testing the directive below that uses an isolated scope. I know the triggerHandler is working but for some reason I keep getting the error
Expected undefined to equal 'http://www.gravatar.com/avatar/12345?s=40&d=identicon'.
Directive:
angular.module('pb.webSites.directives')
.directive('pbOrganizationImagePicker', [ function () {
return {
restrict: "E",
template: '<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />',
scope: {
fileId: '=pbFileId',
defaultSrc: '#pbDefaultSrc',
width: '#pbWidth',
height: '#pbHeight'
},
controller: 'pbOrganizationImagePickerController',
link: function (scope, element, attrs) {
scope.$watch('defaultSrc', function (value) {
if (value !== undefined) {
scope.imageSource = value;
}
});
element.on('click', function () {
scope.pickImage().then(function (image) {
scope.imageSource = image.storageUrl;
scope.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
}
};
}]);
Tests:
describe('pbOrganizationImagePicker', function () {
beforeEach(module('pb.webSites.controllers'));
beforeEach(module('pb.webSites.directives'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var compile;
var scope;
var mockModal = {};
var image;
beforeEach(inject(function ($compile, $rootScope) {
compile = $compile
scope = $rootScope.$new();
}));
beforeEach(inject(function ($q, $injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('/app/webSites/directives/OrganizationImagePicker.html').respond(200, '');
scopeObject = {
profileImageUrl: 'http://www.gravatar.com/avatar/12345?s=40&d=identicon',
profileImageId: 54634
};
scope.webSite = {
profileImageId: 6436
};
scope.pickImage = function () {
var defer = $q.defer();
defer.resolve(scopeObject);
return defer.promise;
};
}));
describe('element.click()', function () {
beforeEach(function () {
var html = angular.element('<pb-organization-image-picker data-pb-default-src="{{ webSite.profileImageUrl || \'/content/img/placeholder-lg.jpg\' }}" data-pb-file-id="webSite.profileImageId" data-pb-width="200"></pb-organization-image-picker>');
element = compile(html)(scope);
element.triggerHandler('click');
});
it('should assign value to scope variables', function () {
scope.pickImage();
scope.$digest();
expect(scope.imageSource).toEqual(scopeObject.profileImageUrl);
expect(scope.fileId).toEqual(scopeObject.profileImageId);
});
});
});
I have also tried changing the test to the following since Im pretty sure in the test above I am faking the test a bit. However here I get pickImage() was never called. Even if you dont see the problem which method do you think is better for testing?
describe('element.click()', function () {
it('should assign value to scope variables', function () {
element = compile(html)(scope);
spyOn(scope, 'pickImage');
element.triggerHandler('click');
scope.$apply();
//scope.pickImage();
expect(scope.pickImage).toHaveBeenCalled();
scope.$digest();
expect(scope.imageSource).toEqual(scopeObject.profileImageUrl);
expect(scope.fileId).toEqual(scopeObject.profileImageId);
});
});
element.on('click', function () {
scope.$apply(function() {
scope.pickImage().then(function (image) {
scope.imageSource = image.storageUrl;
scope.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
});
Wrap the code in your click handler in an $apply.
I suspect the real problem is that your directive uses an isolate scope, so it actually won't have a "pickImage()" method and when you assign imageSource and fileId, you are putting them on the directive scope, and not the scope that your test is trying to validate (the parent scope).
Your test assigns pickImage() and webSite to the scope of your test element. Since you use an isolated scope, your directive won't have access to these methods and properties. You should probably move these to a service and inject them into the directive.
It's not "correct", but to test the theory you can change your directive to:
element.on('click', function () {
scope.$apply(function(){
scope.$parent.pickImage().then(function (image) {
scope.$parent.imageSource = image.storageUrl;
scope.$parent.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
});
This IS NOT something you want in production code, but I'm just trying to demonstrate how the different scopes are related to each other.

Angularjs unit test resolve promise inside custom directive with external template

I have a custom directive that uses an external template and is passed data from a service. I decided to ensure that the promise was resolved before modifying the data, which was fine in the actual code but broke my unit tests, which is annoying. I have tried a number of variations but am now stuck. I am using 'ng-html2js' preprocessor.
Here is the unit test
describe('ccAccordion', function () {
var elm, scope, deferred, promise, things;
beforeEach(module('ccAccordion'));
// load the templates
beforeEach(module('components/accordion/accordion.html'));
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
things = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
deferred = $q.defer();
promise = deferred.promise;
promise.then(function (things) {
scope.items = things;
});
// Simulate resolving of promise
deferred.resolve(things);
// Propagate promise resolution to 'then' functions using $apply().
scope.$apply();
// compile the template?
$compile(elm)(scope);
scope.$digest();
}));
it('should create clickable titles', function () {
var titles = elm.find('.cc-accord h2');
expect(titles.length).toBe(2);
expect(titles.eq(0).text().trim()).toBe('Scifi');
expect(titles.eq(1).text().trim()).toBe('Comedy');
});
I have left out the custom addMatchers and the rest of the tests. The error I get is
TypeError: 'undefined' is not an object (evaluating 'scope.items.$promise')
Here is the directive
var ccAccordion = angular.module("ccAccordion", []);
ccAccordion.directive("ccAccordion", function () {
return {
restrict: "AE",
templateUrl: "components/accordion/accordion.html",
scope: {
items: "="
},
link: function (scope) {
scope.items.$promise.then(function (items) {
angular.forEach(scope.items, function (item) {
item.selected = false;
});
items[0].selected = true;
});
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
}
};
});
This is where the directive is used in main.html
<cc-accordion items="genres"></cc-accordion>
In the main controller the genres service is passed in ie
angular.module('magicApp')
.controller('GenresCtrl', ['$scope', 'BREAKPOINTS', 'Genre',
function ($scope, BREAKPOINTS, Genre) {
$scope.bp = BREAKPOINTS;
$scope.genres = Genre.query();
}]);
Okay, I would move that code you put in link into the controller. The data processing should probably happen in a service. I know you've been told big controllers are bad, but big linking functions are generally worse, and should never do that kind of data processing.
.controller('GenresCtrl', ['$scope', 'BREAKPOINTS', 'Genre',
function ($scope, BREAKPOINTS, Genre) {
$scope.bp = BREAKPOINTS;
$scope.genres = Genre.query().then(function (items) {
angular.forEach(scope.items, function (item) {
item.selected = false;
});
items[0].selected = true;
});
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
});
Your link function is now empty. Define items on the rootScope instead, this ensures that the isolateScope and your directive interface are working correctly.
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
things = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
scope.items = things; // Tests your directive interface
// compile the template?
$compile(elm)(scope);
scope.$digest();
}));
The behavior of the promise should be tested in a controller test, by mocking the return value of the service. Your problem with the $promise test has been solved.
The actual issue was that you were assuming that $q.defer() gave you the same kind of promise as the angular $http, but that is solved by design instead.
As peter said remove the promise from the directive and add it to the controller
angular.module('magicApp')
.controller('MainCtrl', ['$scope', 'Genre',
function ($scope, Genre) {
$scope.genres = Genre.query();
$scope.genres.$promise.then(function () {
angular.forEach($scope.genres, function (genre) {
genre.selected = false;
});
$scope.genres[0].selected = true;
});
}]);
This will also allow the controller to specify which tab is selected to begin with.
In the directive
var ccAccordion = angular.module("ccAccordion", []);
ccAccordion.directive("ccAccordion", function () {
return {
restrict: "AE",
templateUrl: "components/accordion/accordion.html",
scope: {
items: "="
},
link: function (scope) {
scope.select = function (desiredItem) {
(desiredItem.selected === true) ? desiredItem.selected = false : desiredItem.selected = true;
angular.forEach(scope.items, function (item) {
if (item !== desiredItem) {
item.selected = false;
}
});
};
}
};
});
The directive unit test now looks like this
describe('ccAccordion', function () {
var elm, scope, deferred, promise, things;
beforeEach(module('ccAccordion'));
beforeEach(function () {
jasmine.addMatchers({
toHaveClass: function () {
return {
compare: function (actual, expected) {
var classTest = actual.hasClass(expected);
classTest ? classTest = true : classTest = false;
return {
pass: classTest,
message: 'Expected ' + angular.mock.dump(actual) + ' to have class ' + expected
};
}
};
}
});
});
// load the templates
beforeEach(module('components/accordion/accordion.html'));
beforeEach(inject(function ($rootScope, $compile, $q) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
scope.genres = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
$compile(elm)(scope);
scope.$digest();
}));
it('should create clickable titles', function () {
var titles = elm.find('.cc-accord h2');
expect(titles.length).toBe(2);
expect(titles.eq(0).text().trim()).toBe('Scifi');
expect(titles.eq(1).text().trim()).toBe('Comedy');
});
it('should bind the content', function () {
var contents = elm.find('.cc-accord-content div:first-child');
expect(contents.length).toBe(2);
expect(contents.eq(0).text().trim()).toBe('Scifi description');
expect(contents.eq(1).text().trim()).toBe('Comedy description');
});
it('should change active content when header clicked', function () {
var titles = elm.find('.cc-accord h2'),
divs = elm.find('.cc-accord');
// click the second header
titles.eq(1).find('a').click();
// second div should be active
expect(divs.eq(0)).not.toHaveClass('active');
expect(divs.eq(1)).toHaveClass('active');
});
});
And the unit test for main controller now has the added property of selected
'use-strict';
describe('magicApp controllers', function () {
// using addMatcher because $resource is not $http and returns a promise
beforeEach(function () {
jasmine.addMatchers({
toEqualData: function () {
return {
compare: function (actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
beforeEach(module('magicApp'));
beforeEach(module('magicServices'));
describe('MainCtrl', function () {
var scope, ctrl, $httpBackend;
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/api/genres').
respond([{title: 'Scifi', selected: true}, {title: 'Comedy', selected: false}]);
scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {$scope: scope});
}));
it('should create "genres" model with 2 genres fetched from xhr', function () {
expect(scope.genres).toEqualData([]);
$httpBackend.flush();
expect(scope.genres).toEqualData(
[{title: 'Scifi', selected: true}, {title: 'Comedy', selected: false}]);
});
});
});

Need help completing unit test for angular $modal

I am trying to get the "make sure modalInstance.result.then is executed" test in my code to pass. Right now it says Expected [ undefined, 'http://www.link1.com', 'http://www.link2.com', 'http://www.link3.com' ] to contain 'http://www.pressboardmedia.com' I believe this has something to do with the promise needing to be mocked so it passes along the campaignLink but I am unsure if this is the problem and cannot seem to get it to work regardless. please let know if you have any ideas.
Thanks!
Controller File
angular.module('pb.campaignLinks.controllers')
.controller('CampaignLinksController', ['$scope', '$timeout', '$modal', '$stateParams', 'global', 'campaignLinkService', function ($scope, $timeout, $modal, $stateParams, global, campaignLinkService) {
$scope.init = function (currentView) {
$scope.partialViews = {
campaignLinkList: "/app/campaignLinks/views/_list.html",
};
$scope.currentView = currentView;
$scope.getCampaignLinks(currentView, 10);
};
$scope.getCampaignLinks = function (currentView, length, continuationToken) {
// When loading list items, display loading image
if ($scope.campaignLinks) $scope.campaignLinks.loading = true;
var promiseObj = null;
if (currentView.campaignId && currentView.campaignId !== 0 && !currentView.buyRequestId) {
promiseObj = campaignLinkService.getCampaignLinks(global.activeOrganizationId, currentView.campaignId, length, continuationToken)
} else if (currentView.campaignId && currentView.buyRequestId && currentView.campaignId !== 0 && currentView.buyRequestId !== '') {
promiseObj = campaignLinkService.getCampaignBuyRequestLinks(global.activeOrganizationId, currentView.campaignId, currentView.buyRequestId, length, continuationToken);
} else if (currentView.webSiteId && currentView.buyRequestId && currentView.webSiteId !== 0 && currentView.buyRequestId !== '') {
promiseObj = campaignLinkService.getWebSiteBuyRequestLinks(global.activeOrganizationId, currentView.webSiteId, currentView.buyRequestId, length, continuationToken);
}
if (promiseObj) {
promiseObj.then(function (data) {
// If there are already some campaign links being displayed, add newly loaded list to the end
if ($scope.campaignLinks) {
$scope.campaignLinks.continuationToken = data.continuationToken;
$scope.campaignLinks.total += data.total;
$.each(data.items, function (index) {
$scope.campaignLinks.items.push(data.items[index]);
});
} else {
// Otherwise add loaded list to scope
$scope.campaignLinks = data;
}
// When done loading, hide loading image
$scope.campaignLinks.loading = false;
});
}
};
$scope.openAddCampaignLink = function (campaignId) {
var modalOptions = {
templateUrl: '/app/campaignLinks/views/_add.html',
controller: 'AddCampaignLinksModalController',
resolve: {
campaignId: function () {
return campaignId;
}
}
};
var modalInstance = $modal.open(modalOptions);
modalInstance.result.then(function (campaignLink) {
// Add new item to list, otherwise wait for it to be loaded manually.
$scope.campaignLinks.items.unshift(campaignLink);
$scope.campaignLinks.total += 1;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
$scope.openRemoveCampaignLink = function (campaignId, campaignLink, index) {
var modalOptions = {
templateUrl: '/app/campaignLinks/views/_delete.html',
controller: 'RemoveCampaignLinksModalController',
resolve: {
campaignId: function () {
return campaignId;
},
campaignLink: function () {
return campaignLink;
}
}
};
var modalInstance = $modal.open(modalOptions);
modalInstance.result.then(function () {
// Splice the deleted item from the list without reloading all
$scope.campaignLinks.items.splice(index, 1);
$scope.campaignLinks.total -= 1;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
// Once everything is loaded, initialize controller
// init();
}]);
Test file:
describe('CampaignLinksController', function () {
//make module avalible to tests
beforeEach(module('pb.campaignLinks.controllers'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var $controller;
var mockPromiseObj;
var length = 200;
var mockModal = {};
var mockCampaignLinks = {
total: 3,
items: ['http://www.link1.com', 'http://www.link2.com', 'http://www.link3.com']
};
var mockCurrentView = {};
var mockGlobal = { activeOrganizationId: 54 };
var continuationToken = {
nextRowKey: 'fdsf2',
nextPartitionKey: '5432gee'
};
var campaignDetails = {
campaignId: 453,
campaignLink: 'http://www.pressboardmedia.com',
index: 1
};
var mockCampaignLinkService = {
//move campaignDetails here
campaignLinks: {
total: 3,
//length here
items: ['http://www.link1.com', 'http://www.link2.com', 'http://www.link3.com']
},
//all but delete must return a promiseObj
getCampaignLinks: jasmine.createSpy('mockCampaignLinkService.getCampaignLinks').and.returnValue(mockPromiseObj),
getCampaignBuyRequestLinks: jasmine.createSpy('mockCampaignLinkService.getCampaignBuyRequestLinks').and.returnValue(mockPromiseObj),
getWebSiteBuyRequestLinks: jasmine.createSpy('mockCampaignLinkService.getWebSiteBuyRequestLinks').and.returnValue(mockPromiseObj),
deleteCampaignLink: jasmine.createSpy('mockCampaignLinkService.deleteCampaignLinks')
};
beforeEach(inject(function (_$controller_) {
$controller = _$controller_;
}));
beforeEach(inject(function ($rootScope) {
controller = $controller('CampaignLinksController',
{
$scope: scope,
$modal: mockModal,
//$stateParams: mockStateParams,
global: mockGlobal,
campaignLinks: mockCampaignLinks,
campaignLinkService: mockCampaignLinkService,
currentVeiw: mockCurrentView,
//promiseObj: mockPromiseObj
});
}));
describe('openAddCampaignLink()', function () {
var actualOptions;
var modalOptions = {
templateUrl: '/app/campaignLinks/views/_add.html',
controller: 'AddCampaignLinksModalController',
resolve: {
campaignId: jasmine.any(Function)
}
};
beforeEach(inject(function ($q) {
mockModal.open = function (options) {
actualOptions = options;
var defer = $q.defer();
defer.resolve();
return { result: defer.promise };
}
}));
it("make sure modalInstance.result.then is executed", function () {
scope.campaignLinks = mockCampaignLinkService.campaignLinks;
scope.openAddCampaignLink(campaignDetails.campaignId, campaignDetails.campaignLink, campaignDetails.index);
scope.$digest();
expect(scope.campaignLinks.total).toEqual(4);
expect(scope.campaignLinks.items).toContain(campaignDetails.campaignLink);
});
});
I think your problem is that the argument "campaignLink" that is supplied to your modalInstance.result.then function will always be undefined when you test this. The code that calls the modal close should be tested that it is supplying the campaignLink correctly. You only want to test that the proper code gets executed when the modal is closed. We do that by supplying a function that can be tested on it's own to the modalInstance.result.then call instead of doing an inline function so it can be tested easier.
Here's an example:
Here's the code that opens the modal:
$scope.editGradeClick = function () {
var modalInstance = $modal.open({
templateUrl: templateSrv.templateUrl('/Templates/App/Courses/CourseLessonGradeModal.html'),
controller: courseLessonGradeModalBootstrap,
backdrop: 'static',
keyboard: false,
resolve: {
data: $scope.editGradeModalResolve
}
});
modalInstance.result.then(
null,
$scope.editGradeModalDismissCallback
);
};
Notice how we supply a function $scope.editGradeModalDismissCallback instead of writting an inline function there for a modal dismiss. This allows us to test that logic in it's own test.
Now here is the test. We only care that the above function was called since we will be testing the specific function in it's own test. That happens at the last line in the test.
var $controllerConstructor, $scope, $q;
beforeEach(function () {
module('appModule');
inject(function ($controller, $rootScope, _$q_) {
$controllerConstructor = $controller;
$scope = $rootScope.$new();
$q = _$q_;
});
});
it('should execute $scope.editGradeModalDismissCallback when modal is dismissed', inject(function (templateSrv) {
var $modal = {};
$modal.open = function () {
var queryPromise = $q.defer();
queryPromise.reject();
return { result: queryPromise.promise };
};
$controllerConstructor('CourseLessonGradeListCtrl', { $scope: $scope, $modal: $modal, templateSrv: templateSrv});
spyOn($modal, "open").andCallThrough();
spyOn($scope, "editGradeModalDismissCallback");
$scope.editGradeClick();
$scope.$root.$digest();
expect($scope.editGradeModalDismissCallback).toHaveBeenCalled();
}));
I hope this helps you. In this example we are rejecting the promise because we are testing the modal dismiss not close, fyi...

Resources