Wednesday, February 2, 2011

TDD with YUI 3 - find image size with Java Script

Finding size of image using java script involves asynchronous techniques and callback functions. I'm showing how the in-browser YUI 3 test framework can be used for TDD. They are other options for in-browser test frameworks like QUnit and JsUnit, the techniques here should be equally usable.

I came across the need to find the size of image given by URL and using java script. The task was about scaling the width and the height of html elements proportionally for the needs of the UI and accordingly to the size of the image.

To find the size of image you have to first wait until the image downloaded - unless the image is in the browser's cache. Once the image downloaded and loaded into Image object in java script you can check for the size.


Let's first specify the desired java script function. Let's name it ImgSize() with the following signature:

function ImgSize(url, onSizeCalculated)


Parameter url is the url to the image to look for
Parameter onSizeCalculated is a reference to callback function to call once the image downloaded and we have calculated the size of the image. The callback function - once the size of the image is calculated - will receive two numeric arguments: the width and height - the size of the image.

For the directory structure of this project I'll suggest working directory with the following subdirectories:

ahristov@medion:~/Desktop/Projects/js/proj/play$ ls
build deps src test
I usually start with similar directory structure (well, not to mention for things like tags, branches, revisions, etc. but that's another theme :-).
The subdirectory build I use to save the output of my work. In our example you can just ignore it.

deps/yui - contains the complete YUI3 library. Again, we'll be using only the test framework from the YUI. The test framework you'll find in the following subdirectory:


ahristov@medion:~/Desktop/Projects/js/proj/play$ ls deps/yui/build/test/
assets test-debug.js test.js test-min.js
test - this directory contains the HTML test fixture for the ImgSize() function. It is a html file that hosts the java script unit tests. Also there is a gif image with the size of 137px / 38px in subdirectory test/ImgSize the size of which will be calculated from the unit tests and against this we'll be checking how the ImgSize() function works.


ahristov@medion:~/Desktop/Projects/js/proj/play$ ls test
ImgSize ImgSize.html
ahristov@medion:~/Desktop/Projects/js/proj/play$ ls test/ImgSize
137x38.gif

src - this directory holds just one java script file in which is the source code of function ImgSize()

ahristov@medion:~/Desktop/Projects/js/proj/play$ ls src
ImgSize.js
I'll show you first the content of the test fixture - the html document ImgSize.html from my test suite:


ahristov@medion:~/Desktop/Projects/js/proj/play$ cat test/ImgSize.html
<html>
<head>
<script type="text/javascript" charset="utf-8" src="../deps/yui/build/yui/yui-min.js"></script>
<script type="text/javascript" charset="utf-8" src="../src/ImgSize.js"></script>
</head>
<body>
<script type="text/javascript">

// Create new YUI instance, and populate it with the required modules
YUI().use('console', 'test', function(Y) {

Y.namespace("ImgSize.Test");

Y.ImgSize.Test.ImgSizeTestCase = new Y.Test.Case({

name: "Test image size",

test137x98 : function () {
new ImgSize('./ImgSize/137x38.gif',
function assertOnSizeCalculated(width, height) {
Y.Assert.areEqual(137, width, "Width should be same");
Y.Assert.areEqual( 38, height, "Height should be same");
});
}
});

Y.Test.Runner.add(Y.ImgSize.Test.ImgSizeTestCase);

//run the tests
Y.Test.Runner.run();

});
</script>
</body>
</html>


Into the head section I added script link to the YUI library yui-min.js and another script link to the ImgSize.js - the file where the source code of the function under test - ImgSize() - will be.

As you can see, the html markup is very basic. The only content of the body tag - just before closing it - is a script tag where the unit tests are.

YUI.use('console', 'test' - The YUI has a very powerful modular structure. The use() function fetches for us only the functionality we'll need without downloading for us complete framework . So we only include the test runner console and the test framework. That is the only we'll fetch out of the whole YUI framework whenever the browser opens the html page with the unit tests. I really like the modularity of frameworks like YUI or Closure.

Y.namespace() - this is very useful feature of the YUI library that helps you to organize your code into namespaces. We are not going into more details concerning the YUI namespaces in this example.

new Y.Test.Case() creates an instance of test case. The test case will be named "Test image size". After the name of the test case we define one or more unit tests - functions as named properties.

test137x98 - this is the only test in this test case - we ask the ImgSize() function to calculate the size of the image 137x38.gif giving as a callback function assertion function. Whenever the image downloaded and the size of the image calculated, the assertion function will be invoked and will run as YUI test.

You probably will recognize many similarities between YUI test and frameworks like nUnit of Junit. You can for example also specify "setUp" or "tearDown" functions. Besides this the YUI test framework also gives you mocking, asynchronous tests, etc. It is a very nice library.

In case we were testing DOM manipulations and more UI stuff, then we would add HTML markup for that. In this case we are dealing with pure java script function and the abilities of the Image java script object.

The YUI test won't be so good in case we want to automate and run our tests frequently on rich set of browsers. In such cases we would be using automated testing frameworks like jsTestDriver for example.

Finally here is the source code of the ImgSize() function:

ahristov@medion:~/Desktop/Projects/js/proj/play$ cat src/ImgSize.js
/**
* Calculate the size of image.
* @param {string} url Url of the image which size to find
* @param {function(number, number)} onSizeCalculated Callback function to run once
* the size of the image calculated
*/
function ImgSize(url, onSizeCalculated) {

// Once the image is loaded, the calls the callback function onSizeCalculated
function setDimensions() {
// scope changed: this = Image object
if (onSizeCalculated) {
onSizeCalculated(this.width, this.height);
}
}

// Initialization:
// - sets callback on image loaded - setDimensions;
// - loads the image.
function init() {
var img = new Image();
img.name = url;
img.onload = setDimensions;
img.src = url;
}

// Run initialization
init();
};
The first action that happens inside ImgSize() is into the Init() function. We create object of type Image and before the assignment to property src we make sure we have assigned Image.onload event handler. Once the image is loaded, we check if onSizeCalculated callback was sent and call the callback function - see function setDimensions(). In our case is will be unit test assertion.

We just have to open the html test fixture file into our web browser. Here's how the test report looks in Chrome's Developer Tools. You should watch the results of the unit test execution into the console window:





More information for the YUI test framework you'll find here:

http://developer.yahoo.com/yui/3/test/

The YUI Test is part of the Yahoo! User Interface Library and I'll be using version 3 of the YUI. To download the YUI 3 go to the following URL:

http://developer.yahoo.com/yui/3/


Atanas Hristov

kick it on DotNetKicks.com
Shout it

4 comments:

  1. Hi it's failed, cause you will always get a pass!! is there any solution?

    ReplyDelete
  2. It is very nice post.It is good post. It is simple and best. It helpful for future use.You can test your knowledge in html by attending HTML Quiz .

    ReplyDelete