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.
Related
I am trying to test a directive which is defined in a sub-module of another submodule of the root module. I am unable to access the scope variables defined in the controller of the directive. The problem seems to lie in accessing the scope from inside the test.
The main module is named oppia and it has a submodule named stateEditorModule and this submodule also has a submodule named stateContentEditorModule. I have tried injecting $scope instead of $rootScope but it doesn't seem to work.
This is the test code:
beforeEach(function() {
module('oppia');
module('stateEditorModule');
module('stateContentEditorModule');
});
outerScope = $rootScope.$new();
var elem = angular.element(
'<state-content-editor>' +
'</state-content-editor>');
var compiledElem = $compile(elem)(outerScope);
outerScope.$digest();
ctrlScope = compiledElem[0].getControllerScope();
}));
fit('should start with the content editor not being open', function() {
expect(ctrlScope.contentEditorIsOpen).toBe(false);
});
The contentEditorIsOpen variable is defined in the directive's scope.
The contentEditorIsOpen is inaccessible with the current state of the code.
I am stuck on this for a quite long and I would be grateful if someone could provide a solution or point to the documentation on how to test a multi-modular AngularJS app.
Normally you wouldn't test the directive itself. Instead, you would test the controller of that directive.
This means you need to register the controller in DI. Then you can get the controller of that directive directly through DI (without creating and compiling the direcitve), provide an initial scope to that controller, call a function, and assert some vars on that scope to be a specific value.
Example from the doc:
https://docs.angularjs.org/guide/unit-testing#testing-a-controller
If you are using components instead of directives, check out this:
https://docs.angularjs.org/guide/component#unit-testing-component-controllers
UPDATE:
I used the controllerAs property of the directive to give a name to the controller as stateContentEditorController
controllerAs only creates an alias for that controller in the scope of the template, but it won't register it in the DI (dependency injection) module, so you can't retrieve it from the DI system.
What I suggested was to write your directive and your controller separately and register your controller using angular.module('moduleName').controller('controllerName', [...]). Then in your directive definition object just write {controller: 'controllerName', ...}. This way you can retrieve the controller in your tests.
I have an angular directive, which can take a parent controller function as attribute. During DOM manipulation, this angular directive is cloned, compiled and placed into the beginning part of the DOM. As a result, the passed in parent controller function does not work anymore, since there is no parent controller anymore. How could I solve this? Could I pass the parent controller function from the original to the clone when I'm compiling it? I have tried this, but it is not working:
$timeout(function () {
$scope.test ="passed content";
var compiledClonedDirective = $compile(clonedDirective)($scope);
divInTheBeginningPartOfTheDOM.prepend(clonedDirective);});
function getBack() {
console.log($scope.test);
}
This getBack() function is used in the cloned directive. When it is activated, it logs undefined. Any hints?
One solution was to copy the parent controller function to rootScope, and then call it from the rootScope in the cloned and replaced directive (directive's controller). But I would like to avoid using rootScope if possible.
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.
everywhere in application my company created we used this example of how to create a controller :
app.myFunnyController = function($scope.....){}
but i see that everywhere in test people are using this way of creating controllers:
app.controller('myFunnyController', function ($scope) {
}
And i can see that when i am creating my test and using app.myFunnyController declaration:
'use strict';
describe('publicCtrl', function(){
beforeEach(module('app'));
it("should be true", inject(function($controller){
var scope = {},
ctrl = $controller('myFunnyController', {$scope : scope});
expect(scope.data).toBe("test2");
}));
})
I getting an error of myFunnyController is not a function. If i using the second type of declaration, everything works fine. Why does this happend?
An other problem is that i am getting error: scope is not defined.
I am new to Karma and Unit testing for front end, what am i doing wrong?
From my understanding, the second syntax (app.controller(...)) registers the controller function on the module. The first syntax just adds the controller function as an attribute of the module. app.controller does a bit more magic under the hood so that when you call $controller('myFunnyController, ...) in your test, the module knows about the controller and can run it. This is the recommended way to define controllers according to the angular controllers guide.
I have to access variable defined in directive and access it in the controller using angularjs
directive :
app.directive('htmlData', function ($compile) {
return {
link: function($scope, element, attrs) {
$(element).on('click', function() {
$scope.html = $compile(element)($scope).html();
});
return $scope.html;
}
};
});
and use $scope.html in controller.
Since you are not creating an isolate scope (or a new scope) in your directive, the directive and the controller associated with the HTML where the directive is used are both using/sharing the same scope. $scope in the linking function and the $scope injected into the controller are the same. If you add a property to the scope in your linking function, the controller will be able to see it, and vice versa.
As you set the variable in the $scope, all you got to do is to bind to it normally. In your case, osmehting like:
<div html-data>{{html}}</div>
Maybe you're not seeing the update because it lacks a $scope.$apply().
Anyway, let me say that I see two problems on you code. First of, you could use ng-click directive to attach click events to your HTML.
Secondly, why would you recompile your HTML every time? There is almost no need for that. You may have a big problem, because after first compilation, your template is going to be lost, so recompiling will render it useless.
If you need to get the element, you can inject $element.