In this example of a directive unit test from the Angular docs:
describe('Unit testing great quotes', function() {
var $compile;
var $rootScope;
// Load the myApp module, which contains the directive
beforeEach(module('myApp'));
// Store references to $rootScope and $compile
// so they are available to all tests in this describe block
beforeEach(inject(function(_$compile_, _$rootScope_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('Replaces the element with the appropriate content', function() {
// Compile a piece of HTML containing the directive
var element = $compile("<a-great-eye></a-great-eye>")($rootScope);
// fire all the watches, so the scope expression {{1 + 1}} will be evaluated
$rootScope.$digest();
// Check that the compiled element contains the templated content
expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
});
});
Can someone explain what ($rootScope) is doing in the element variable declaration in the it function.
I not sure what effect it has.
It is used to force a $digest cycle so that the element above it is compiled and rendered immediatly instead of waiting for an undetermined $digest cycle
The $compile function creates a function that grabs values from a scope variable to complete bindings.
When you call the function created by $compile with a scope variable, it replaces all bindings with a value on the scope you gave as an argument, and create a DOM element.
For example :
$rootScope.age = 15;
$scope.age = 52;
var element = $compile("<div>Tom is {{age}}</div>")($rootScope);
/* element is a div with text "Tom is 15" */
var element2 = $compile("<div>Tom is {{age}}</div>")($scope);
/* element2 is a div with text "Tom is 52" */
Compilation in Angular is done in two steps. $compile(template) does just the first half, where directives usually just transform the DOM (and other, more complicated stuff happens as well ;)), and returns a "linking function". The second part is done when the linking function is called with a particular scope as argument. In this part, directives can edit the scope, link behavior to DOM events, etc. More can be found in the official guide.
Related
This is a continuation of another question I asked that was successfully answered.
I'm learning how to unit test AngularJS applications with Karma, Jasmine, and ngMock. Here's the code I have a question about:
describe('myController function', function() {
describe('myController', function() {
var scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
// These are the 2 lines I'm a bit confused about:
scope = $rootScope.$new();
$controller('MyController', {$scope: scope});
}));
it("...");
});
});
Question 1: Why do we create a new scope and include it in the locals injection area in this line: $controller('MyController', {$scope: scope});? It seems to work just fine (as in, scope now represents the $scope object from that controller and has all the properties and functions it's supposed to), but the code seems to imply that we're resetting the controller's $scope with our newly-created (and empty) scope (from the line scope = $rootScope.$new();). So I think I'm just not fully understanding the purpose/inner-workings of those locals.
Question 2: I also have seen from my searching that new scope gets created in 2 common ways, either the way shown above with $rootScope.$new(), or by simply declaring scope = {}. Even the Angular docs do it both ways, as seen here (using $rootScope.$new()) and here (using $scope = {}. Why do this one way over another? Is there a difference?
Why do we create a new scope
There are a couple of reasons that come to mind, but the short answer is that you don't have to if you don't want to. You can just as easily pass in $rootScope and your tests will still work. It is also just typically done because it is more in line with what actually happens in angular - the $scope that the controller is injected with is likely a descendent of $rootScope.
What is $controller('MyController', {$scope: scope}); doing?
The ngMock module provides the $controller function as a sort of constructor for creating your controller - well, technically it's a decorator to the $controller function in the ng module, but that's not important. The first argument is typically a string, but can be a function.
If called with a function then it's considered to be the controller constructor function. Otherwise it's considered to be a string which is used to retrieve the controller constructor...
The second argument are the "locals" to be injected into the controller during creation.
So, in your example, you are saying you want to create the "MyController" controller, and you want to inject your scope variable as the argument named $scope in the controllers function.
The whole point of this is to inject your own version of the scope into the controller, but a version that you created in your test, so that you can assert the different things that happen to the scope because of the controller.
This is one of the benefits of dependency injection.
Examples
If the following makes sense:
var scope = {};
scope.add = function(){};
expect(typeof scope.add).toBe('function');
then let's take it one step further and move the adding of the function into another function:
var addFunctionToScope = function(scope) {
scope.add = function(){};
};
var scope = {};
addFunctionToScope(scope);
expect(typeof scope.add).toBe('function');
Now just pretend your new function to add the function to the scope is really just called $controller, instead of "addFunctionToScope".
var scope = {};
$controller('SomeController', {$scope: scope});
expect(typeof scope.add).toBe('function');
Why use $rootScope.$new() over {}
Again, you can use either. However, if you plan on using some of the scope specific functions, like $on, or $new, or $watch, etc - you will want to inject an actual scope object, created using the $new function on an existing scope.
Docs
scope
ngMock $controller
ng $controller
I try do something with directive in angular, but I've some problem with $compile function in programmatically html element, call here "phe".
var phe = angular.element('<div style="background-color:orange">{{value}}</div>');
When I append "phe" after or before the directive's element, it work like a charm...
var $phe = $compile(phe)(scope);
element.after($phe);
but if I wrapped the directive element with this "phe" the $compile not work.
element.wrap($phe);
Somebody have some idea?
I have made a plunker http://plnkr.co/edit/0x2MmQ7WYmiNog0IEzTj?p=preview
it works if you change the compilation sequence... compile the element before placing it in the dom
var phe_b = angular.element('<div style="background-color:orange"> b {{value}}</div>');
var $phe_b = $compile(phe_b)(scope);
element.before($phe_b);
do same for after...
The reason it doesn't work with wrap is because wrap clones the DOM element. In other words, if you did:
var wrapper = angular.element("<div>");
element.wrap(wrapper);
console.log(wrapper[0] !== element[0].parentNode); // this would output "true"
So, the element that you compiled/linked is not the same that ends up in the DOM.
You could, of course, get the wrapping element (it's the return value of wrap) and $compile it, but you need to be careful not to re-compile/re-link certain directives that were applied on the current element (including the very same directive) and its children.
Is it possilbe to introspect/reflect of the model in Angular app, where you can change the scope and traverse it? Something like batarang have but that will allow to change the values.
If not is it possilbe to monkey patch Angular code (by including another script on the page) that will make it possilbe?
1. Accessing/Modyfing scope properties
Angular's $scope objects are plain old JS objects, so thay can be manipulated the standard way.
E.g. someScope.someProp retrieves the value of a property, while someScope.someProp = someValue sets the value of a property.
2. Letting Angular know
Modifying the object's properties is one thing - making Angular aware of the change is another.
Angular will not know about our modifications, until it runs a $digest cycle. If we want to apply the changes right away, we can explicitely trigger a $digest cycle, by using someScope.$apply().
3. Getting hold of a scope
In order to get a reference to the scope associated with a DOM element, we need to have a reference to the corresponding DOM Node object, wrap it in angular.element and execute its scope() method.
Something like this:
<body class="ng-scope">
var someScope = angular.element(document.body).scope();
Furthermore, if we want to access the $rootScope (the parent scope of all scopes), we can use the injector to inject the $rootScope service:
<html ng-app="myApp">
var injector = angular.element(document.documentElement).injector();
var rootScope = injector.get('$rootScope');
4. Traversing the scope-tree
Once we get hold of a scope object, we might want to traverse the scope-tree. Every scope has the following properties (among others):
$parent: The parent scope of this scope (if any).
$$nextSibling: The next sibling scope of this scope (if any).
(Sibling scopes are scopes that have the same $parent)
$$childHead: The first child scope of this scope (if any).
In order to traverse the branch of the scope-tree with someScope as its root:
var scopes = [someScope];
while (scopes.length) {
var scope = scopes.shift();
console.log(scope);
if (scope.$$nextSibling) { scopes.unshift(scope.$$nextSibling); }
if (scope.$$childHead) { scopes.unshift(scope.$$childHead); }
}
E.g. to traverse the whole scope-tree, you can use the following snippet:
var injector = angular.element(document.body).injector();
var rootScope = injector.get('$rootScope');
var scopes = [rootScope];
while (scopes.length) {
var scope = scopes.shift();
report(scope);
if (scope.$$nextSibling) { scopes.unshift(scope.$$nextSibling); }
if (scope.$$childHead) { scopes.unshift(scope.$$childHead); }
}
function report(scope) {
var str = '' + scope.$id;
while (scope.$parent) {
str = scope.$parent.$id + ' => ' + str;
scope = scope.$parent;
}
console.log(str);
}
Found the way, here is a code that change the scope outside of Angular:
var $scope = angular.element($('.section:eq(0)')).scope();
$scope.$apply(function() {
scope.color = "blue";
});
the angular.element().scope() will return scope and can be investigated. Not sure about how to traverse scope tree, but this is good starting point. If there is no scope traverse you could just traverse the DOM and check if there is new scope on that DOM element.
You can get the scope by calling the scope() method on an object returned by angular.element() (or $(), if you also use jQuery), e.g. angular.element(".foo").scope(). There's no direct way of accessing child scopes. But knowing that all elements that have a new scope associated with them have the ng-scope class, you can traverse the element tree instead.
$(".foo").find(".ng-scope").each(function() {
var scope = $(this).scope();
});
This, however can be a bit tricky, as find() will find all nodes below .foo, regardless of their depth. You could use children() instead but it may not yield any results as direct descendants of .foo may not create a new scope.
I have this directive. it gets an array and creates a stacked bar. while the directive works fine, the unittesting failes miserably. I tried:
describe('Stacked bar directive', function(){
var scope, elem;
beforeEach(inject(function(_$compile_, _$rootScope_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
}))
beforeEach(function(){
scope = $rootScope.$new();
scope.distribution = [[0.4,'diamonds'],
[0.05,'gold'],
[0.15,'silver'],
[0.4, 'bronze']];
elem = angular.element('<bars-chart chart-data="distribution" chart-width="200"></bars-chart>');
$compile(elem)(scope);
scope.$digest();
});
it('Should display the correct number of bars', function(){
dump(elem)
expect(elem.find(".bars").length).to.equal(4)
//and no other find works either..
});
And I get:
expected 0 to equal 4
Since the compiled directive has a .bars class all over the place, this doesn't make sense.
I noticed that dumping the elem doesn't show me the rendered Directive but:
{0: <bars-chart chart-data="distribution" chart-width="200" class="ng-scope"></bars-chart>, length: 1}
so maybe something with compiling the directive doesn't work for me.
I'm using jquery-chai to make testing dom/css a little easier but It doesn't suppose to have any effect on this problem (at least as I can tell, I disabled it and got the same effect).
Any ideas? I'll be glad to help with this
Is this your entire test? I don't see any calls to load the module containing your directive. This means your directive is not being registered at all and when you compile it will not be called.
Typically you should do something like:
beforeEach(module('myApp'))
This will ensure that the 'myApp' module is loaded and your directives in that module are available when you $compile.
I found this small note in angularjs doc :
find() - Limited to lookups by tag name
Maybe the cause of your issue...
I have a controller defined in a directive and having trouble unit testing it. Is this possible without globalizing or separating the controller from the directive?? Can you add a simple example??
In your case you could test the elements controller by accessing controllers functions from the compiled elements scope.
The easiest way to access the scope of the element is by calling the #scope() function on the compiled angular element.
it ('should have a function X on scope', inject(function($rootScope, $compile) {
var element = $compile('<div test-directive></div>')($rootScope);
expect(element.scope().myFunction).toEqual(jasmine.any(Function));
});
Heres a simple example of the following technique used in a jsFiddle.