Reputation: 1424
I have the following directive which tells me whether or not the image i'm trying to use has loaded successfully or not:
return {
restrict: 'A',
scope: {
imageLoad: '@'
},
link: function(scope, element, attrs) {
attrs.$observe('imageLoad', function (url) {
var deferred = $q.defer(),
image = new Image();
image.onerror = function () {
deferred.resolve(false);
};
image.onload = function () {
deferred.resolve(true);
};
image.src = url;
return deferred.promise;
});
}
};
All i then want to do is two simple tests that test image.onerror
and image.onload
but i only seem to get into the on error function, here's what i have so far:
compileDirective = function() {
var element = angular.element('<div data-image-load="http://placehold.it/350x150"></div>');
$compile(element)(scope);
$rootScope.$digest();
return element;
};
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
}));
it('should do something', function() {
var compiledElement, isolatedScope;
compiledElement = compileDirective();
isolatedScope = compiledElement.isolateScope();
expect(true).toBe(true);
});
obviously this test passes as it just expects true to be true, however in terms of coverage this gets into the onerror function, so i somehow need to test that the deferred.promise should return false.
so ultimately a two part question, how do i get the result of the deferred.resolve?
and secondly how do i get into the onload function?
i've had a look around and seen some suggestions of adding the following:
element[0].setAttribute('imageLoad','http://placehold.it/350x150');
$compile(element)(scope);
element.trigger('imageLoad');
and leaving the data-image-load=""
blank, but haven't seemed to have any luck, any suggestions would be great.
Upvotes: 0
Views: 2147
Reputation: 38490
From what you have shown, you shouldn't need the promise at all.
Even if you did, since the promise is only used internally and no one is using the result, it should be considered an implementation detail and your test shouldn't care about it.
Let us say you have the following directive:
app.directive('imageLoad', function() {
return {
restrict: 'A',
scope: {
imageLoad: '@'
},
link: function(scope, element, attrs) {
var fallback = 'http://placekitten.com/g/200/300';
attrs.$observe('imageLoad', function(url) {
var image = new Image();
image.onerror = function(e) {
setBackground(fallback);
};
image.onload = function() {
setBackground(url);
};
image.src = url;
});
function setBackground(url) {
element.css({
'background': 'url(' + url + ') repeat 0 0'
});
}
}
};
});
Demo of it in use: http://plnkr.co/edit/3B8t0ivDbqOWU2YxgrlB?p=preview
From an outside perspective, the purpose of the directive is to set the element's background to either the passed url or to the fallback, based on if the passed url is working.
So what you want to test is:
This means that you need to be able to control if the image can be loaded or not.
To prevent any network traffic in your test I would recommend using data URIs instead of URLs.
Example:
var validImage = 'data:image/jpeg;base64, + (Valid data omitted)';
var invalidImage = 'data:image/jpeg;base64,';
Full example:
describe('myApp', function() {
var $compile,
$rootScope;
beforeEach(module('myApp'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
var validImage = 'data:image/jpeg;base64, + (Valid data omitted)';
var invalidImage = 'data:image/jpeg;base64,';
compileDirective = function(url) {
var element = angular.element('<div data-image-load="' + url + '"></div>');
return $compile(element)($rootScope.$new());
};
it('should use correct background when image exists', function(done) {
var element = compileDirective(validImage);
$rootScope.$digest();
setTimeout(function() {
var background = element.css('background');
expect(background).toBe('url("' + validImage + '") 0px 0px repeat');
done();
}, 100);
});
it('should use fallback background when image does not exist', function(done) {
var element = compileDirective(invalidImage);
$rootScope.$digest();
setTimeout(function() {
var background = element.css('background');
expect(background).toBe('url("http://placekitten.com/g/200/300") 0px 0px repeat');
done();
}, 100);
});
});
Note that since loading of an image is asynchronous you need to add a bit of waiting in your tests.
You can read more about how it is done with Jasmine here.
Demo: http://plnkr.co/edit/W2bvHih2PbkHFhhDNjrG?p=preview
Upvotes: 2