Ian
Ian

Reputation: 34519

Triggering browser events for testing

I'm trying to write some tests for some custom event handling that I've created in D3, but I can't figure out an appropriate way to trigger these events that still works properly with D3's internals.

I won't go into the exact implementation details of the custom eventing, but will give a brief example of what I've got. So firstly there is a function that hooks up a range of events to an SVG element referred to as g. For simplicity I'll use the mousedown event to explain my problem:

var events = function(g) {
    container = g;

    // Register the raw events required
    g.on("mousedown", mousedown)
     .on("mouseenter", mouseenter)
     .on("mouseleave", mouseleave)
     .on("click", clicked)
     .on("contextmenu", contextMenu)
     .on("dblclick", doubleClicked);

    return events;
};

When the mousedown event fires this will call the following function:

function mousedown(d, i) {

    // Record the initial position of the mouse down
    windowStartPosition = getWindowPosition();
    position = getPosition();

    // Do more stuff

    // Trigger a mouse down event
    dispatch.mousedown.call(this, d, i);
}

function getWindowPosition() {
    var point = {
        x: d3.event.clientX,
        y: d3.event.clientY,
        dx: 0,
        dy: 0
    };

    if (position) {
        point.dx = windowPosition.x -point.x;
        point.dy = windowPosition.y - point.y;
    }       

    return point;
};

So I wish to test this code using Jasmine, I've setup my custom events up on an SVG circle and I now want to fire them to test my code. Note that myfunc is a Jasmine spy.

it("triggers mouse down", function () {
    events.on("mousedown", myfunc);
    // Trigger mouse down
    expect(myfunc).toHaveBeenCalled();
})

So I've currently tried 2 different ways to trigger this event on a circle, using D3 to get the event and fire it manually:

circle.on("mousedown")();

And using jQuery to select and trigger the event:

$("circle").trigger("mousedown");

Unfortunately what I find is that my getWindowPosition() call fails, and this is because d3.event is null.

I'm therefore trying to figure out a way to trigger an event that will behave as much like a real click as possible, such that D3 correctly sets up the d3.event information. Can anyone make any other suggestions on how I might trigger this event?

Upvotes: 0

Views: 1195

Answers (2)

altocumulus
altocumulus

Reputation: 21578

This sounds pretty much like a similar problem I had to fight my way through a couple of weeks ago. Your approach was the first which occured to me at that time. After doing some research I concluded it is not going to work for the following reasons:

The jQuery spec tells us about the jQuery.trigger() method:

Although .trigger() simulates an event activation, complete with a synthesized event object, it does not perfectly replicate a naturally-occurring event.

There are two drawbacks resulting from this behaviour:

  1. The d3.event will not get populated with the triggered event leaving it null as you have already mentioned.

  2. Even if you tried to set the d3.event programmatically to the synthesized event jQuery created you would just stumble into the next problem noticing that it is pretty much blank. Since the event did not result from a real mouse click there won't be any coordinates (like .clientX, .clientY) in it. Your code would therefore break on this shortcomming.

Eventually, I ended up creating my own MouseEvent:

// Initialize the event as needed.
var mouseEventInit = {
    "button": 0,
    "clientX": 0,  // maybe something like circle.getBoundingClientRect().left + 1
    "clientY": 0   // maybe something like circle.getBoundingClientRect().top + 1
    // ... fill any properties you need.
};

// create new event and...
var event = new MouseEvent("click", mouseEventInit);

If you need to support Internet Explorer you have to use the deprecated method MouseEvent.initMouseEvent() since the use of a new event object which was created via the constructor is not yet supported by IE:

var event = document.createEvent("MouseEvents");
event.initMouseEvent( /* params */ );

I used browser detection and switched to the correct way of setting up the event object.

Having created the new event object you are ready to dispatch it to the desired target by using method dispatchEvent() of interface EventTarget which is implemented by DOM's Element:

// dispatch it to the target
circle.dispatchEvent(event);

This way you end up with a fully fledged event which will also be recognized by d3 putting it to d3.event accordingly.

Upvotes: 2

Tim Lim
Tim Lim

Reputation: 118

This might be a scoping issue. Try calling the d3.event within the mousedown handler and see if the value is still null. If so, simply move the getWindowPosition function declaration to within the mousedown handler scope.

Upvotes: 0

Related Questions