Search code examples
prototypejsjasmine

Testing Prototype custom events with Jasmine


I'm posting this for those that still use PrototypeJS and want to test custom events using jasmine and jasmine-prototype plugin.

We're triggering custom events based on users clicks or form submits. We wanted a simple way to test that the custom events is fired correctly.

Here is an example:

var CustomEventsDispatcher = ( function() {
   function init(){

    //if elements exists
    if($$('a.trigger_bomb')) {
        // Observe the elements
        $$(elements).each(function(element) {
            element.observe('click', function(e) {
                this.fire("bomb:explode", {})
            });
        });
    }


    return {'init' : init};
 })();

Using resources available on the web, I've created a fixture ...

 // spec/fixtures/links.html
 <a class="trigger_bomb" href="#"> Trigger Bomb </a>

... and a test suite:

  //spec/trigger_bomb_spec.js
 describe("Trigger Bombs", function() {

    beforeEach(function(){
       loadFixtures("links.html")
       CustomEventsDispatcher.init();
   })

   it("should raise the custom event", function(){
      var element=$$('a.trigger_bomb').first();
      spyOnEvent(element, 'click');
      element.click();

      expect('click').toHaveBeenTriggeredOn(element);
      expect('bomb:explode').toHaveBeenTriggeredOn(element);
   });
 }); 

The first assertion works fine, but not the second. The reason is that spyOnEvent alters the way click events are handled on element.


Solution

  • The simplest way is to mock the fire function like this:

      //spec/trigger_bomb_spec.js
     describe("Trigger Bombs", function() {
    
        beforeEach(function(){
           loadFixtures("links.html")
           CustomEventsDispatcher.init();
       })
    
       it("should raise the custom event", function(){
          var element=$$('a.trigger_bomb').first();
            spyOn(element, 'fire')
            element.click()
            expect(element.fire).toHaveBeenCalledWith('bomb:explode', {})
       });
     }); 
    

    However this solution has a side effect : the click will be executed which will cause the jasmine page result to reload continuously. You need to intercept click events and stop the default behavior with a global beforeEach hook:

    // spec/helpers/spec_helper.js
    beforeEach(function(){
    
        // stop all clicks from fixtures 
        // (but allow clicks from the user in the jasmine result page)
        document.observe('click', function(e){
            var element = e.findElement('#HTMLReporter')
            if (element == document)   {
                Event.stop(e)
            }
        })             
    })
    

    Hope this will help someone to save a few hours of debugging :). Feel free to comment / improve / propose a better alternative.