Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

isolateScope() returns undefined when using templateUrl

I have a directive that I want to unittest, but I'm running into the issue that I can't access my isolated scope. Here's the directive:

<my-directive></my-directive>

And the code behind it:

angular.module('demoApp.directives').directive('myDirective', function($log) {
    return {
        restrict: 'E',
        templateUrl: 'views/directives/my-directive.html',
        scope: {},
        link: function($scope, iElement, iAttrs) {
            $scope.save = function() {
                $log.log('Save data');
            };

        }

    };
});

And here's my unittest:

describe('Directive: myDirective', function() {
    var $compile, $scope, $log;

    beforeEach(function() {
        // Load template using a Karma preprocessor (http://tylerhenkel.com/how-to-test-directives-that-use-templateurl/)
        module('views/directives/my-directive.html');
        module('demoApp.directives');
        inject(function(_$compile_, _$rootScope_, _$log_) {
            $compile = _$compile_;
            $scope = _$rootScope_.$new();
            $log = _$log_;
            spyOn($log, 'log');
        });
    });

    it('should work', function() {
        var el = $compile('<my-directive></my-directive>')($scope);
        console.log('Isolated scope:', el.isolateScope());
        el.isolateScope().save();
        expect($log.log).toHaveBeenCalled();
    });
});

But when I print out the isolated scope, it results in undefined. What really confuses me though, if instead of the templateUrl I simply use template in my directive, then everything works: isolateScope() has a completely scope object as its return value and everything is great. Yet, somehow, when using templateUrl it breaks. Is this a bug in ng-mocks or in the Karma preprocessor?

Thanks in advance.

like image 910
J.P. ten Berge Avatar asked Feb 05 '14 13:02

J.P. ten Berge


4 Answers

I had the same problem. It seems that when calling $compile(element)($scope) in conjunction with using a templateUrl, the digest cycle isn't automatically started. So, you need to set it off manually:

it('should work', function() {
    var el = $compile('<my-directive></my-directive>')($scope);
    $scope.$digest();    // Ensure changes are propagated
    console.log('Isolated scope:', el.isolateScope());
    el.isolateScope().save();
    expect($log.log).toHaveBeenCalled();
});

I'm not sure why the $compile function doesn't do this for you, but it must be some idiosyncracy with the way that templateUrl works, as you don't need to make the call to $scope.$digest() if you use an inline template.

like image 53
Tom Spencer Avatar answered Nov 06 '22 21:11

Tom Spencer


With Angularjs 1.3, if you disable debugInfoEnabled in the app config:

$compileProvider.debugInfoEnabled(false);

isolateScope() returns undefined also!

like image 38
asicfr Avatar answered Nov 06 '22 21:11

asicfr


I had to mock and flush the $httpBackend before isolateScope() became defined. Note that $scope.$digest() made no difference.

Directive:

app.directive('deliverableList', function () {
    return {
        templateUrl: 'app/directives/deliverable-list-directive.tpl.html',
        controller: 'deliverableListDirectiveController',
        restrict = 'E',
        scope = {
            deliverables: '=',
            label: '@'
        }
    }
})

test:

it('should be defined', inject(function ($rootScope, $compile, $httpBackend) {
    var scope = $rootScope.$new();

    $httpBackend.expectGET('app/directives/deliverable-list-directive.tpl.html').respond();

    var $element = $compile('<deliverable-list label="test" deliverables="[{id: 123}]"></deliverable-list>')(scope);

    $httpBackend.flush();
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();

    expect($element).toBeDefined();
    expect($element.controller).toBeDefined();

    scope = $element.isolateScope();
    expect(scope).toBeDefined();
    expect(scope.label).toEqual('test');
    expect(scope.deliverables instanceof Array).toEqual(true);
    expect(scope.deliverables.length).toEqual(1);
    expect(scope.deliverables[0]).toEqual({id: 123});
}));

I'm using Angular 1.3.

like image 29
Jeff Fairley Avatar answered Nov 06 '22 19:11

Jeff Fairley


You could configure karma-ng-html2js-preprocessor plugin. It will convert the HTML templates into a javascript string and put it into Angular's $templateCache service.

After set a moduleName in the configuration you can declare the module in your tests and then all your production templates will available without need to mock them with $httpBackend everywhere.

beforeEach(module('partials'));

You can find how to setup the plugin here: http://untangled.io/how-to-unit-test-a-directive-with-templateurl/

like image 22
leocborges Avatar answered Nov 06 '22 20:11

leocborges