Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test if custom angular directive shows/hides element

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?

like image 574
edua_glz Avatar asked Apr 23 '15 18:04

edua_glz


2 Answers

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.

like image 146
jtrussell Avatar answered Oct 28 '22 13:10

jtrussell


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

  1. 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.

  1. Get the getBoundingClientRect() for the element. Then check that document.elementFromPoint for each of the corners does not return the element you expect to be hidden.

:

like image 43
Enzey Avatar answered Oct 28 '22 13:10

Enzey