I have a custom directive that shows/hide an element based on the value of another service. I tried creating a test to see if the directive does what is should do. At the beginning I tested agains the ":visible" selector, but it always returned false even when I knew that the element was actually showing.
it('Should show element if logged', function () {
element = angular.element('<p hc-auth>Some Text</p>');
AuthService.Logged = true;
scope = $rootScope.$new();
scope.$apply(function () {
$compile(element)(scope);
});
expect(element.is(":visible")).toBeTruthy();
});
After some debugging, I realized that during the test execution, the element had a width and height of 0 even if the display was set to block, and therefore the ":visible" selector was always returning false. I changed it to check against the display selector instead and now it correctly tests the element, but this seems to be too dependent on the implementation of how to show an element.
it('Should show element if logged', function () {
element = angular.element('<p hc-auth>Some Text</p>');
AuthService.Logged = true;
scope = $rootScope.$new();
scope.$apply(function () {
$compile(element)(scope);
});
expect(element.css('display')).toBe('block');
});
What would be the best approach to this situation?
Your question got me wondering how angular tests ngHide
and ngShow
, here's a snippet from their spec file:
it('should show if the expression is a function with a no arguments', function() {
element = jqLite('<div ng-show="exp"></div>');
element = $compile(element)($scope);
$scope.exp = function() {};
$scope.$digest();
expect(element).toBeShown();
});
Seems tidy. At first I thought they must be using jquery-jasmine which does expose a toBeHidden
matcher (though not toBeShown
as it turns out), in fact they wrote their own custom matcher for determining visibility:
// [jtrussell] Custom Jasmine Matcher
toBeShown: function() {
this.message = valueFn(
"Expected element " + (this.isNot ? "" : "not ") + "to have 'ng-hide' class");
return !isNgElementHidden(this.actual);
}
// [jtrussell] ...
// [jtrussell] The helper from elsewhere in same file
function isNgElementHidden(element) {
// we need to check element.getAttribute for SVG nodes
var hidden = true;
forEach(angular.element(element), function(element) {
if ((' ' + (element.getAttribute('class') || '') + ' ').indexOf(' ng-hide ') === -1) {
hidden = false;
}
});
return hidden;
}
So I guess they're cheating a little... checking for the existence of an .ng-hide
class to determine visibility. But I suppose they were once in your shoes and decided this was the best route, given that angular already supports this classname.
Since we're already using angular perhaps you could consider using the .ng-hide
class to set visibility and writing a similar helper.
Edit:
As a side note I'll say that adding/removing .ng-hide
to set visibility will have the benefit of getting for free any ngAnimations the user of your module has already put in place for that class.
Since an element being visible is a loaded term and :visible
is not a real CSS selector there is no silver bullet.
Here are a few possibilities
using not
in Jasmine.
expect(element.css('display')).not.toBe('hidden');
However that would not cover the case that the element is displayed but not visible because it has no opacity or it is behind another element.
getBoundingClientRect()
for the element. Then check that document.elementFromPoint
for each of the corners does not return the element you expect to be hidden.:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With