Cross-browser Testing DOM Manipulation

This project is based on James Shore: Let's Code: Test Driven Javascript lessons (Chapter 8, Testing the DOM).

The plan

  • set up the cross-browser client testing framework
  • create some basic tests for canvas API

We would like to have a production code, which can

  • test if the browser supports the canvas API
  • throw an exception if the browser doesn't support the canvas API
  • handle this exception
  • set the size of a canvas object

We would like to create the production code via test-driven method, so we need to make the appropriate tests to produce the code.

Setting up Grunt-Karma-Mocha-Expect-Jshint client testing framework

We need to go through the following steps:

  • initialize git repository
    • (make readme.md)
  • initialize package.json
    • (edit package.json)
  • install Grunt
  • install Karma
  • create karma bash script / batch file
  • initialize Karma
  • install Mocha
  • install karma-mocha plugin
  • install karma-expect plugin
  • add expect to karma.conf.js
  • install grunt-contrib-jshint
  • create basic Gruntfile.js
  • create a simple test to test the framework

There is a detailed post here about setting up the framework.

Test if the browser supports the canvas API

You can simply test if the browser supports the canvas API: you have to create a canvas object, and check the value its getContext key. If the browser doesn't support the canvas API, the getContext key doesn't exist (so the value is undefined), otherwise it has some value. You can transform it to a boolean like this:

var support = Boolean(document.createElement('canvas').getContext);

You can also use Modernizr, which is a javascript library, and detects if the browser supports HTML5 and CSS3 features. The test with Modernizr looks like this:

if (Modernizr.canvas) {  
  ...
}
else {  
  ...
}

(source: Dive into HTML5)

Ok, so we can write the test any of the upper solution. Let's choose Modernizr:

// client-test.js

(function(){
"use strict";  
describe("Test canvas support", function(){  
  it("should be true if the browser supports canvas API", function(){
    if (Modernizr.canvas) {     
      expect(myCanvas.supported()).to.be(true);
    }
    else {      
      expect(myCanvas.supported()).to.be(false);
    }
  });
});
}());

myCanvas will be an object in the production code, and myCanvas.supported() will be a method, that must return true, if canvas API is supported, else false. Let's make a chunk:

// canvas.js  
var myCanvas = {};  
(function(){
    "use strict";
    myCanvas.supported = function(){
};

Of course our test will fail, because the returned value is undefined. Let's use the other solution to detect the support of canvas API:

myCanvas.supported = function(){  
    var support = Boolean(document.createElement('canvas').getContext);
    return support;
};

(This is quite a cheat because Modernizr uses a similar approach for detection.)

Now our test runs well:

$ grunt  
Running "jshint:src" (jshint) task  
>> 3 files lint free.

Running "karmaRun" task  
[2014-08-31 22:23:29.256] [DEBUG] config - Loading config /Users/tompascall/dev/playground/dom_test/karma.conf.js
Chrome 36.0.1985 (Mac OS X 10.9.4): Executed 1 of 1 SUCCESS (1.044 secs / 0.003 secs)  
IE 8.0.0 (Windows 7): Executed 1 of 1 SUCCESS (0.03 secs / 0 secs)  
TOTAL: 2 SUCCESS

Throw an exception if the browser doesn't support the canvas API

The aim is to have a myCanvas.exceptionIfNotSupported() method, that throws an exception, if it gets a parameter false, otherwise it does nothing. According to the documentation of expect.js we can use withArgs() to create an anonymous function to call the tested function with arguments:

it("should throw exception if canvas API is not supported", function(){  
  expect(myCanvas.exceptionIfNotSupported).withArgs(false).to.throwException();
});

Let's create the myCanvas.exceptionIfNotSupported() method in the production code:

myCanvas.supportedMessage = "Canvas API is supported";  
myCanvas.notSupportedMessage = "Sorry, but canvas API is not supported by this browser";  
myCanvas.exceptionIfNotSupported = function(support){  
  if (!support) {
    throw new Error(this.notSupportedMessage);
  }
};
(We introduced two properties that are messages about supporting, and we will use them during exception handling)

Handle the exception

Now we can assemble the exception handler method, myCanvas.tryCanvasSupported() by using the former methods. The exception handler will give back a message that informs us about supporting. It would be also nice if we had a log about it, so we could see if a given browser supports the canvas API or not:

it("should get the right message about supporting", function(){  
  var myCanvasMessage = myCanvas.tryCanvasSupported();
  if (myCanvas.supported()) {
    expect(myCanvasMessage).to.be(myCanvas.supportedMessage);
  }
  else {
    expect(myCanvasMessage).to.be(myCanvas.notSupportedMessage);
  }
  dump(myCanvasMessage);
});

And create myCanvas.tryCanvasSupported():

myCanvas.tryCanvasSupported = function(){  
  try {
      myCanvas.exceptionIfNotSupported(myCanvas.supported());
      return this.supportedMessage;
  }
  catch(error) {
    return error.message;
  }    
};

If we run the tests now, we get the following:

$ grunt  
Running "jshint:src" (jshint) task  
>> 3 files lint free.

Running "karmaRun" task  
[2014-09-01 20:13:06.055] [DEBUG] config - Loading config /Users/tompascall/dev/playground/dom_test/karma.conf.js
IE 8.0.0 (Windows 7) DUMP: 'Sorry, but canvas API is not supported by this browser'  
Chrome 36.0.1985 (Mac OS X 10.9.4) DUMP: 'Canvas API is supported'  
Chrome 36.0.1985 (Mac OS X 10.9.4): Executed 3 of 3 SUCCESS (1.885 secs / 0.012 secs)  
IE 8.0.0 (Windows 7): Executed 3 of 3 SUCCESS (0.035 secs / 0.016 secs)  
TOTAL: 6 SUCCESS

Set the canvas size

We would like to create a method [`myCanvas.setCanvasSize()] that can set the size of a canvas object. In the test let's create a canvas object, than call myCanvas.setCanvasSize() with the canvas, width and height as parameters, and check the object's parallel attributes:

it("should set the size of a canvas object", function(){  
    if (myCanvas.supported()) {
        var canvasObject = document.createElement('canvas');
        var width = 200;
        var height = 100;
        myCanvas.setCanvasSize(canvasObject, width, height);
        expect(canvasObject.width).to.be(width);
        expect(canvasObject.height).to.be(height);
    }
});

And we are so happy, because all the tests pass :)

Summary

As we planned, first we set up the cross-browser testing framework using Grunt, Karma, Mocha and Expect. After setting the framework we created a test for detecting if the browser supports canvas API. Then we created a test that tested exceptions, and a test, that tested the exception handling method. Finally, we made a simple test for DOM manipulation.

You can download this projec from here.

At the end I would recommend a good tutorial video about unit testing DOM manipulation code by James Shore.



comments powered by Disqus