3

I am attempting to trigger a d3 event using Jasmine. In particular, I want to check that my event listener is being called using a Jasmine spy.

For example, if I attach the d3 zoom behavior to an svg element (I am using Backbone.js for my front end):

Code (a):

class MyView extends Backbone.View
  initialize: ->
    zoom = d3.behavior.zoom().on("zoom", this.zoom_listener)   
    d3.select(this.el).append("svg").attr("class", "viewport").call(zoom)

  zoom_listener: ->
    console.log("zoom called") 

The following test in Jasmine fails:

Code (b):

it "calls zoom listener on dblclick", ->
  zoom_spy = spyOn(MyView.prototype, "zoom_listener").andCallThrough()
  view = new MyView()
  view.$(".viewport").trigger("dblclick")
  waitsFor((-> zoom_spy.callCount == 1), "Call zoom", 1000) 

On the other hand, (just as a sanity check) if I binded a 'dblclick' event to my view as shown below, the above test i.e. Code (b) will pass:

Code (c):

class MyView extends Backbone.View
  events:
    "dblclick" : "zoom_listener"

  initialize: ->
    zoom = d3.behavior.zoom().on("zoom", this.zoom_listener)   
    d3.select(this.el).append("svg").attr("class", "viewport")
    # .call(zoom)     # commented off this line for the sanity check  

  zoom_listener: ->
    console.log("zoom called")  

Can anyone give me some insight as to why I can't seem to get the D3 zoom event triggered within the Jasmine test i.e. Code (b) using my original view above i.e. Code (a)?

Sunil Sandhu
  • 385
  • 4
  • 13

3 Answers3

5

Backbone triggers jQuery events, which doesn't seem to register outside of the jQuery world. Some workarounds are explained here. But here is a general way to test D3 events with Jasmine.

it('should trigger a callback on custom events', function() {
  fixture.datum(dataset)
    .call(barChart);

  var callback = jasmine.createSpy("filterCallback");
  barChart.on('customHover', callback);

  var bars = fixture.selectAll('.bar');
  bars[0][0].__onmouseover();
  var callBackArguments = callback.argsForCall[0][0];

  expect(callback).toHaveBeenCalled();
  expect(callBackArguments).toBe(dataset[0]);
});

D3 exposes events on the DOM object. So you can attach a spy and trigger it.

Community
  • 1
  • 1
Biovisualize
  • 2,455
  • 21
  • 20
0

As stated by M. Bostock in https://github.com/mbostock/d3/issues/906: "jQuery trigger doesn't dispatch real events; it only calls its own listeners."

One way to dispatch a real event using vanilla JS is (based on the reply from user "handler" in How to invoke "click" event programmatically in d3?):

Code (b-modified)

it "calls zoom listener on dblclick", ->
  zoom_spy = spyOn(MyView.prototype, "zoom_listener").andCallThrough()
  view = new MyView()

  jQuery.fn.custom_mouse_dblclick = (->
    this.each(((i, element) ->
      evt = document.createEvent("MouseEvent")
      evt.initMouseEvent(
        "dblclick",
        true,
        true,
        window,
        0,
        0,
        0,
        0,
        0,
        false,
        false,
        false,
        false,
        0,
        null)
      element.dispatchEvent(evt)
    ))
  )
  view.$(".viewport").custom_mouse_dblclick()
  waitsFor((-> zoom_spy.callCount == 1), "Call zoom", 1000) 

So the Jasmine test in Code (b-modified) passes when performed on Code (a) in the original question i.e. svg element with d3.zoom() behavior.

Community
  • 1
  • 1
Sunil Sandhu
  • 385
  • 4
  • 13
0

A variation on the example by @Biovisualize:

fixture.datum(dataset).call(barChart);

var callback = jasmine.createSpy("filterCallback");
barChart.on('customHover', callback);
//to trigger all elements    
fixture.selectAll('.bar').each(function() {
    this.__onmouseover();
});

expect(callback).toHaveBeenCalled();
  • You should expand your answer to be your own as you never know if that post by Biovisualize could get deleted for whatever reason. – ObieMD5 Jul 23 '13 at 00:42