Testing Directives in Angular - unit-testing

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

Related

SharePoint 2013 - Attach file to custom list using drag and drop

In Sharepoint 2013, is it possible to drag and drop a file (.jpg , .pdf, .png) to a custom list as an attachment? If so, can this be achieved by using a script (JS, jquery)? So, if a user is reporting a bug (NewForm.aspx), and he has a screenshot of the error message saved as a .jpg, I would want him to be able to drop this file into a drop zone on the form.
Can this scenario work - I would be very grateful for your suggestions?
Here is my tested code.
<style type="text/css">
.droppable {
background: #08c;
color: #fff;
padding: 100px 0;
text-align: center;
}
.droppable.dragover {
background: #00CC71;
}
</style>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript">
function makeDroppable(element, callback) {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('id', 'dragdropContent');
input.setAttribute('multiple', true);
input.style.display = 'none';
input.addEventListener('change', triggerCallback);
element.appendChild(input);
element.addEventListener('dragover', function (e) {
e.preventDefault();
e.stopPropagation();
element.classList.add('dragover');
});
element.addEventListener('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
element.classList.remove('dragover');
});
element.addEventListener('drop', function (e) {
e.preventDefault();
e.stopPropagation();
element.classList.remove('dragover');
triggerCallback(e);
});
element.addEventListener('click', function () {
input.value = null;
input.click();
});
function triggerCallback(e) {
var files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
callback.call(null, files);
}
}
$(function () {
String.prototype.format = function () {
var args = [].slice.call(arguments);
return this.replace(/(\{\d+\})/g, function (a) {
return args[+(a.substr(1, a.length - 2)) || 0];
});
};
var element = document.querySelector('.droppable');
function callback(files) {
// Here, we simply log the Array of files to the console.
//console.log(files);
var fileName = files[0].name;
// Construct the endpoint. mydoc is document library name
var reader = new FileReader();
reader.file = files[0];
reader.onload = function (event) {
var arrayBuffer = event.target.result;
//var fileData = '';
//var byteArray = new Uint8Array(arrayBuffer);
//for (var i = 0; i < byteArray.byteLength; i++) {
// fileData += String.fromCharCode(byteArray[i]);
//}
var fileEndpoint = String.format(
"{0}/_api/web/lists/getByTitle('{1}')/RootFolder/files" +
"/add(overwrite=true, url='{2}')",
_spPageContextInfo.webAbsoluteUrl, 'mydoc', fileName);
//Add the file to the SharePoint folder.
$.ajax({
url: fileEndpoint,
type: "POST",
data: arrayBuffer,//fileData,
processData: false,
headers: {
"X-RequestDigest": jQuery("#__REQUESTDIGEST").val(),
"accept": "application/json;odata=verbose",
"content-length": arrayBuffer.byteLength,
//"IF-MATCH": itemMetadata.etag,
//"X-HTTP-Method": "MERGE"
},
success: function (data, b, c) {
alert('File upload succeeded.');
},
error: function (a, b, err) {
alert('Error: ' + err.responseText);
}
})
};
reader.onerror = function (e) {
alert(e.target.error);
}
var arrayBuffer = reader.readAsArrayBuffer(files[0]);
}
makeDroppable(element, callback);
})
</script>
<div class="droppable">
<p>Drag files here or click to upload</p>
</div>
Update:
This is the control in my test sample:
You could try to debug is any JS error in your page.

How to incorporate multiple objects of json array into react component

I would like to iterate over this json string and input the values into a react component: {"users":[{"name":"jkhhjjh","url":"/users/240/individual_show"},{"name":"bob","url":"/users/241/individual_show"}]}
I can currently make one component from the first or second object within the json index but I cannot iterate over the whole string. My react-jsx code is below:
enter code here
var FriendInfo = React.createClass({
getInitialState: function() {
return {
url: '',
name: ''
};
},
componentDidMount: function() {
$.get(this.props.source, function(user) {
var users= user["users"][0];
if (this.isMounted()) {
this.setState({url: users.url,
url: users.url,
name: users.name
});
}
}.bind(this));
},
render: function() {
return (
<div className="friendInfo">
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/92/SRC-TV.svg/140px-SRC-TV.svg.png"></img>
<a href={this.state.url}>{this.state.name}</a>
</div>
);
}
});
var RequestLinks= React.createClass({
render: function() {
return (
<div className="requestLinks" style={{float:"right"}}>
accept
deny
</div>
);
}
});
var FriendBox = React.createClass({
render: function() {
return (
<div className="friendBox">
<FriendInfo source="/individual_relationships/show"/>
<RequestLinks />
</div>
);
}
});
React.render(<FriendBox />, document.getElementById('test'));`enter code here`
After the line var users= user["users"][0] I am able to insert the data into one iteration of the component. I would like to make a loop that would iterate over user["users"][0] and user["users"][1] to make two components on the page. How would I go about doing this?
You need to make use of map function which returns array of indivisual elements.
var FriendInfo = React.createClass({
getInitialState: function () {
return {
users: []
};
},
componentDidMount: function () {
$.get(this.props.source, function (user) {
var users = user["users"];
if (this.isMounted()) {
this.setState({users: users});
}
}.bind(this));
},
render: function () {
return (
<div>
{
this.state.users.map(function (user) {
return <div className="friendInfo">
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/92/SRC-TV.svg/140px-SRC-TV.svg.png"></img>
<a href={user.url}>{user.name}</a>
</div>
})
}
</div>
);
}
});
var RequestLinks = React.createClass({
render: function () {
return (
<div className="requestLinks" style={{float: "right"}}>
accept
deny
</div>
);
}
});
var FriendBox = React.createClass({
render: function () {
return (
<div className="friendBox">
<FriendInfo source="/individual_relationships/show"/>
<RequestLinks />
</div>
);
}
});
React.render(<FriendBox />, document.getElementById('test'));

How to Unit Test a directive which binds from controller

I have a simple directive which simply uppercases the binding from the controller.
My unit test code isn't running. Any ideas?
html:
<div ng-controller="MainCtrl as main">
<div uppercase>{{main.message}}</div>
</div>
controller:
app.controller('MainCtrl', function(){
this.message = 'hello world'
})
directive:
app.directive('uppercase', function($timeout) {
return {
restrict: 'A',
transclude: true,
template: '<span ng-transclude></span>',
link: function(scope, el, attr) {
$timeout(function(){
el.text(el.text().toUpperCase());
});
}
}
});
My Unit Test currently is:
describe("App", function() {
var $compile
, $rootScope
, $controller
, MainCtrl
, element;
beforeEach(module('app'))
beforeEach(inject(function(_$compile_, _$rootScope_, _$controller_){
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
MainCtrl = $controller('MainCtrl');
}))
describe("MainCtrl", function() {
it("should have a message property", function() {
expect(MainCtrl.message).toBe('hello world');
});
});
describe("Uppercase directive", function() {
it("should return uppercase text", function() {
element = '<p superman>{{MainCtrl.message}}</p>';
element = $compile(element)($rootScope);
$rootScope.$digest();
expect(element.text()).toBe('HELLO WORLD');
});
});
});
I cannot use $timeout in my test, so how should I test it?
I haven't tested it, but I believe that before the expect(element.text()).toBe('HELLO WORLD');, you'll have to flush your timeout.
I mean, you'd have an extra var called $timeout (in the beforeEach) and then, before the expected mentioned above, you'd call $timeout.flush().

Unit testing AngularJS and Flot Charts

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);
}]);

Angular directive test throws an error

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(...);