Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing a directive with $parsers (and $formatters)

I have the following directive:

!(function (window, angular) {
    'use strict';

    /**
     * @ngdoc directive
     * @name app.directive:social
     * @description
     * # social
     */
    angular.module('app')
        .directive('social', function(social_network_conf) {
            return {
                restrict: 'A',
                scope: {
                    social: "@"
                },
                require: 'ngModel',
                controller: function($scope, $element){
                    //for tests only
                    $scope.render = function(){
                        //how to I get the ngModel here
                        ngModel.$render();
                    };

                    $scope.setViewValue = function(val){
                        ngModel.$setViewValue(val);
                    };
                },
                link: function(scope, element, attr, ngModel) {

                    ngModel.$formatters.push(function(value) {// from model to view
                        value = value.trim();
                        if(value){
                            if (value.indexOf(social_network_conf.matcher) === 0){
                                var split_link = value.split(social_network_conf.divider);
                                return split_link[split_link.length-1];
                            }
                            else{
                                return value;
                            }
                        }
                    });

                    ngModel.$parsers.push(function(value) { // from view to model
                        value = value.trim();
                        if(value){
                            if (value.indexOf(social_network_conf.matcher) === 0){
                                return value;
                            }
                            else{
                                return social_network_conf.prefix + scope.social +
                                       social_network_conf.suffix + value;
                            }
                        }
                    });
                }
            };
        });
}(window, window.angular));

The test goes as following:

'use strict';

describe('Directive: social', function () {

  // load the directive's module
  beforeEach(module('app'));

  var element,
    social_network_conf,
    linker,
    scope,
    $httpBackend;

  beforeEach(inject(function ($rootScope, _$httpBackend_, _social_network_conf_) {
    scope = $rootScope.$new();
      social_network_conf = _social_network_conf_;
      //Must be an object to make use of prototypical inheritence for out-side-of-isolate-scope access
      scope.models = {};

      $httpBackend = _$httpBackend_;
      $httpBackend.whenGET(/views\/social.html/).respond('<div></div>');
      $httpBackend.whenGET(/views\/navigation.html/).respond('<div></div>');

  }));

  it('It should convert ngModel into full HTTP address notation', inject(function ($compile) {
    element = angular.element('<input social="test_network" ng-model="models.test_network"></social>');
    linker = $compile(element);
    element = linker(scope);
    scope.$apply(function(){
        element.val('test');
    });
    scope.$digest();
    expect(scope.models.test_network).toBe(social_network_conf.prefix + 'test' +
          social_network_conf.suffix);
//    expect(element.text()).toBe('this is the social directive');
  }));
});

Problem is that those lines:

scope.$apply(function(){
    element.val('test');
});

Don't actually invoke the $parser I defined.

Though of creating a controller for the directive with an API to call ngModel.$render or ngModel.$setViewValue but I have no way to access the ngModel at the directive controller without an ugly hack.

like image 344
Oleg Belousov Avatar asked Oct 30 '14 17:10

Oleg Belousov


People also ask

How do you test a Jasmine directive?

Unit Test in Jasmine You use TestComponentBuilder as shown in the lined SO question/answer. Create a test component where the directive is used in the template and then get a reference to the directive from the created test component instance.

Which of the following can be used to run unit tests in Angular?

Karma. Karma is a test runner for JavaScript. Along with Jasmine, Karma is one of the default testing tools for Angular.

How is unit testing done in Angular?

To run the test, you will only need to run the command ng test . This command will also open Chrome and run the test in watch mode, which means your test will get automatically compiled whenever you save your file. In your Angular project, you create a component with the command ng generate component doctor .


1 Answers

2 possible solutions:

First is to wrap the element inside a form, and to assign the input field and the form a name attribute, and then access the input field as following:

scope.form_name.input_name.$setViewValue('value')

Working code:

'use strict';

describe('Directive: social', function () {

  // load the directive's module
  beforeEach(module('app'));

  var element,
    social_network_conf,
    linker,
    scope,
    $compile,
    $body,
    html,
    $httpBackend;

  beforeEach(inject(function ($rootScope, _$compile_, _$httpBackend_, _social_network_conf_) {
    scope = $rootScope.$new();
      social_network_conf = _social_network_conf_;
      //Must be an object to make use of prototypical inheritence for out-side-of-isolate-scope access
      scope.models = {};
      $compile = _$compile_;
      $httpBackend = _$httpBackend_;
      $body = $('body');
      $httpBackend.whenGET(/views\/social.html/).respond('<div></div>');
      $httpBackend.whenGET(/views\/navigation.html/).respond('<div></div>');
      $body.empty();
      html = '<form name="testForm">' +
                '<input social="test_network" name="test" ng-model="models.test_network">' +
             '</form>';

  }));

  it('It should convert ngModel into full HTTP address notation', function () {

    element = angular.element(html);
    linker = $compile(element);
    element = linker(scope);

    var viewValue = 'test',
        input = element.find('input');

    scope.models.test_network = viewValue;
    scope.$digest();

    scope.testForm.test.$setViewValue(viewValue);

    scope.$digest();
    expect(scope.models.test_network).toBe(social_network_conf.prefix + input.isolateScope().social +
          social_network_conf.suffix + viewValue);

  });

Alternatively, the second one offers to dispatch an input event on the element - didn't work for me with Angular 1.3. This is demonstrated in This YouTube video.

 element.val('test');
 element.trigger('input');
like image 166
Oleg Belousov Avatar answered Sep 20 '22 17:09

Oleg Belousov