I am trying to unit test (Jasmine) AngularJS and Flot Charts but receive the following errors. I do not receive these errors in the console of my application and the charts render as expected.
PhantomJS 1.9.2 (Mac OS X) Charts Directive should populate the container element FAILED
TypeError: 'undefined' is not an object (evaluating 'placeholder.css("font-size").replace')
at parseOptions (/Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:740)
at Plot (/Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:673)
at /Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:3059
at /Library/WebServer/Documents/zui/app/js/directives/charts.js:6
at /Library/WebServer/Documents/zui/app/js/libs/angular.js:7942
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:10
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:23
at invoke (/Library/WebServer/Documents/zui/app/js/libs/angular.js:2902)
at workFn (/Library/WebServer/Documents/zui/app/js/libs/angular-mocks.js:1795)
at /Library/WebServer/Documents/zui/app/js/libs/angular-mocks.js:1782
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:24
PhantomJS 1.9.2 (Mac OS X): Executed 30 of 40 (1 FAILED) (0 secs / 0.126 secs)
Charts Directive:
FAILED - should populate the container element TypeError: 'undefined' is not an object (evaluating 'placeholder.css("font-size").replace')
at parseOptions (/Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:740)
at Plot (/Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:673)
at /Library/WebServer/Documents/zui/app/js/libs/flot/jquery.flot.js:3059
at /Library/WebServer/Documents/zui/app/js/directives/charts.js:6
at /Library/WebServer/Documents/zui/app/js/libs/angular.js:7942
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:10
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:23
at invoke (/Library/WebServer/Documents/zui/app/js/libs/angular.js:2902)
at workFn (/Library/WebServer/Documents/zui/app/js/libs/angular-mocks.js:1795)
at /Library/WebServer/Documents/zui/app/js/libs/angular-mocks.js:1782
at /Library/WebServer/Documents/zui/test/unit/directives/charts.spec.js:24
PhantomJS 1.9.2 (Mac OS X): Executed 31 of 40 (1 FAILED) (0 secs / 0.134 secs)
Directive:
angular.module('directives.FlotCharts', [])
.directive('flotChart', function () {
return {
restrict: 'EA',
controller: ['$scope', '$attrs', function ($scope, $attrs) {
var plotid = '#' + $attrs.id,
model = $scope[$attrs.ngModel];
$scope.$watch('model', function (x) {
$.plot(plotid, x.data, x.options);
});
}]
};
});
Controller:
angular.module('Charts', ['directives.FlotCharts'])
.controller('diskChartCtrl', ['$scope', function ($scope) {
$scope.model = {};
$scope.model.data = [
{
label: "Available",
data: 20,
color:"#00a34a"
},
{
label: "Used",
data: 100,
color:"#c00"
}
];
$scope.model.options = {
series: {
pie: {
innerRadius: 0.5, // for donut
show: true,
label: {
formatter: function (label, series) {
return '<div class="pie">' + label + ': ' +
series.data[0][1] + 'GB <br>(' + Math.round(series.percent) + '%)</div>';
}
}
}
},
legend: {
show: false
}
};
}])
}]);
Test Spec:
describe('Charts Directive', function () {
var scope, html, tmpl, ctrl, $compile;
var compileTmpl = function (markup, scope) {
var el = $compile(markup)(scope);
scope.$digest();
return el;
};
beforeEach(function () {
module('directives.FlotCharts');
module('Charts');
inject(function ($rootScope, _$compile_, $controller) {
$compile = _$compile_;
scope = $rootScope.$new();
ctrl = $controller('diskChartCtrl', {$scope: scope});
html = angular.element('<div data-flot-chart id="disk" data-chart="pie" data-status="danger" data-ng-model="model" data-ng-controller="diskChartCtrl"></div>');
tmpl = compileTmpl(html, scope);
});
});
it('should populate the container element', function () {
expect(true).toBe(true);
//expect(tmpl.html()).toContain('canvas');
});
});
Any insight is greatly appreciated.
This may not be the answer to your question, but hopefully it will help steer you in the right direction. Here's the source of the exception from jquery.flot.js:
fontDefaults = {
style: placeholder.css("font-style"),
size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)),
variant: placeholder.css("font-variant"),
weight: placeholder.css("font-weight"),
family: placeholder.css("font-family")
};
It appears that placeholder.css('font-size') is returning undefined. I seem to remember hearing of some problems with jQuery.css('margin') not working in PhantomJS, but jQuery.css('margin-left') behaving correctly.
If you explicitly set style: "font-size: 10px;" on the element do you get different results? I noticed you were setting the directive's class to pie, have you included any stylesheets?
I was able to solve this issue as commented by compiling the markup against rootScope and setting inline width and height styles. It may have been an issue of missing width and height properties.
inject(['$rootScope', '$compile', '$controller', function ($rootScope, $compile, $controller) {
scope = $rootScope.$new();
ctrl = $controller('itemChartCtrl', { $scope: scope });
tmpl = '<div data-flot-chart id="items" data-chart="pie" data-status="danger" data-ng-model="model" data-ng-controller="itemChartCtrl" style="width:300px;height:300px"></div>';
$compile(tmpl)($rootScope);
}]);
Related
I'm building a wagtail / django app using requirejs as js assets combiner, for the site front end.
I'm using it because I've ever been in a kind of JS dependencies hell, where nothing works because of multiple versions of same libs loaded, from different django apps... (I don't even know if it is a good solution)
I've to tell that I'm not a JS expert, and I've none arround me :(
I'm using the good old templates to render the pages, not using angular, react, riot nor vue : I'm a pretty old school dev :)
I've already adapted some scripts to use require, but I'm stuck for now...
I've installed the django_select2 application, and I'm trying to adapt the django_select2.js asset.
I've loaded select2 through bower, and I've updaetd my config.js:
"shim": {
select2: {
deps: ["jquery"],
exports: "$.fn.select2"
}
},
paths: {
...
select2: "select2/dist/js/select2"
}
Then I'm trying to adapt the django_select2.js:
require(['jquery', 'select2'], function ($, select2) {
return (function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
I'm having a Mismatched anonymous define() when running my page in the browser...
I'me realy not a JS expert, I'm coding by trial and error... Could anyone help me with this ?
Thanks !
OK, I have an auto-response...
I've inherited the mixin:
class _Select2Mixin(Select2Mixin):
def _get_media(self):
"""
Construct Media as a dynamic property.
.. Note:: For more information visit
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(js=('django_select2/django_select2.js', ),
css={'screen': (settings.SELECT2_CSS,)})
media = property(_get_media)
class _Select2MultipleWidget(_Select2Mixin, forms.SelectMultiple):
pass
Then I can use the widget:
class DocumentationSearchForm(forms.Form):
...
document_domains = forms.ModelMultipleChoiceField(required=False,
label=_('Document domains'),
queryset=NotImplemented,
widget=_Select2MultipleWidget)
I've set my config.js file for path:
requirejs.config({
paths: {
jquery: 'jquery/dist/jquery',
select2: "select2/dist/js"
},
shim: {
"select2/select2": {
deps: ["jquery"],
exports: "$.fn.select2"
}
}
});
Then I've overridden the django_select2.js file, to wrap the content in a require satement:
require(['jquery', 'select2/select2'], function ($) {
(function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
That's all, folks !
This is my first time testing directives. Does anyone know how I should get started on this or know of any good resources for finding out how to test directives? The angular docs where not a great help
angular.module('pb.campaigns.directives')
.directive('pbImagePicker', ['$window', '$document', function ($window, $document) {
return {
restrict: "E",
template: '<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />',
scope: {
fileId: '=pbFileId',
accountId: '=pbAccountId',
defaultSrc: '#pbDefaultSrc',
width: '#pbWidth',
height: '#pbHeight'
},
controller: 'pbImagePickerController',
link: function (scope, element, attrs) {
scope.$watch('defaultSrc', function (value) {
if (value !== undefined) {
scope.imageSource = value;
}
});
element.click(function () {
scope.pickImage(scope.accountId).then(function (image) {
scope.imageSource = image.storageUrl;
scope.fileId = image.fileId;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
});
}
};
}]);
I was trying to do something like the following but im not sure if im on the right track or how to proceed.
describe('pbImagePicker', function () {
beforeEach(module('pb.campaigns.directives'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var $compile;
var $rootScope;
beforeEach(inject(function (_$compile_, _$rootScope_, _$document_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$document = _$document_;
}));
describe('', function () {
it('Replaces the element with the appropriate content', function () {
// Compile a piece of HTML containing the directive
var element = $compile("<pb-image-picker></pb-image-picker>")($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()).toEqual('<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />');
});
});
describe('element.click()', function () {
beforeEach(function () {
element = angular.element('<img data-ng-src="{{ imageSource }}" width="{{width}}" height="{{height}}" alt="Image Picker" class="img-rounded" />');
compiled = $compile(element)($rootScope);
compiled.triggerHandler('click');
expect().toEqual();
});
it('should resolve a promise when clicked', function () {
spyOn($rootScope, 'pickImage');
$rootScope.$digest();
expect($rootScope.pickImage).toHaveBeenCalled();
});
it('should assign data from resolved promise when clicked', function () {
$rootScope.$digest();
expect($rootScope.imageSource).toEqual();
expect($rootScope.fileId).toEqual();
});
});
});
Use the AngularJS tests specs as a reference. For example:
'use strict';
describe('style', function()
{
var element;
afterEach(function()
{
dealoc(element);
});
it('should compile style element without binding', inject(function($compile, $rootScope)
{
element = jqLite('<style type="text/css">.header{font-size:1.5em; h3{font-size:1.5em}}</style>');
$compile(element)($rootScope);
$rootScope.$digest();
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.header{font-size:1.5em; h3{font-size:1.5em}}');
}));
it('should compile style element with one simple bind', inject(function($compile, $rootScope)
{
element = jqLite('<style type="text/css">.some-container{ width: {{elementWidth}}px; }</style>');
$compile(element)($rootScope);
$rootScope.$digest();
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.some-container{ width: px; }');
$rootScope.$apply(function()
{
$rootScope.elementWidth = 200;
});
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.some-container{ width: 200px; }');
}));
it('should compile style element with one bind', inject(function($compile, $rootScope)
{
element = jqLite('<style type="text/css">.header{ h3 { font-size: {{fontSize}}em }}</style>');
$compile(element)($rootScope);
$rootScope.$digest();
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.header{ h3 { font-size: em }}');
$rootScope.$apply(function()
{
$rootScope.fontSize = 1.5;
});
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.header{ h3 { font-size: 1.5em }}');
}));
it('should compile style element with two binds', inject(function($compile, $rootScope)
{
element = jqLite('<style type="text/css">.header{ h3 { font-size: {{fontSize}}{{unit}} }}</style>');
$compile(element)($rootScope);
$rootScope.$digest();
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.header{ h3 { font-size: }}');
$rootScope.$apply(function()
{
$rootScope.fontSize = 1.5;
$rootScope.unit = 'em';
});
// read innerHTML and trim to pass on IE8
expect(trim(element[0].innerHTML)).toBe('.header{ h3 { font-size: 1.5em }}');
}));
it('should compile content of element with style attr', inject(function($compile, $rootScope)
{
element = jqLite('<div style="some">{{bind}}</div>');
$compile(element)($rootScope);
$rootScope.$apply(function()
{
$rootScope.bind = 'value';
});
expect(element.text()).toBe('value');
}));
});
References
AngularJS source: ngStyleSpec
AngularJS test helpers: testabilityPatch.js
What is the best way to set up the DS.store for testing Ember-data models?
UPDATE: Here is a fiddle: http://jsfiddle.net/jdcravens/B7Jy6/
I'm using the yeoman ember-generator, and I want to provide a guide for a simple getting started setup for QUnit and Mocha tests.
I've bootstrapped a project using:
$ yo ember --karma
What I am unable to do so far is access the ember-data store from my tests?
First, I've tried to do a setup similar to ember-data test suite abstracting store setup to the initializer.
document.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');
document.write('<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 800px; height: 500px; overflow: auto; z-index: 9999; border: 5px solid #ccc; } #ember-testing { zoom: 80%; }</style>');
Ember.testing = true;
App.rootElement = "#ember-testing";
App.setupForTesting();
App.injectTestHelpers();
//Ember.run(App, App.advanceReadiness);
// Error: Assertion Failed: You cannot defer readiness since the `ready()` hook
// has already been called.
window.setupStore = function(options) {
var env = {};
options = options || {};
var container = env.container = new Ember.Container();
var adapter = env.adapter = (options.adapter || DS.Adapter);
delete options.adapter;
for (var prop in options) {
container.register('model:' + prop, options[prop]);
}
container.register('store:main', DS.Store.extend({
adapter: adapter
}));
container.register('serializer:-default', DS.JSONSerializer);
container.register('serializer:-rest', DS.RESTSerializer);
container.register('adapter:-rest', DS.RESTAdapter);
container.injection('serializer', 'store', 'store:main');
env.serializer = container.lookup('serializer:-default');
env.restSerializer = container.lookup('serializer:-rest');
env.store = container.lookup('store:main');
env.adapter = env.store.get('defaultAdapter');
return env;
};
window.createStore = function(options) {
return setupStore(options).store;
};
window.start = function () {};
window.stop = function () {};
Then from my tests:
/*global describe, it */
'use strict';
(function () {
//var store = App.__container__.lookup('store:main');
//console.log(store);
// LOG: undefined
// PhantomJS 1.9.6 (Mac OS X): Executed 0 of 0 ERROR (0.684 secs / 0 secs)
//So then, I try to generate use createStore() from the initializer.
var store;
module("unit/model - DS.Activity", {
setup: function() {
store = createStore();
},
teardown: function() {
store = null;
}
});
test('display_id property returns correct value', function() {
Ember.run(function () {
var activity = store.push('App.Activity', {'id': 1, 'display_id': 'activity1'});
var result = activity.get('display_id');
equal(result, 'activity1', "display_id was " + result);
});
});
// PhantomJS 1.9.6 (Mac OS X) unit/model - DS.Activity display_id property returns
// display_id FAILED
// Died on test #1 at ../node_modules/qunitjs/qunit/qunit.js:42
// at ../test/spec/test.js:58
// at ../test/spec/test.js:60: No model was found for 'App.Activity'
// Error: No model was found for 'App.Activity'
// PhantomJS 1.9.6 (Mac OS X): Executed 1 of 1 (1 FAILED) ERROR (0.743 secs / 0.003 secs)
})();
So the question is .. what is the best way to access the store and test Ember-data models using Karma Qunit basic setup?
Ive updated the fiddle with a few corrections: http://jsfiddle.net/jdcravens/B7Jy6/
module("unit/model - App.Activity", {
setup: function() {
store = createStore({activity: App.Activity}); // Pass the model
},
teardown: function() {
store = null;
}
});
then in the tests, notice 'activity', rather than 'App.Activity':
test('display_id property returns correct value', function() {
Ember.run(function () {
var activity = store.push('activity', {'id': 1, 'display_id': 'activity1'});
var result = activity.get('display_id');
equal(result, 'activity1', "display_id was " + result);
});
});
I also decided to move the creation of the activity record to the setup:
module("unit/model - App.Activity", {
setup: function() {
store = createStore({activity: App.Activity}); // Pass the model
Ember.run(function () {
activity = store.push('activity', {
'id': 1,
'display_id': 'activity1'
});
},
teardown: function() {
store = null;
}
});
then, in the tests:
test('display_id property returns correct value', function() {
var result = activity.get('display_id');
equal(result, 'activity1', "display_id was " + result);
});
});
I am running my first test on a directive, and while it seems that the directive is being invoked (there is a GET request being made for the directive's template url), the DOM manipulation that is supposed to occur in the link function is not - well it's that or I'm just not testing it correctly.
// Generated by CoffeeScript 1.6.3
(function() {
var sprangularDirectives;
sprangularDirectives = angular.module('sprangularDirectives', []);
sprangularDirectives.directive('productDirective', function() {
return {
scope: {
product: '='
},
templateUrl: 'partials/product/_product.html',
link: function(scope, el, attrs) {
return el.attr('testattr', 'isthisclasshere');
}
};
});
}).call(this);
Test:
'use strict';
describe('productDirective', function() {
var scope, el, directive, $httpBackend, compiler, compiled, html;
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(function() {
html = '<div data-product-directive product="currentProduct"></div>';
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
// jasmine.getHTMLFixtures().fixturesPath='base/partials/product';
$httpBackend.when('GET', 'partials/product/_product.html').respond(
' <div class="product">'
+' {{ currentProduct.name }}'
+' {{ currentProduct.page }}'
+' </div>'
);
scope = $injector.get('$rootScope');
el = angular.element(html);
compiler = $injector.get('$compile');
compiled = compiler(el);
compiled(scope);
scope.$digest();
});
});
it('Should have an isolate scope', function() {
scope.currentProduct = {name: 'testing'};
console.log(el.attr('testattr'))
console.log(el.isolateScope())
expect(el.scope().product.name).toBe('testing');
});
});
console.log(el.attr('testattr')) returns undefined...even though when I boot my browser up it's there. Some help would be awesome :) thanks
The element you are using is the pre-compiled element reference. The element you want is returned from the "compiled(scope)" method call:
compiler = $injector.get('$compile');
compiled = compiler(el);
var element = compiled(scope); // <-- This guy!
I use this snippet as a testing helper method:
var compileTemplate = function (scope, rawTemplate) {
var template = angular.element(rawTemplate)
var element = $compile(template)(scope)
scope.$digest()
var new_scope = element.scope()
return [element, new_scope]
}
Here is the directive, that wraps jquery-ui autocomplete
angular.module('myApp.directives', [])
.directive('autocomplete', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<input ng-model="autocomplete" type="text"/>',
link: function (scope, element, attrs) {
scope.$watch(attrs.typedvalue, function () {
element.autocomplete({
search: function (event) {
scope[attrs.typedvalue] = this.value;
scope[attrs.fullselection] = '';
scope[attrs.selectionid] = '';
scope[attrs.shortselection] = '';
scope.$apply();
},
source: scope.fetchList,
select: function (event, ui) {
scope[attrs.fullselection] = ui.item.label;
scope[attrs.selectionid] = ui.item.itemId;
scope[attrs.shortselection] = ui.item.value;
scope.$apply();
}
});
});
}
};
});
I'm trying to unit-test it with the following test (following instructions from here https://github.com/vojtajina/ng-directive-testing):
describe('Directives', function () {
beforeEach(module('myApp.directives'));
describe('autocomplete directive', function () {
var elm, scope;
beforeEach(inject(function ($rootScope, $compile) {
elm = angular.element('<autocomplete fullselection="fullDstn" shortselection="dstn" selectionid="geonameId" typedvalue="typedValue" id="DstnSlctr"/>');
scope = $rootScope;
$compile(elm)(scope);
scope.$digest();
}));
it('should create input', inject(function ($compile, $rootScope) {
expect(elm.id).toBe('DstnSlctr');
expect(elm.prop('tagName')).toBe('INPUT');
debugger;
}));
});
});
But I get an error:
TypeError: Object [[object HTMLInputElement]] has no method 'autocomplete'
at Object.fn (C:/Users/kmukhort/Documents/_files/TMate/AngularTest/a
pp/js/directives.js:13:33)
on the line element.autocomplete({
I suspect that jquery-ui functionality is not attached to the element while $compile.
I'm referring jquery-ui library in testacular.config
basePath = '../';
files = [
...
'app/lib/jquery-ui-*.js',
];
Could you, please, tell, what I'm doing wrong?
Thanks!
Ksenia
I think you need to replace:
element.autocomplete(...);
With:
$(element).autocomplete(...);