As stated in the title of the question, I am getting undefined when calling isolateScope(). I tried to Unit Test my Angular Directive based on the instructions given on Unit Testing AngularJS Directives With External Templates.
Here is my code:
directive.js
angular.module("myTestApp")
.directive("testDirective", function(){
return{
restrict: "E",
require: "ngModel",
scope:{
ngModel: "=",
label: "#?"
},
templateUrl: "/mypath/templates/testDirective.html",
link: function($scope, element, attributes, ngModelCtrl){
$scope.functions = {},
$scope.settings = {
label: $scope.label ? $scope.label : ""
}
}
};
});
I have used karma-ng-html2js-preprocessor for templateUrl.
karma.conf.js (Code contains only the parts that are concerned with ng-html2js)
files:[
//All Js files concerning directives are also included here...
'/mypath/templates/*.html'
],
preprocessors: {
'/mypath/templates/*.html': [ng-html2js']
},
ngHtml2JsPreprocessor: {
moduleName: 'template'
},
plugins: ['karma-*'],
directiveSpec.js
describe("testDirective Test", function(){
var scope, $compile,$httpBackend, template;
beforeEach(module('myTestApp'));
beforeEach(module('template'));
beforeEach(inject(function($rootScope, _$compile_, _$httpBackend_){
scope = $rootScope.$new();
$compile = _$compile_;
$httpBackend = _$httpBackend_;
}));
it("should check if the value of label attribute id set to dummyLabel", function(){
$httpBackend.expect('GET','/mypath/templates/testDirective.html').respond();
scope.label = 'dummyLabel';
var element = angular.element('<test-directive label= "label" ></test-directive>');
element = $compile(element)(scope);
console.log(element);
scope.$digest();
console.log('Isolate scope: '+ element.isolateScope());
expect(element.isolateScope().label).toEqual('dummyLabel');
});
});
Here the console.log(element); prints {0: <test-directive label="label" class="ng-scope"></test-directive>, length: 1}
Problem: console.log('Isolate scope: '+ element.isolateScope()); gives undefined.
I looked on this problem on many questions in StackOverflow but was not able to find the correct solution.
Also, I have to use the $httpBackend to get the html file or else it throws Unexpected Request error.
I would be really grateful for any help since I am stuck on this error since past one week!
Just a guess here at first glance but...
Your .respond() needs to return something. Right now you are intercepting the request to your template and not responding with anything, thus resulting in empty data and isolateScope() being undefined.
For someone who might have the same problem:
The directive testing do not work as expected when using the $httpBackend to mock the HTML files. It should be and it is possible to include HTML files by simply using the ng-html2js plugin. So I removed the $httpBackend calls and used stripPrefix to properly include my HTML files as modules. It worked!
Hope it helps someone else!
Related
I am not sure if what I am doing is completely wrong, but when I switched from "directive" to "components" for defining a few of my HTML elements, I suddenly broke all of my Karma tests. here's what I have:
karam.conf.js
...
preprocessors: {
'module-a/module-a.view.html': ['ng-html2js'],
...,
'module-z/module-z.view.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
moduleName: 'theTemplates'
},
...
module-a.component.js
(function(){
"use strict";
angular.module('ModuleA').component('moduleAComponent',{
controller: 'ModuleAController as moduleAVm',
templateUrl: 'module-a/module-a.view.html'
});
})();
module-a-tests.js
"use strict";
describe('ModuleA',function(){
beforeEach(module('ModuleA'));
describe('Controller',function(){
...
});
describe('Component',function(){
var element, $rootScope;
beforeEach(module('theTemplates'));
beforeEach(inject([
'$compile','$rootScope',
function($c,$rs) {
$rootScope = $rs;
element = $c('<module-a-component></module-a-component>')($rootScope);
$rootScope.$digest(); // ???
}
]));
it('should have moduleAVm',function(){
expect(element.html()).not.toBe(''); // FAILS HERE
expect(element.html()).toContain('moduleVm'); // FAILS HERE TOO
});
});
});
The Error:
Expected '' not to be ''.
OK, after reading Angular's documentation more thoroughly, I came across this statement:
The easiest way to unit-test a component controller is by using the
$componentController that is included in ngMock. The advantage of this
method is that you do not have to create any DOM elements. The
following example shows how to do this for the heroDetail component
from above.
And it dawned on me, my describe('Controller',function(){...}); was what I really needed to change, and that I should just get rid of the 'Component' portion, formally known as 'Directive'
Here's my 'Controller' now:
beforeEach(inject([
'$componentController', // replaced $controller with $componentController
function($ctrl){
ctrl = $ctrl('queryResults',{ // Use component name, instead of controller name
SomeFactory:MockSomeFactory,
SomeService:MockSomeService
});
}
]));
Now, I still get to test my controller, while simultaneously testing the component. And I no longer have to create DOM elements using $compile, $rootScope, etc.
I am unit testing some Angular directives in Jasmine and running the tests with Karma in the PhantomJS headless browser.
I have a couple hundred specs. Since I've started testing the directives, I noticed that PhantomJS takes up a huge chunk of memory after running through the whole test suite a couple of times. I have a hunch that this is because the directives (and perhaps other garbage) isn't being freed up after they have been tested.
I do have some afterEach statements that remove the "compiled" directives after use, but it seems this is not enough, or I am not doing it correctly.
example.spec.js
describe('leftNavigation', function() {
var nlElement = angular.element('<div left-navigation></div>');
beforeEach(module('app'));
beforeEach(module('path/to/leftNavigation.html'));
beforeEach(inject(function($templateCache, _$compile_, _$rootScope_) {
template = $templateCache.get('full/path/to/leftNavigation.html');
$templateCache.put('full/path/to/leftNavigation.html', template);
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should contain the text BLAH', function() {
var element = $compile(nlElement)($rootScope);
$rootScope.$digest();
expect(element.text()).toContain('BLAH');
});
// Teardown
afterEach(function() {
(nlElement).remove();
});
});
Am I tearing down the right variable (nlElement), or do I need to teardown element instead? Do I need afterEach statements for every beforeEach statement?
EDIT: The directive is defined as so:
'use strict';
// Grab ref to app module
var app = angular.module('app');
app.directive('leftNavigation', ['$log',
function ($log) {
return {
restrict: 'A',
replace: true,
templateUrl: 'path/to/template.html',
scope:{
selection: '#'
}
};
}
]); //end of directive
EDIT 2: The directive's HTML template does include AngularJS expressions and ng-class attributes on multiple elements.
The function that I'm providing to define my directive is never called. It used to work fine but suddenly stopped working and I have no idea why.
Here's my directive:
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
restrict: 'AE',
replace: 'true',
templateUrl: 'partials/account/contactPanel.html',
scope: {
contact: '=contact',
primaryRoleName: '#',
roleName: '#',
primary: '=primary',
locations: '=locations'
},
controller: function (userService, $rootScope, $scope) {
...snip...
}
};
});
and here an example of its use:
<contact-panel contact="user.account.contacts.billing" role-name="billing"
locations="locations"></contact-panel>
Note that I'm using the correct casing, i.e. camel-case in JS and hyphenation in HTML.
The key clue is that the message that's logged in the second line (i.e. 'NEVER SHOWN!') never shows up in the console. If I log a message immediately before the directive declaration then that shows up, so this code is being executed by the interpreter, but the framework is just never using my declaration.
I'd love to have an answer obviously, but I'd also love to hear of some approaches to debug this kind of problem.
I can see only 2 possibilities that would exhibit the behavior you described. Either the HTML with the directive was not compiled or the directive is not registered.
The "not compiled" case could be because the directive is used outside of the Angular app, for example:
<div ng-app="portalApp">
...
</div>
...
<contact-panel></contact-panel>
Or, if you added the HTML dynamically, but did not $compile and link it.
The "not registered" case could be due to re-registration of the app's module. In other words, you might have the following case:
var portalApp = angular.module("portalApp", []);
portalApp.directive("contactPanel", function(){...});
// then elsewhere you use a setter:
angular.module("portalApp", []).controller("FooCtrl", function(){});
// instead of the getter:
// var app = angular.module("portalApp");
The second call to angular.module("portalApp", []) removes the previous registration of .directive("contactPanel", ....
I figured out the cause of this problem. At some point I must've accidentally moved the directive into a configuration block like this:
portalApp.config(function ($stateProvider, $urlRouterProvider) {
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
...snip...
};
});
});
Once I moved it back out of the configuration block and into the global scope the directive immediately rendered as it should.
The reason why this doesn't work is that angular runs the configuration code after it's run the directives, like this:
runInvokeQueue(moduleFn._invokeQueue); // runs directives
runInvokeQueue(moduleFn._configBlocks); // runs config blocks
So things added to the _invokeQueue (which the directive() function does) from within a config block will never be executed.
Thank you to all those who tried to help.
I'm having trouble unit-testing directives that make use of templateUrl.
There's this awesome tutorial about AngularJS testing [1] and it even has a Github repo that goes with it [2]
So I forked it [3] and made the following changes:
On directives.js I created two new directives:
.directive('helloWorld', function() {
return {
restrict: 'E',
replace: true,
scope:{},
template: '<div>hello world!</div>',
controller: ['$scope', function ($scope) {}]
}
})
.directive('helloWorld2', function() {
return {
restrict: 'E',
replace: true,
scope:{},
templateUrl: 'mytemplate.html',
controller: ['$scope', function ($scope) {}]
}
})
and I changed test/unit/directives/directivesSpecs.js so that it loads a template into $templateCache and then added two more tests for the new directives:
//
// test/unit/directives/directivesSpec.js
//
describe("Unit: Testing Directives", function() {
var $compile, $rootScope, $templateCache;
beforeEach(angular.mock.module('App'));
beforeEach(inject(
['$compile','$rootScope', '$templateCache', function($c, $r, $tc) {
$compile = $c;
$rootScope = $r;
//Added $templateCache and mytemplate
$templateCache = $tc;
$templateCache.put('mytemplate.html', '<div>hello world 2!</div>');
}]
));
//This was already here
it("should display the welcome text properly", function() {
var element = $compile('<div data-app-welcome>User</div>')($rootScope);
expect(element.html()).to.match(/Welcome/i);
});
//Added this test - it passes
it("should render inline templates", function() {
var element = $compile('<hello-world></hello-world>')($rootScope);
expect(element.text()).equal("hello world!");
});
//Added this test - it fails
it("should render cached templates", function() {
var element = $compile('<hello-world2></hello-world2>')($rootScope);
expect(element.text()).equal("hello world 2!");
});
});
The last test fails because Angular won't compile the template like it should.
$ grunt test:unit
Running "karma:unit" (karma) task
INFO [karma]: Karma v0.10.10 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 35.0.1916 (Linux)]: Connected on socket ChISVr0ZABZ1fusdyv3m
Chrome 35.0.1916 (Linux) Unit: Testing Directives should render cached templates FAILED
expected '' to equal 'hello world 2!'
AssertionError: expected '' to equal 'hello world 2!'
Chrome 35.0.1916 (Linux): Executed 18 of 18 (1 FAILED) (0.441 secs / 0.116 secs)
Warning: Task "karma:unit" failed. Use --force to continue.
Aborted due to warnings.
I'm pretty sure this was supposed to work.
At least, it's very similar with the solution proposed by #SleepyMurth on [4].
But I feel I reached the limit of understanding what's going wrong with my current knowledge of AngularJS.
HELP! :-)
[1] http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html
[2] https://github.com/yearofmoo-articles/AngularJS-Testing-Article/
[3] https://github.com/tonylampada/AngularJS-Testing-Article
[4] Unit Testing AngularJS directive with templateUrl
The problem
When templateUrl is specified, templates are fetched using $http (even for cached templates, which $http serves from the $templateCache). For that reason, there needs to be a $digest cycle for the $http's promise to be resolved with the template content and processed by the directive.
The solution
Since promises get resolved during a $digest cycle (and since we are outside of an "Angular context"), we need to manually invoke $rootScope.$digest() before evaluating our assertions.
Thus, the last test should be modified like this:
it("should render cached templates", function() {
var element = $compile('<hello-world2></hello-world2>')($rootScope);
$rootScope.$digest(); // <-- manually force a $digest cycle
expect(element.text()).toBe(tmplText);
});
See, also, this short demo.
It is possible to run javascript in a directive before returning anything, as well as in a directive's compile step before returning anything:
angular.module('foo').directive('fooDirective', [function(){
console.debug('before return');
return {
restrict: 'E',
controller: function($scope){
console.debug('controller');
},
compile: function(scope, elem){
console.debug('compile');
return {
pre: function(scope,elem, attr){
console.debug('pre');
},
post: function(scope,elem,attr){
console.debug('post');
}
}
}
}
}]);
<body ng-app="foo">
<foo-directive></foo-directive>
<foo-directive></foo-directive>
</body>
This produces the following console log order:
before return
compile
compile
controller
pre
post
controller
pre
post
I have several questions about this:
1) Why would I ever want to run code before returning the actual directive object? What would be a usecase?
2) Why would I ever want to run code before returning the pre/post link functions? How is the prelink step different from the compile step? What is a use case?
3) Why does compile run twice in succession when there is two items, while everything else runs iteratively in the same order irrelevantly of number of elements?
Plunk: http://plnkr.co/edit/1JPYLcPlMerXlwr0GnND?p=preview
You would want to do this so you can have some private method definitions that only your object can use.
You would want to do this if you were utilizing some service precompile to get the data for the directive, and post compile to use the data and do something with it.
No idea
I think there is some weird bubbling going on.
because you only have two instances of your directive.
This is what I see in the console log:
before return
compile
compile
controller
pre
post
controller
pre
post