I'm having a real hard time running a karma unit test over an angularjs directive. Here's my setup:
var scope, ele;
var template = '<div data-my-directive data="data" config="config"></div>';
beforeEach(function () {
// Load directive's module
module('dashboard');
module('templates-dev');
// Load mock services/data (overriding real implementation)
module(function($provide) {
$provide.value("MockWidgetData", new MockWidgetData());
});
// Construct services/data to be available
// inside each testing block.
inject(function($rootScope, $compile, MockWidgetData) {
// Create a fresh scope
scope = $rootScope.$new();
ele = angular.element(template);
// Fill that scope with mock data
scope.config = MockWidgetData.config;
scope.data = MockWidgetData.data;
// Compile the element and attach our scope
$compile(ele)(scope);
// Digest the scope to trigger a scope update
// and attach our directive's link function
scope.$digest();
});
});
My directive passes 'data' and 'config' objects to its children, which happen to be other directives. The directive itself uses a templateUrl, which is why I provide the module(templates-dev): an html2js pre-compiled, angular-modularized, $templateCache'd version of my templates. Here's a sample of my directive's templateUrl:
<div id="container">
<button id="my-btn" ng-click="doSomething('parameter')"></button>
<div other-dir data="data" config="config"></div>
</div>
Here's a sample of my directive:
angular.module('dashboard').directive('myDirective', function() {
return {
restrict: 'A',
templateUrl: 'my-dir-template.html',
scope: { //
data: '=',
config: '='
},
replace: true,
link: function(scope, element, attrs) {
scope.someFunc() = { ... },
scope.someFunc1() = { ... },
});
Problem:
After $compile(ele)(scope), a .log() of the element shows the entire
DOM, including all sorts of angular.js directives and other strange
information. I would expect to see my directive's test template
mashed together with the above templateUrl.
A .log() of the scope shows the complete data and config objects, with their expected values. It also shows the functions available inside of my directive's link function, as one would expect. However, inside my test blocks, it's as if the scope object is emptied... I cannot access any of my directive's functions (they all return undefined).
I am able to capture some elements of my directive's html via ele.find("#my-btn").eq(0), but none of the normal functions work (.click(), etc.).
What am I doing wrong? Is there something wrong with the compile process? Am I structuring the test wrong? Should I try to remove the link function and replace it with a Controller? My scopes just aren't lining up, and I've been failing at this for two days now... kind of annoying.
After closer inspection... It looks like I can scope.$digest() before $compile(ele)(scope), which will correctly propagate "data" and "config" objects to the child scope. However, my scope then loses access to the functions defined in my directive's link.
You should call scope.$digest() after $compile(ele)(scope). Digest is necessary to proceed compilation of directive. Your directive has an isolated scope, that's why you can't access it functions. To get them you should try this:
var elScope = element.isolateScope();
here you'l got your elScope.someFunc();
Check this example of unit testing directives:
http://best-web-creation.com/articles/view/id/angular-js-unit-test-directive?lang=en
Related
On Github, many Handlebars helpers are presented. You can find them here.
I'd like to use them, but have no Idea, how to include them. The code is looking as it's javascript (files are ending with .js ...), but words like 'import','export','default' are confusing me.
As I understand (guess), I have to include the if-condition.js at first. Later, the other (included) files refer to this file.
But when I do this, the console throws an Error:
Uncaught SyntaxError: Unexpected reserved word .
Has anyone an idea, how to get these codes working?
import and export are keywords for the upcoming module syntax in the next version of Javascript. You can use them today by using a transpiler to convert it to normal ES5 syntax.
However, if you're only using a few helpers, it's very easy to 'transpile' them by hand. Instead of exporting the function, just pass it to a Ember.Hanldebars.registerBoundHelper call. Here's the if-condition helper:
Ember.Handlebars.registerBoundHelper('if-condition', function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
if (!options.conditional) {
throw new Error("A conditional callback must be specified when using the if-condition helper");
}
// Gather all bound property names to pass in order to observe them
var properties = options.types.reduce(function(results, type, index) {
if (type === 'ID') {
results.push(args[index]);
}
return results;
}, []);
// Resolve actual values for all params to pass to the conditional callback
var normalizer = function() {
return Ember.Handlebars.resolveParams(context, args, options);
};
// This effectively makes the helper a bound helper
// NOTE: 'content' path is used so that multiple properties can be bound to using the `childProperties` argument,
// however this means that it can only be used with a controller that proxies values to the 'content' property
return Ember.Handlebars.bind.call(context, 'content', options, true, options.conditional, normalizer, properties);
});
So let's say I'm storing <div>{{name}}</div> and <div>{{age}}</div> in my database.
Then I want to take the first HTML string and render it in a template - {{> template1}} which just renders the first string with the {{name}} handlebar in it. Then I want to give that newly generated template/html data, so that it can fill in the handlebar with the actual name from the database, so that we would get <div>John</div>. I've tried doing
<template name="firstTemplate">
{{#with dataGetter}}
{{> template1}}
{{/with}}
</template>
Where template1 is defined as
<template name="template1">
{{{templateInfo}}}
</template>
And templateInfo is the helper that returns the aforementioned html string with the handlebar in it from the database.
dataGetter is just this (just an example, I'm working with differently named collections)
Template.firstTemplate.dataGetter = function() {
return Users.findOne({_id: Session.get("userID")});
}
I can't get the {{name}} to populate. I've tried it a couple of different ways, but it seems like Meteor doesn't understand that the handlebars in the string need to be evaluated with the data. I'm on 0.7.0 so no Blaze, I can't upgrade at the moment due to the other packages I'm using, they just don't have 0.8+ version support as of yet. Any ideas on how I can get this to work are much appreciated.
In 1.0 none of the methods described above work. I got this to work with the function below defined in the client code. The key was to pass the options { isTemplate: true} to the compile function.
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true });
var renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err){
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
The you can call with something like this on the client:
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
This will render with a UI dynamic in your html
{{> Template.dynamic template='faceplate'}}
You can actually compile strings to templates yourself using the spacebars compiler.. You just have to use meteor add spacebars-compiler to add it to your project.
In projects using 0.8.x
var compiled = Spacebars.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var rendered = eval(compiled);
Template["dynamicTemplate"] = UI.Component.extend({
kind: "dynamicTemplate",
render: rendered
});
In projects using 0.9.x
var compiled = SpacebarsCompiler.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var renderer = eval(compiled);
Template["dynamicTemplate"] = Template.__create__("Template.dynamicTemplate", rendered);
Following #user3354036's answer :
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true }),
renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err) {
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
1) Add this in your HTML
{{> Template.dynamic template=template}}
2) Call the compileTemplate method.
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
Session.set('templateName','faceplate');
Save the template name in a Session variable. The importance of this is explained in the next point.
3) Write a helper function to return the template name. I have used Session variable to do so. This is important if you are adding the dynamic content on a click event or if the parent template has already been rendered. Otherwise you will never see the dynamic template getting rendered.
'template' : function() {
return Session.get('templateName');
}
4) Write this is the rendered method of the parent template. This is to reset the Session variable.
Session.set('templateName','');
This worked for me. Hope it helps someone.
If you need to dynamically compile complex templates, I would suggest Kelly's answer.
Otherwise, you have two options:
Create every template variation, then dynamically choose the right template:
eg, create your templates
<template name="displayName">{{name}}</template>
<template name="displayAge">{{age}}</template>
And then include them dynamically with
{{> Template.dynamic template=templateName}}
Where templateName is a helper that returns "age" or "name"
If your templates are simple, just perform the substitution yourself. You can use Spacebars.SafeString to return HTML.
function simpleTemplate(template, values){
return template.replace(/{{\w+}}/g, function(sub) {
var p = sub.substr(2,sub.length-4);
if(values[p] != null) { return _.escape(values[p]); }
else { return ""; }
})
}
Template.template1.helpers({
templateInfo: function(){
// In this context this/self refers to the "user" data
var templateText = getTemplateString();
return Spacebars.SafeString(
simpleTemplate(templateText, this)
);
}
Luckily, the solution to this entire problem and any other problems like it has been provided to the Meteor API in the form of the Blaze package, which is the core Meteor package that makes reactive templates possible. If you take a look at the linked documentation, the Blaze package provides a long list of functions that allow for a wide range of solutions for programmatically creating, rendering, and removing both reactive and non-reactive content.
In order to solve the above described problem, you would need to do the following things:
First, anticipate the different HTML chunks that would need to be dynamically rendered for the application. In this case, these chunks would be <div>{{name}}</div> and <div>{{age}}</div>, but they could really be anything that is valid HTML (although it is not yet part of the public API, in the future developers will have more options for defining this content in a more dynamic way, as mentioned here in the documentation). You would put these into small template definitions like so:
<template name="nameDiv">
<div>{{name}}</div>
</template>
and
<template name="ageDiv">
<div>{{age}}</div>
</template>
Second, the definition for the firstTemplate template would need to be altered to contain an HTML node that can be referenced programmatically, like so:
<template name="firstTemplate">
<div></div>
</template>
You would then need to have logic defined for your firstTemplate template that takes advantage of some of the functions provided by the Blaze package, namely Blaze.With, Blaze.render, and Blaze.remove (although you could alter the following logic and take advantage of the Blaze.renderWithData function instead; it is all based on your personal preference for how you want to define your logic - I only provide one possible solution below for the sake of explanation).
Template.firstTemplate.onRendered(function() {
var dataContext = Template.currentData();
var unrenderedView = Blaze.With(dataContext, function() {
// Define some logic to determine if name/age template should be rendered
// Return either Template.nameDiv or Template.ageDiv
});
var currentTemplate = Template.instance();
var renderedView = Blaze.render(unrenderedView, currentTemplate.firstNode);
currentTemplate.renderedView = renderedView;
});
Template.firstTemplate.onDestroyed(function() {
var renderedView = Template.instance().renderedView;
Blaze.remove(renderedView);
});
So what we are doing here in the onRendered function for your firstTemplate template is dynamically determining which of the pieces of data that we want to render onto the page (either name or age in your case) and using the Blaze.With() function to create an unrendered view of that template using the data context of the firstTemplate template. Then, we select the firstTemplate template element node that we want the dynamically generated content to be contained in and pass both objects into the Meteor.render() function, which renders the unrendered view onto the page with the specified element node as the parent node of the rendered content.
If you read the details for the Blaze.render() function, you will see that this rendered content will remain reactive until the rendered view is removed using the Blaze.remove() function, or the specified parent node is removed from the DOM. In my example above, I am taking the reference to the rendered view that I received from the call to Blaze.render() and saving it directly on the template object. I do this so that when the template itself is destroyed, I can manually remove the rendered view in the onDestroyed() callback function and be assured that it is truly destroyed.
A very simple way is to include in the onRendered event a call to the global Blaze object.
Blaze.renderWithData(Template[template_name], data ,document.getElementById(template_id))
I'm trying to test a directive using Karma and Jasmine that does a couple of things. First being that it uses a templateUrl and second that it defines a controller. This may not be the correct terminology, but it creates a controller in its declaration. The Angular application is set up so that each unit is contained within its own module. For example, all directives are included within module app.directive, all controllers are contained within app.controller, and all services are contained within app.service etc.
To complicate things further, the controller being defined within this directive has a single dependency and it contains a function that makes an $http request to set a value on the $scope. I know that I can mock this dependency using $httpBackend mock to simulate the $http call and return the proper object to the call of this function. I've done this numerous times on the other unit tests that I've created, and have a pretty good grasp on this concept.
The code below is written in CoffeeScript.
Here is my directive:
angular.module('app.directive')
.directive 'exampleDirective', [() ->
restrict: 'A'
templateUrl: 'partials/view.html'
scope: true
controller: ['$scope', 'Service', ($scope, Service) ->
$scope.model = {}
$scope.model.value_one = 1
# Call the dependency
Service.getValue()
.success (data) ->
$scope.model.value_two = data
.error ->
$scope.model.value_two = 0
]
]
Here is the dependency service:
angular.module("app.service")
.factory 'Service', ['$http', ($http) ->
getValue: () ->
options.method = "GET"
options.url = "example/fetch"
$http _.defaults(options)
Here is the view:
<div>
{{model.value_one}} {{model.value_two}}
</div>
I've simplified this quite a bit, as my goal is only to understand how to wire this up, I can take it from there. The reason I'm structuring it this way is because I did not initially create this. I'm working on writing tests for an existing project and I don't have the ability to configure it any other way. I've made an attempt to write the test, but cannot get it to do what i want.
I want to test to see if the values are being bound to the view, and if possible to also test to see if the controller is creating the values properly.
Here is what I've got:
'use strict'
describe "the exampleDirective Directive", ->
beforeEach module("app.directive")
beforeEach module("app/partials/view.html")
ServiceMock = {
getValue : () ->
options.method = "GET"
options.url = "example/fetch"
$http _.defaults(options)
}
#use the mock instead of the service
beforeEach module ($provide) ->
$provide.value "Service", ServiceMock
return
$httpBackend = null
scope = null
elem = null
beforeEach inject ($compile, $rootScope, $injector) ->
# get httpBackend object
$httpBackend = $injector.get("$httpBackend")
$httpBackend.whenGET("example/fetch").respond(200, "it works")
#set up the scope
scope = $rootScope
#create and compile directive
elem = angular.element('<example-directive></example-directive>')
$compile(elem)(scope)
scope.$digest()
I don't know how close I am, or if this is even correct. I want to be able to assert that the values are bound to the view correctly. I've used Vojtajina's example to set up html2js in my karma.js file to allow me to grab the views. I've done a lot of research to find the answer, but I need some help. Hopefully a programmer wiser than I can point me in the right direction. Thank you.
Create the element in karma, then use the .controller() function with the name of your directive to grab the controller. For your example, replace the last couple of lines with these:
elem = angular.element('<div example-directive></div>');
$compile(elem)($rootScope);
var controller = elem.controller('exampleDirective');
Note, that given how you defined your directive, it should be by attribute, and not as an element. I'm also not 100% sure, but I don't think you need the scope.$digest; usually I just put anything that needs to be applied into a scope.$apply(function() {}) block.
I'm writing directive tests for AngularJS with Jasmine, and using templateUrl with them: https://gist.github.com/tanepiper/62bd10125e8408def5cc
However, when I run the test I get the error included in the gist:
Error: Unexpected request: GET views/currency-select.html
From what I've read in the docs I thought I was doing this correctly, but it doesn't seem so - what am I missing here?
Thanks
If you're using ngMockE2E or ngMock:
all HTTP requests are processed locally using rules you specify and none are passed to the server. Since templates are requested via HTTP, they too are processed locally. Since you did not specify anything to do when your app tries to connect to views/currency-select.html, it tells you it doesn't know how to handle it. You can easily tell ngMockE2E to pass along your template request:
$httpBackend.whenGET('views/currency-select.html').passThrough();
Remember that you can also use regular expressions in your routing paths to pass through all templates if you'd like.
The docs discuss this in more detail: http://docs.angularjs.org/api/ngMockE2E.$httpBackend
Otherwise use this:
You'll need to use the $injector to access the new backend. From the linked docs:
var $httpBackend;
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('views/currency-select.html').respond(200, '');
}));
the Karma way is to load the template html dynamically into $templateCache. you could just use html2js karma pre-processor, as explained here
this boils down to adding templates '.html' to your files in the conf.js file
as well
preprocessors = {
'.html': 'html2js'
};
and use
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
into your js testing file
If this is a unit-test, you won't have access to $httpBackend.passthrough(). That's only available in ngMock2E2, for end-to-end testing. I agree with the answers involving ng-html2js (used to be named html2js) but I would like to expand on them to provide a full solution here.
To render your directive, Angular uses $http.get() to fetch your template from templateUrl. Because this is unit-testing and angular-mocks is loaded, angular-mocks intercepts the call to $http.get() and give you the Unexpected request: GET error. You can try to find ways to by pass this, but it's much simpler to just use angular's $templateCache to preload your templates. This way, $http.get() won't even be an issue.
That's what the ng-html2js preprocessor do for you. To put it to work, first install it:
$ npm install karma-ng-html2js-preprocessor --save-dev
Then configure it by adding/updating the following fields in your karma.conf.js
{
files: [
//
// all your other files
//
//your htmp templates, assuming they're all under the templates dir
'templates/**/*.html'
],
preprocessors: {
//
// your other preprocessors
//
//
// tell karma to use the ng-html2js preprocessor
"templates/**/*.html": "ng-html2js"
},
ngHtml2JsPreprocessor: {
//
// Make up a module name to contain your templates.
// We will use this name in the jasmine test code.
// For advanced configs, see https://github.com/karma-runner/karma-ng-html2js-preprocessor
moduleName: 'test-templates',
}
}
Finally, in your test code, use the test-templates module that you've just created. Just add test-templates to the module call that you typically make in beforeEach, like this:
beforeEach(module('myapp', 'test-templates'));
It should be smooth sailing from here on out. For a more in depth look at this and other directive testing scenarios, check out this post
You could perhaps get the $templatecache from the injector and then do something like
$templateCache.put("views/currency-select.html","<div.....>");
where in place of <div.....> you would be putting your template.
After that you setup your directive and it should work just fine!
If this is still not working , use fiddler to see the content of the js file dynamically generated by htmltojs processor and check the path of template file.
It should be something like this
angular.module('app/templates/yourtemplate.html', []).run(function($templateCache) {
$templateCache.put('app/templates/yourtemplate.html',
In my case , it was not same as I had in my actual directive which was causing the issue.
Having the templateURL exactly same in all places got me through.
As requested, converting a comment to an answer.
For the people who want to make use of #Lior's answer in Yeoman apps:
Sometimes the way the templates are referenced in karma config and consequently - the names of modules produced by ng-html2js don't match the values specified as templateUrls in directive definitions.
You will need adjusting generated module names to match templateUrls.
These might be helpful:
https://github.com/karma-runner/karma-ng-html2js-preprocessor#configuration
gist: https://gist.github.com/vucalur/7238489
this is example how to test directive that use partial as a templateUrl
describe('with directive', function(){
var scope,
compile,
element;
beforeEach(module('myApp'));//myApp module
beforeEach(inject(function($rootScope, $compile, $templateCache){
scope = $rootScope.$new();
compile = $compile;
$templateCache.put('view/url.html',
'<ul><li>{{ foo }}</li>' +
'<li>{{ bar }}</li>' +
'<li>{{ baz }}</li>' +
'</ul>');
scope.template = {
url: 'view/url.html'
};
scope.foo = 'foo';
scope.bar = 'bar';
scope.baz = 'baz';
scope.$digest();
element = compile(angular.element(
'<section>' +
'<div ng-include="template.url" with="{foo : foo, bar : bar, baz : baz}"></div>' +
'<div ng-include="template.url" with=""></div>' +
'</section>'
))(scope);
scope.$digest();
}));
it('should copy scope parameters to ngInclude partial', function(){
var isolateScope = element.find('div').eq(0).scope();
expect(isolateScope.foo).toBeDefined();
expect(isolateScope.bar).toBeDefined();
expect(isolateScope.baz).toBeDefined();
})
});
If you are using the jasmine-maven-plugin together with RequireJS you can use the text plugin to load the template content into a variable and then put it in the template cache.
define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
"use strict";
describe('Directive TestSuite', function () {
beforeEach(inject(function( $templateCache) {
$templateCache.put("path/to/template.html", directiveTemplate);
}));
});
});
I have a viewmodel whose template I want to change dynamically at runtime when the state of my application changes. I referred to this link
while coming up with my solution.
In my html page I have a div that is bound to a list of view models:
<div class="app"
data-bind="template: {name: templateSelector, foreach: viewModelBackStack}">
</div>
And my templateSelector method looks like this:
this.templateSelector = function(viewModel)
{
if (!_itemTemplate)
{
_itemTemplate = ko.computed(function() {return this.template();}, viewModel);
}
return _itemTemplate();
}
var _itemTemplate;
As can be seen, I am constructing a computed observable which returns viewModel's template.
My viewModel looks like this:
function MyViewModel
{
this.template = ko.observable("MyTemplate");
}
I am changing the value of template as a result of an ajax call being completed and I see that computed observable is called correctly as well (I placed an alert in there to verify it), however the bindings in html does not update the template of my viewmodel. Any help will be appreciated.
UPDATE: I found the bug that was causing it not to work. Basically I was including jquery.tmpl plugin before including knockout.js. Removing jquery.tmpl did the trick !
I don't see a problem with your code, unless it lies in the part where you update the template observable as the result of an AJAX call. Make sure that you have a reference to your view model and are setting it as an observable vm.template(newValue); and not recreating the observable.
Here is your code working: http://jsbin.com/ipijet/5/edit#javascript,html,live
One thing to note is that bindings are already executed within the context of a computed observable, so it is unnecessary to create your own within your templateSelector function.
You can simply create a function that returns your observable directly like:
this.getTemplate = function(data) {
return data.template();
};
http://jsbin.com/ipijet/3/edit#javascript,html,live
Here is an article that I wrote a while back on this topic: http://www.knockmeout.net/2011/03/quick-tip-dynamically-changing.html