angular testing directive stub child directive - angularjs

I want to test a directive whose markup contains a child directive.
e.g.
<div class="directiveUnderTest">
<child-directive></child-directive>
</div>
I would like to stub out the child directive in my unit test so it won't try to 'compile' it and instead just ignore this directive.
I could do
inject(function($templateCache) {
$templateCache.put('/path/to/child/directive', '');
});
But this now depends on the implementation of the child directive. I would like to stub out the entire child directive.
I don't want to override the template of the directive under test as I need to compile the real template in order for it to create the expected scope.
Any suggestions?
Thanks in advance.

Because a directive is just a factory, you can mock them out in tests with $provide. Then when you build the "fake" directive, you can provide it with a templateUrl that will still load what you need. An example below shows a way to make sure the fake directive stays in an isolated scope and provides what you need.
beforeEach(function () {
module('moduleName', function ($provide) {
$provide.factory('nameOfDirective', function () {
return { templateUrl: 'path/to/template' };
}
});
});
Something to be aware of is you actually need to suffix Directive onto the name of your directive, the normal compiler will do this automatically when declaring .directive('name', ...) to make angular aware of what type of factory it is.

Related

ANGULAR JS dynamic data binding to template

Hello All Angular Friends
I am trying to find a way for dynamic data binding to the template.
Created a test page: http://jsbin.com/jiminey/edit?html,js,output.
Currently I have my HTML
<banner compSrc="banner1"></banner>
<banner compSrc="banner2"></banner>
And the Data
$scope.bannerData ={
"banner1": {
"heading": "Hero Test"
},
"banner2": {
"heading": "Page Title (h1)"
}
};
Template
template: '<div>BannerHeading - {{bannerData.banner2.heading}}</div>'
How can I make this template dynamic based on the compSrc attribute?
I am looking for something like below So I dont have to update the template.
template: '<div>BannerHeading - {{heading}}</div>'
Thank you.
You can use isolated scope for directives. Take in account the name normalization.
Here is fixed JSBin
Create a directive for your template, and use function as value for your compile property of DDO
Plz refer this question on SO: What are the benefits of a directive template function in Angularjs?
app.directive('myDirective', function(){
return {
// Compile function acts on template DOM
// This happens before it is bound to the scope, so that is why no scope
// is injected
compile: function(tElem, tAttrs){
// This will change the markup before it is passed to the link function
// and the "another-directive" directive will also be processed by Angular
tElem.append('<div another-directive></div>');
// Link function acts on instance, not on template and is passed the scope
// to generate a dynamic view
return function(scope, iElem, iAttrs){
// When trying to add the same markup here, Angular will no longer
// process the "another-directive" directive since the compilation is
// already done and we're merely linking with the scope here
iElem.append('<div another-directive></div>');
}
}
}
});

css testing in angularjs

I am using controller to change the class of an object in angularjs
$scope.$watch('sideQuery',function(){
if($scope.sideQuery==""){
$(".indicator").removeClass('glyphicon-minus');
$(".indicator").addClass('glyphicon-plus');
}
else{
$(".indicator").removeClass('glyphicon-plus');
$(".indicator").addClass('glyphicon-minus');
}
});
How to test using karma? a function like
expect(scope.elem('.indicator').hasClass("glyphicon-plus")).toBe(true);
Please, do not use jQuery to toggle classes in Angular, it defeats the purpose of it. Use ng-class and apply your classes based on flags, like so:
<div class="indicator" ng-class="{'glyphicon-minus' : sideQuery != '', 'glyphicon-plus':sideQuery == ''} ></div>
Then in testing check the value of sideQuery and know that you'll have classes based off that.
If you are testing a directive, then you can compile a sample element and test the result object.
var $element;
beforeEach(inject(function ($compile) {
$element = $compile('<div data-my-directive></div>')($scope);
}));
it('should have the class "someClass"', function(){
expect($element.hasClass('someClass')).toBe(true);
});
However, be aware that tymeJV is right, you should use the ng-class directive and test your scope's values. If the scope value is right, then the class will be applied (you don't have to test the ng-class directive, that's something done in Angular's unit tests).

How do I assign an attribute to ng-controller in a directive's template in AngularJS?

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.
An example showing the relevant snippets is below.
HTML:
<div ng-controller="appController">
<custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>
JS:
function appController($scope) {
// Main application controller
}
function templateController($scope) {
// Controller (separate from main controller) for exclusive use with template
}
app.directive('customDirective', function() {
return {
restrict: 'A',
scope: {
ctrl: '#',
tmpl: '#'
},
// This will work, but not what I want
// Assigning controller explicitly
template: '<div ng-controller="templateController">\
<div ng-include="tmpl"></div>\
</div>'
// This is what I want, but won't work
// Assigning controller via isolate scope variable from attribute
/*template: '<div ng-controller="ctrl">\
<div ng-include="tmpl"></div>\
</div>'*/
};
});
It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.
I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:
Plunker
Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.
The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.
The following ideas may be relevant:
Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
Using one-way binding ('&') to execute functions instead of text binding ('#').
Using a link function instead of / in addition to an isolate scope.
Using an element/class directive instead of attribute directive.
The priority level of ng-controller is lower than that of ng-include.
The order in which the directives are compiled / instantiated may not be correct.
While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.
I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.
Plunker
Relevant code:
link: function( scope, element, attrs )
{
$http.get( scope.tmpl, { cache: $templateCache } )
.then( function( response ) {
templateScope = scope.$new();
templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
element.html( response.data );
element.children().data('$ngControllerController', templateCtrl);
$compile( element.contents() )( templateScope );
});
}
Inspired strongly by this similar answer.

How can I call a method on a custom directive's isolated scope from within a transcluded controller in Angular.js

I created a directive called dt-modal under the dt module. In my main app's module called demo, I use this dt-modal which has an isolated scope. I created this directive such that the HTML form written within the directive is transcluded since I want to reuse this modal for many different forms.
<dt-modal>
<form ng-controller="ReviewFormController"
name="reviewForm"
novalidate
ng-submit="reviewForm.$valid && submitReview(review)">
<!-- form contents here -->
</form>
</dt-modal>
This transcluded form has a custom controller called ReviewFormController that listens for the submit event. How can I call the close() method on the dt-modal's scope from within submitReview() in ReviewFormController?
Here is a JSBin. If you hit ESC, you can see close() in the directive run.
http://jsbin.com/cukanole/1/edit
If this isn't possible, is there a better design for this directive?
Thanks in advance!
Since you are using an isolated scope, you could pass a control object to the directive...
<dt-modal id="review-form-modal" api="modal.api">
and add the close method to it via two-way binding:
scope: {
api: '='
},
link: function($scope, $el, attrs) {
$scope.api = {
close: function() {
$el.css({
display: 'none'
})
}
}
...
Then ng-click can use the control object to call close:
<button type="submit" ng-click="modal.api.close()">Submit</button>
If you want to try this code, here it is on Plunker.
My recommendation is to use $emit to trigger the event from the controller and use $on on the directly.
Controller
scope.$emit("ValueChanged", value);
In the directive the event will be captured using $on like:
$scope.$on("ValueChanged", function(event, ars){
... //your event has been triggered.
});
Important:
Directives should be always independent components, if inside the directive there is a call to a method from a controller(outside the directive) this will create a dependency between my directive and the controller and of course this will force one not being able to exist without the other.
If I would have to apply a design principle to a directive it will be the S in SOLID, Single responsibility principle. Directives should be able to encapsulate and work independently.

Angular communication between controllers and directives

I have this piece of code which allows a user to leave comments on a list of items.
I created a directive and listen to keydown in order to let the user submit a comment if keyCode == 13.
Not sure if I should include the code to post a comment within the directive. What is the best way to communicate between controllers and directives?
I also check whether or not the input is empty before submitting the comment. It works but not sure this is Angular best practice?
Here is my plunker.
you don't need to write a directive, if you want to use ng-keydown..
example:
template:
<input type="text" ng-model="myText" ng-keydown="checkKeyCode($event)">
controller: -- written in coffeescript
$scope.checkKeyCode = ($event)->
if $event.keyCode == 13 and $scope.myText?
$scope.doSomething()
You generally don't want your directives knowing anything about your controller, so the best(Angular) way of communicating between controllers and directives is through bi-directional bindings.
In your situation, I think best practice, again IMO, would be to create a directive for the button -- not the input. You'd tell the button which "input" (by id) to monitor. Something like:
<input id="input-{{item.id}}" type="text" ng-model="currMessage" />
<button class="btnMessage" ng-click="addMessage(currMessage, item)" default-input="input-{{item.id}}">Add</button>
ETA: Here's what the directive would end up looking like
http://plnkr.co/edit/HhEAUUq0IZvzblbRksBH?p=preview
myApp.directive('defaultInput', function () {
return {
restrict:'A',
link: function(scope, element, attrs) {
attrs.$observe('defaultInput', function(value) {
var inputElement = angular.element(document).find('#' + value);
inputElement.bind('keydown', function(e) {
if (e.keyCode == 13) {
element.click();
}
});
});
}
};
});
It could get tricky because the $observe callback will fire every time your controller's scope.items changes, so you'd need to somehow unbind and rebind (I know you're using jQuery, but I'm not seeing angular.unbind in the docs).
Another option, if you wanted to stick closer to your original approach:
http://plnkr.co/edit/3X3usJJpaCccRTtJeYPF?p=preview
HTML
<input id="input-{{item.id}}" type="text" ng-model="currMessage" enter-fires-next-button />
JavaScript
myApp.directive('enterFiresNextButton', function() {
return function(scope, element, attrs){
element.on('keydown', function(e){
if(e.keyCode == 13) {
element.next('button').click();
}
});
}
});
What is the best way to communicate between controllers and directives?
It depends... I like to first determine which type of scope is appropriate for a directive: no new scope, new scope, or new isolate scope. See When writing a directive in AngularJS, how do I decide if I need no new scope, a new child scope, or a new isolated scope?
Once that has been decided, the next decision is to determine if the communication should really be going to a service. If so, the controller and directive would both inject the service and interact with it, rather than each other.
If a service is not required, attributes are used to facilitate the communication between the controller and the directive. How that is done is determined by the type of scope the directive creates. Tip: if an isolate scope is not used, use $parse to get and set properties inside the directive, or to call methods on the controller from inside the directive -- see
How to set angular controller object property value from directive in child scope
https://stackoverflow.com/a/12932075/215945 - an example of calling a controller function with arguments

Resources