How can I simulate a click event in my AngularJS directive test?

I've tried following the format of the ng-directive-testing repo for a directive I've written. The directive basically renders an overlay when the user clicks on an element. Here's the directive (simplified):

mod.directive('uiCopyLinkDialog', function(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var $elm = angular.element(element);
            element.bind('click', function(event) {
                $elm.addClass('test');
            });
        }
    };
});

The test I'm writing looks like this:

describe('pre-compiled link', function () {

    beforeEach(mocks.inject(function($compile, $rootScope) {
        scope = $rootScope;
        element = angular.element('<span class="foo" ui-copy-link-dialog="url"></span>');
        $compile(element)(scope);
        scope.$digest();
    }));

    it("should change the class when clicked", function () {
        element.click(); // this returns "'undefined' is not a function"
        element[0].click(); // so does this
        $(elm).click(); // this uses jquery and doesn't *seem* to fail
        waits(500); // hack to see if it was a race condition
        expect(elm.className).toContain('test'); // always fails
    });

});

You can see in the test that I try several ways to trigger the click() event on the link, with most of them giving an undefined error.

Can anyone tell me what I'm doing wrong here? Reading the examples this sounds like it's the correct syntax but my test runner (Karma via Grunt) doesn't want to play ball.


Solution 1:

You can use triggerHandler, part of JQLite.

I used this to trigger a click event on a directive...

element = angular.element("<div myDirective-on='click'></div>");
compiled = $compile(element)($rootScope);
compiled.triggerHandler('click');

Full example available on this blog post: http://sravi-kiran.blogspot.co.nz/2013/12/TriggeringEventsInAngularJsDirectiveTests.html

Solution 2:

So this turned out to be a problem with PhantomJS: some events that act on elements don't seem to fire when the elements aren't actually on a document anywhere, but just in memory (that's my theory, anyway). To work around this I had to use this function to trigger click events on elements:

define(function () {
    return {
        click: function (el) {
            var ev = document.createEvent("MouseEvent");
            ev.initMouseEvent(
                "click",
                true /* bubble */, true /* cancelable */,
                window, null,
                0, 0, 0, 0, /* coordinates */
                false, false, false, false, /* modifier keys */
                0 /*left*/, null
            );
            el.dispatchEvent(ev);
        }
    };
});

This worked, although other things were harder: I also wanted to write a test that ensures a given form input has focus, but getting this value was almost impossible using PhantomJS since I guess the browser can't make something focused if it has no onscreen representation. Anyone needing this could have a look at CasperJS which offers a simple API for some of these requirements.