0

I've got a directive that adds a click handler to an element:

module.directive('toggleSection', ['$timeout', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.bind('click', function (event) {
                scope.$apply(function () {
                    var scopeProp = 'show' + attrs.toggleSection;

                    event.preventDefault();
                    event.stopPropagation();

                    scope[scopeProp] = !scope[scopeProp];

                    return false;
                });

            });
        }
    };
}]);

When the element is clicked, it toggles another property on the scope, which another element is bound to with ng-show. It's working as it should in the app.

I've added the following test for the directive:

(function () {
    'use strict';

    // get the app module from Angular
    beforeEach(module('app'));

    describe('myCtrl', function () {

        var $scope, $rootScope;

        beforeEach(inject(function ($controller, _$rootScope_) {
            $scope = {};
            $controller('myCtrl', { $scope: $scope });
            $rootScope = _$rootScope_;
        }));

        describe('the toggleSection directive', function () {

            var testElement;

            beforeEach(function () {
                testElement = $compile('<a toggle-section="Test" href="#">Collapse section</a>')($rootScope);
                $rootScope.$digest();
            });

            it('inverts the value of the specified scope property', function () {
                $scope.showTest = false;
                testElement.click();

                expect($scope.showTest).toEqual(true);
            });

        });
    });

In the real code there are properties like $scope.showSection1 = false and by adding console logs in the directive I can see the properties before and after clicking the bound element and they have the expected values (e.g. the property starts as false and after you click the toggle element once it changes to true).

However, the test always fails with 'Expected false to equal true'. I think it's to do with the $apply method, because none of the show properties seem to exist on the scope when I run the test.

Other tests I have (even in the same spec file), which don't use the directive can see properties on the scope just fine.

What am I doing wrong?

danwellman
  • 9,068
  • 8
  • 60
  • 88
  • 2
    You should compile your directive in test agains scope not rootScope. Try that and let me know if it solves your issue. Also, not sure if this will help, but I create my directive by first creating angular.element:`element = angular.element('');compile(element)(scope);scope.$digest();` – Diana R Sep 14 '15 at 12:53
  • Hi Diana, I tried this (this was the only thing I changed, I left `$digest()` being called on `$rootScope`), but now the test has an error: 'undefined' is not a function (evaluating 'scope.$apply') - it's tripping up on the use of '$apply()' in the directive itself..? – danwellman Sep 14 '15 at 12:58
  • 1
    Well if you compile it against rootScope, it will be in the rootScope.$apply. That is why I am saying you have to compile it against scope, not rootScope. – Diana R Sep 14 '15 at 13:01
  • Sure, it makes sense, but when I try calling `$scope.$digest()` on the line after compiling the element, then I see a message in the console with: TypeError: 'undefined' is not a function (evaluating '$scope.$digest()') – danwellman Sep 14 '15 at 13:04
  • 1
    Ah ok, this is because your scope is not created properly. It should be created in before each not just `$scope = {}` but `$scope = $rootScope.$new();` – Diana R Sep 14 '15 at 13:08
  • Amazing, that's done it :) thanks! If you summarize these comments into an answer, I'll mark it as accepted – danwellman Sep 14 '15 at 13:11
  • :) great. Ok, will post them in a few mins. – Diana R Sep 14 '15 at 13:12
  • how does $scope.showTest matter in your test? I am not sure I understand this part. – Winnemucca Apr 12 '16 at 23:15
  • @stevek it doesn't 'matter' as such, it's just the scope property that gets toggled. the directive prepends each property with the string 'show' and the property in the test is just called 'test's so the scope property become 'showTest' – danwellman Apr 13 '16 at 09:12
  • 1
    Ok thanks for the explanation! – Winnemucca Apr 13 '16 at 11:22

1 Answers1

3

There are a few things to be changed in your test:

1 - scope creation should be changed from $scope = {} into $scope = $rootScope.$new();

2 - the directive should be compiled not into rootScope, but into scope

3 - the directive should first be created via angularjs.element and then compiled:

element = angular.element('<my-directive/>');
compile(element)(scope);
scope.$digest(); 
Diana R
  • 1,174
  • 1
  • 9
  • 23