QA in EcmaScript

aka QA in JavaScript

Trygve Lie

Language for amateurs!

Wrong!

But; JavaScript has a history of:

Some facts:

Based on stats from misc top ten web sites in November 2009 - Not official numbers.

Some examples:

JavaScript is object oriented


var Music = function(){
    // Private method
    var isImportant = function(){

    };
    // Privileged method
    this.listen = function(){

    };
};
// Public method
Music.prototype.create = function(){

};

// Instantiate a object
var Rock = new Music();
Rock.create();
Rock.listen();

Some examples:

JavaScript has closures


var travel = function(to){
    var city = to;
    function whereToGo(){
        city = 'Berlin';
        return city;
    }
    return whereToGo;
};

Some examples:

JavaScript has inheritance by prototyping


var Music = function() {
    // Set something
};
Music.prototype.getGender = function() {
    // Return gender
};

var GarageRock = function() {
    // Set something different
};
GarageRock.prototype = new Music();
GarageRock.prototype.getArtists = function() {
    // Return artists
};

Some examples:

JavaScript can have classical inheritance


var Music = function(){
    // Set something
};
Music.prototype.getGender = function(){
    // Return gender
};

var GarageRock = function(){
    Music.superclass.constructor.call();
};
inheritance.extend(GarageRock,Music);
GarageRock.prototype.getArtists = function(){
    // Return artists
};

Some examples:

JavaScript can have interfaces


var archive = new Interface('archive',['getGender','setGender']);

var Music = function(){
    interface.implements(this,archive);
};
Music.prototype.getGender = function(){/* Do something... */};
Music.prototype.setGender = function(){/* Do something... */};

JavaScript is not broken

Basic functions are quite consistent

They work pretty much the same among browsers

JavaScript is not broken

Some methods are not implemented in all browsers

Those can be added gracefully


if (!Array.prototype.indexOf){
  Array.prototype.indexOf = function(elt /*, from*/){
    var len = this.length >>> 0;
    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++){
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}    

Example: IE is missing the indexOf method on the Array object

JavaScript is not broken

What people need help with:

Dustin Diaz's: Top ten custom JavaScript functions of all time

DOM implementations is broken

How will browsers display this invalid HTML?


<html>
<head>
    <style type="text/css">
    p i{color: green;}
    i p{color: red;}
    </style>
</head>
<body>
    <div><i><p>I'm wrong markup, but I should be red</p></div>
    <div><i><p>I'm wrong markup, but I should be red</p></i></div>
    <div><i><p>I'm wrong markup, but I should be red</i></p></div>
    <div><p><i>I'm correct markup and I should be green</i></p></div>
</body>
</html>

DOM implementations is broken

Different! Blame difference in DOM implementations

DOM difference

People also need help with

If you do not want to deal with these differences:
Consider a framework!

JavaScript rocks!!!

In other words; we can build solid and cool applications

Live Comet application

But; Now what?

What "real" programers know


// Helpers (can be refactored to public utility class) -----------------------------
/**
 * Returns true if the given accept header accepts the given value.
 * @param acceptHeader The accept header.
 * @param toAccept The value to be accepted.
 * @return True if the given accept header accepts the given value.
 */
private static boolean accepts(String acceptHeader, String toAccept) {
    String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*");
    Arrays.sort(acceptValues);
    return Arrays.binarySearch(acceptValues, toAccept) > -1
        || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1
        || Arrays.binarySearch(acceptValues, "*/*") > -1;
}

The challenge

JavaScript mostly goes over the wire

The challenge

...so; JavaScript looks like this:


(function(){var j=this,n=/^(on)?load/,b=/^on/,i="addEventListener",f="attachEvent",
e="_oc",h="detachEvent",g="removeEventListener",l=j[i],m=j[g],p=j[f],a=j[h],k={},
d=0;function c(r,s,q){if(!r||!s){return;}if(n.test(r)){if(!s[e])
{var t=++d;s[e]=t;k[t]=s;}}else{if(p&&b.test(r)){p(r,s);}else{if(l){l(r,s,q);}}}}
function o(r,s,q){if(!r||!s){return;}if(n.test(r)){var t=s[e];if(t){delete k[t];}}
else{if(a&&b.test(r)){a(r,s);}else{if(m){m(r,s,q);}}}}j.OnloadCache={
enable:function(){if(l){j[i]=c;j[g]=o;}if(p){j[f]=c;j[h]=o;}},disable:function(){
if(l){j[i]=l;j[g]=m;}if(p){j[f]=p;j[h]=a;}},dispatch:function(){var r={type:"load"},
q;for(q in k){if(k.hasOwnProperty(q)){k[q](r);}}},reset:function(){k={};}};})();

From yahoo.com frontpage

Not so easy to deal with...

Order and packaging

JAWR

Structure with JAWR

Order and packaging

JAWR does:

Not without danger

Do you spot a potential bug?


var someCheck = function(m,n){
    if(m > n){
        return m + ++n;
    }else if(m < n){
        return ++m + n;
    }else{
        return m + n;
    }
};

Not without danger

Do you spot a potential bug?


var someCheck = function(m,n){
    if(m > n){
        return m + ++n;
    }else if(m < n){
        return ++m + n;
    }else{
        return m + n;
    }
};

Post minification


var someCheck = function(m,n){
    if(m>n){return m+++n;}
    else if(m<n){return ++m+n;}
    else{return m+n;}
};

Not without danger

Do you spot a potential bug?


var someCheck = function(m,n){
    if(m > n){
        return m + ++n;     // someCheck(5,4) = 10
    }else if(m < n){
        return ++m + n;     // someCheck(4,5) = 10
    }else{
        return m + n;       // someCheck(5,5) = 10
    }
};

JavaScript interprets m+++n as m++ + n


var someCheck = function(m,n){
    if(m>n){return m+++n;}      // someCheck(5,4) = 9
    else if(m<n){return ++m+n;} // someCheck(4,5) = 10
    else{return m+n;}           // someCheck(5,5) = 10
};

Not without danger

Do you spot a potential bug?


var someCheck = function(m,n){
    if(m > n){
        return m + ++n;     // someCheck(5,4) = 10
    }else if(m < n){
        return ++m + n;     // someCheck(4,5) = 10
    }else{
        return m + n;       // someCheck(5,5) = 10
    }
};

JavaScript interprets m+++n as m++ + n


var someCheck = function(m,n){
    if(m>n){return m+++n;}      // someCheck(5,4) = 9
    else if(m<n){return ++m+n;} // someCheck(4,5) = 10
    else{return m+n;}           // someCheck(5,5) = 10
};

Small change, big impact


var someCheck = function(m,n){
    if(m>n){return m+(++n);}      // someCheck(5,4) = 10
    else if(m<n){return (++m)+n;} // someCheck(4,5) = 10
    else{return m+n;}             // someCheck(5,5) = 10
};

Not without danger

Do you spot a bug here then?


var urlEncode = function(j){
    var l = [];
    if(!(typeof(j) == 'undefined')){
        for(var i = 0; i < j.length; i++){
            l.push(escape(j[i].innerHTML));
        }
        while(i <= l.length) {
            j[i].innerHTML = l[i];
            i++;
        }
    }
};

Not without danger

Do you spot a bug here then?


var urlEncode = function(j){
    var l = [];
    if(!(typeof(j) == 'undefined')){
        for(var i = 0; i < j.length; i++){
            l.push(escape(j[i].innerHTML));
        }
        while(i <= l.length) {
            j[i].innerHTML = l[i];
            i++;
        }
    }
};

Due to the variable scope, i is already at l.length


var urlEncode = function(j){
    var l = [];
    if(!(typeof(j) == 'undefined')){
        for(var i = 0; i < j.length; i++){
            l.push(escape(j[i].innerHTML));
        }
        var i = 0;
        while(i <= l.length) {
            j[i].innerHTML = l[i];
            i++;
        }
    }
};

Known tools does not catch this

Dragonfly and FireBug

Opera Dragonfly

Firebug

Fun fact: both Dragonfly and FireBug are written in JavaScript

Introducing linting - JSLint

Unit testing - JSUnit


// Set up a object to test on
function PersonTest(name){TestCase.call(this,name);}
function setUp(){person = new Person();}

// Set up a unit test
function testFirstName(){this.assertEquals("Bobba", person.getFirstName());}
function testLastName(){this.assertEquals("Fet", person.getLastName());}

// Setup and load the object into the unit test
PersonTest.prototype = new TestCase();
PersonTest.prototype.setUp = setUp;

// Run the tests
PersonTest.prototype.testGetFirstName = testFirstName;
PersonTest.prototype.testGetLastName = testLastName;

Documentation - JSDoc


/**
 * @constructor
 * A person object
 */
var Person = function (){// Something }

/**
 * Setter method for the name of the person
 * @param {string} name Sets the name
 */
Person.prototype.setName = function(name){// Set something };

/**
 * Getter method for the name of the person
 * @returns {string} Name of the person
 */
Person.prototype.getName = function(){// return Something};

Documentation - JSDoc

Did you see a pattern?

Melbournes Digital Harbour Port 1010

Photo: Melbourne's Digital Harbour Port 1010

Did you see a pattern?

What real programers know

Now we have the same tools for JavaScript

The cool part!!!!

Did you notice another pattern?

Now JavaScript can live among real languages!

Demo time!

All fine down in the lab

But what about in the wild?

The RAND home computer

RAND Corporation's 1954 model of how a home computer would look like in 2004. Where is my boat wheel???

Browsers are different

Difference in JavaScript engines performance

Browsers javascript speed with IE9

From: MS IE Blog - An Early Look At IE9 for Developers - 2009.11.18

Different errors will happen in the wild

Logging in the browser

Log4JS / log4javascript

Based on known logging principle


// Create the logger
var log = log4javascript.getLogger();

// Create a appender with default options
var ajaxAppender = new log4javascript.AjaxAppender(URL);

// Add the appender to the logger
log.addAppender(ajaxAppender);

In our code:


try {
    throw new Error("Dohh...");
} catch (error) {
    log.error("Something went dohh...", error);
}

Logging in the browser

Calling home

Logging in the browser

Tracing errors

Thank you!

Trygve - @trygve_lie