2

I'm trying to create an Angular directive to wrap Snap.svg functionality, but I'm having trouble with unit testing it. So far I have a directive that looks like this:

'use strict';

angular.module('rpApp')
  .directive('rpSvgMap', function () {
    return {
      template: '<svg id="" width="" height=""></svg>',
      restrict: 'E',
      replace: true,
      // Don't isolate scope because we want to attach rpMap to parent scope
      link: function postLink(scope, element, attrs) {
        // Set up Snap.svg wrapper
        scope.rpMap = Snap('#' + attrs.id);
      }
    };
  });

And my Karma/Jasmine tests look like this:

'use strict';

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

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

  var element,
    scope;

    beforeEach(inject(function($rootScope,$compile) {
        scope = $rootScope.$new();
        element =
      '<rp-svg-map id="anyOldID" width="800" height="400" src="../../assets/testmap.svg"></rp-svg-map>';

        element = $compile(element)(scope);
        scope.$digest();
  }));

  describe('on initialization', function() {
    it('should create an SVG element with the provided dimensions and id', function() {
      expect(element.attr('id')).toBe('anyOldID');
      expect(element.attr('height')).toBe('400');
      expect(element.attr('width')).toBe('800');
    });
    it('should provide a working Snap.svg drawing surface', function() {
      var testCircle = scope.rpMap.circle(100,150,200);
      expect(testCircle.attr(cx)).toBe(100);
    });

});

The first test passes, and the second one fails as scope.rpMap always comes back as "null".

In the browser, this works just fine -- if I attach $scope to the window in my controller, rpMap is correctly wrapped by Snap.svg and rpMap.circle() correctly draws a circle.

So far as I can tell, the testing environment is properly loading snap.svg as a dependency and I'm reading scope properly out of the directive. For instance, if I add:

scope.hasSnap = angular.isFunction(Snap);

to the directive's link function, then this test passes:

it('should tell us when Snap.svg is available', function() {
      expect(scope.hasSnap).toBe(true);
    });

Snap() is not async and changing the beforeAll/it to asynchronous mode does not help.

Any ideas what I'm doing wrong here?

stuff and things
  • 133
  • 2
  • 10
  • Ideally you should be mocking out `Snap` as you don't need to test that it works, just that your directive calls it. I'd make it into a spy and `expect(Snap).toHaveBeenCalledWith('#anyOldID')`. See http://stackoverflow.com/questions/9510148/using-jasmine-to-spy-on-a-function-without-an-object – Phil Jun 29 '15 at 00:04
  • Maybe I'm missing something about the best way to design a directive here. The eventual goal is that this directive will load an SVG, create/modify some models based on the contents, bind elements to other data in the scope, etc. If the test suite can't see any of the stuff Snap enables me to do inside the directive it's going to be hard to test. – stuff and things Jun 29 '15 at 02:13
  • You don't need to test `Snap` though. If you're testing your directive **and** `Snap`, then it isn't a unit test. – Phil Jun 29 '15 at 03:55
  • I finally wrapped my head around this. Do you want to write your first comment as an answer? (My answer below will hopefully help people having the same Snap.svg problem but yours is the correct answer to my actual testing question) – stuff and things Jun 30 '15 at 01:54

1 Answers1

1

This jsfiddle I found (sorry, can't remember the source) contains the solution http://jsfiddle.net/hRy4A/2/

Specifically, we can pass the element directly to Snap like so:

scope.rpMap = Snap(element[0]);

When rpMap is created this way, the following test passes:

it('should provide a working Snap.svg drawing surface', function() {
        var testCircle = scope.rpMap.circle(100,150,200);
        expect(testCircle.attr('cx')).toBe('100');
        expect(testCircle.attr('cy')).toBe('150');
        expect(testCircle.attr('r')).toBe('200');
    });

I guess one potential issue with this approach is that I don't necessarily need or want to have all the Snap.svg methods exposed in $scope -- I suppose ideally the graphical stuff would all be self-contained inside the directive.

Note that there was also a minor typo in my original test, it should be

expect(testCircle.attr('cx')).toBe('100');
stuff and things
  • 133
  • 2
  • 10