jasmine canvas directive test without ‘karma-ng-html2js-preprocessor’
webdev, angular, jasmine
October 20th 2015 (4 years ago)
In one of my current projects, i was responsible for writing tests to a directive, that uses canvas, to create and manage a pie chart (I don’t need to actually care about how the canvas is rendered, i am rerponsible to check if the values and algorithms on directive are done properly, this part is already tested, but the subject is large and maybe deserves it’s own post someday :) ). The project itself looks great, but the directive is written a little chaotically. I don’t have access to main test environment, hence i cannot add and use karma-ng-html2js-preprocessor, additionally, my directive is hidden (forced) when the application starts. There is a solution to that, so no worries if you have a similar problem.

The problem

First, if we do not use karma-ng-html2js-preprocessor, we need to deliver the template of the directive to do testing. Second, we need to properly initiate the directive, even when its not visible.

First, the part of the directive

Will not post the whole body of directive, because it’s not the issue here
(function () {
    'use strict';
    angular
        .module('mainApp.chartDirectives')
        .constant('PRODUCTION_CHART', {
            'TEMPLATE': 'partials/PieChartTemplate.html',
        })
        .directive("PieChart", PieChart);

    PieChart.$inject = ['$window', 'PRODUCTION_CHART'];


    function PieChart($window, PRODUCTION_CHART) {
        var directive =  {
            restrict: 'AE',
            replace: true,
            transclude: true,
            scope: {
                value: "=",
                maxvalue: "=",
                unit: "="
            },
            templateUrl: function(){
                return PRODUCTION_CHART.TEMPLATE;
            },
            link: link
        };

        return directive;

        function link(scope, element, attr) {
            var canvas = element[0],
                context = canvas.getContext('2d');

            scope.$watch('value', function (valueArg) {
                scope.value = valueArg;
                render();
            });
          }):                

        ...
        ...
        ...

second, the directive template

  <canvas ng-transclude="" value="0" maxvalue="60" unit="l/h">
  </canvas>

Third, the test

'use strict';
describe('dlProductionChart directive tests', function () {
    var element,
        scope,
        $log,
        canvas = {},
        PRODUCTION_CHART,
        DIRECTIVES_CONST;

    beforeEach(function () {

        module('vmsFarm');
        module('vmsFarm.dlDirectives');

        inject(function($templateCache, $rootScope, $compile, _PRODUCTION_CHART_, _$log_){
            scope = $rootScope;
            $templateCache.put('partials/dlProductionChartTemplate.html','<canvas ng-transclude="" value="0" maxvalue="60" unit="l/h"></canvas>');

            $log = _$log_;
            PRODUCTION_CHART = _PRODUCTION_CHART_;

            $log.reset(); // to avoid that log.info[] in angular-mocks to be undefined

            element = angular.element('<dl-production-chart></dl-production-chart>');
            element = $compile(element)(scope);
            document.body.appendChild(element[0]);
            scope.$digest();
        });
    });

    fdescribe('test the functionality of filters', function () {
        it('should check value watcher',function (){
            scope.value = 4;
            scope.$digest();
            expect(element.scope().value).toBe(4);
        });
    });
});

Fourth the explanation

Ok, first, to deliver the template for the directive, we need to use the $templateCache.put – this will store the template of the directive (it’s a bug minus over the karma-ng-html2js-preprocessor, that you actually need to add each directive you want to test manually, but hey, it gets the job done :) )

After that is done, we can focus on making the element rendered into the DOM structure, for us to use – we create element, then commpile it to the scope and additionally add this to DOM structure, last step is to launch a $digest on our scope to initialize the process.

Those 4 lines are responsible for doing just that:
  element = angular.element('<dl-production-chart></dl-production-chart>');
  element = $compile(element)(scope);
  document.body.appendChild(element[0]);
  scope.$digest();
And there you have it. properly initialized directive with html canvas inside without using karma-ng-html2js-preprocessor.

P.s. Just a side note, i recommend using karma-ng-html2js-preprocessor – it’s easier, quicker and hassle-free, this is just an example of solution, when you cannot use karma-ng-html2js-preprocessor for any reason.