I had to dig into a production issue the other day that presented itself like this:
There was a piece of javascript code that iterated over some dom elements, gathered ids into 2 arrays, ran a validation check, and then flattened the arrays to add them to the url.
On firefox, opera, and chrome this was working correctly, and had been tested by the developers, but on IE 7 it isn’t working, and the problem wasn’t detected until it made it out to production. (Which raises a point about testing in IE during QA, which I’ll get to in a sec).
I started my investigation doing what I’d call “poor man’s debugging”, putting some alerts in, breaking up the code a bit to make it easier to inspect, etc. Normally I don’t do this when testing in Firefox, because I have Firebug, but in IE I always seem to start with the basics to get my bearings, then quickly move to a more pragmatic approach.
Then I started using firebug-lite, confirming that I can see the info I need, and I reverted my changes to the code so that I could verify that I hadn’t introduced any issues.
After sifting through the code, checking through the prototype library to make sure that it was being used correctly, I narrowed the problem down to a single line, which I could test from the address bar of either browser (when on a page with prototype.js loaded)
javascript:alert([[,,,'foo'],[,'bar',,]].flatten().length == 2)
For me that will fail in IE 7, but pass in everything else.
I then wrote a test using jqUnit, the javascript testing library from jQuery, so that I could get into that rapid process of test => fail => fix => pass, without having to screw around with retyping or copying code, or forgetting what I’ve tried.
test('prototype flatten array test', function(){
ok([[,,,'foo'],[,'bar',,]].flatten().length == 2, 'the flattened array should be 2');
});
Now if I load the page with that test on it, in Safari, Firefox, Chrome, or Opera, the test passes, but if I load it in IE 7, it fails. I now have an environment where I can dig in and make changes, and quickly confirm that I didn’t break anything in the other browsers.
After a bit of investigating I conclude that the issue is within the prototype.js library itself, behaving differently between browsers for this function:
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(Object.isArray(value) ?
value.flatten() : [value]);
});
}
I know that I have undefined or nil elements in my array, so I write a quick test to see how Object.isArray handles that. Turns out that it behaves the same way in all the browsers, but interestingly, it will return either True, False, or Undefined, which means that those undefined values will get added, because the flatten method above is just looking for ‘true’.
At this point I feel like writing a version of the method that is a bit more explicit, just to make sure that I’m on the right track. (I throw this in my test copy of prototype.js)
//explicitly checking for true and false, because isArray returns undefined
myflatten: function() {
return this.inject([], function(array, value) {
var _isArray = Object.isArray(value)
if (_isArray == true) {
return array.concat(value.myflatten()); //must call myflatten
} else if (_isArray == false) {
return array.concat([value]);
} else {
return array;
}
});
}
I create another test, and see that it passes in all browsers, but I’m not really interested in going to production with a patched version of prototype, and at this point I’ve already spent enough time on this bug, so I go back to my original problem code and find another way to remove the undefined elements of the array, and get the code pushed out to prod.
After things calm down, and we’ve gotten the outstanding bugs taken care of, I wanted to take another look at the problem, because while I feel that I’ve figured out a way to stop the problem, as I still don’t understand why its happening. After some googling, I don’t really see a clear indication that other people have run into this, but I take a look at versions newer than what I’m running in prod (1.6.0.1), and I see that the isArray method was recently changed to:
function isArray(object) {
return getClass(object) === "Array";
}
Which we can see will result in a strict true/false response, and when I reran my tests against that version, they passed, so I figure I’m done with it and we’ll just upgrade to the latest version when we can.
But something keeps nagging at me, as it seems that there’s more going on here than just the isArray method, so I dive into the code again and see that the function that is passed .inject is called more times in IE than in the other browsers, and that the code in .inject is pretty simple and just relies on .each.
I write a test to explore how .each will iterate over undefined elements in all the browsers.
//shows that .each will iterate over empty array elements in IE
test('iterator test', function() {
var hitCount = 0;
[,,,'foo'].each(function(value){hitCount++;})
equals(hitCount, 1, 'should be 1');
});
And I see that IE iterates over the array 4 times, whereas opera, safari and chrome only iterate 1 time. Interestingly my latest copy of Firefox is behaving the same as IE, and I swear it wasn’t doing this with the previous version I had when I was originally testing this a month or so ago.
Conclusion:
Be careful in any assumptions you make about the contents of your arrays, and their iteration, in case they contain nils, or ‘undefined’ elements you weren’t expecting.
The other point I’d make is about the general practice of javascript debugging, and testing. If you get comfortable with firebug, and firebug-lite on IE/opera, you can save yourself a lot of time and narrow down to the issue pretty quickly. Having even a basic test suite can go a long way in ensuring that your javascript code behaves the way you expect in all of the browsers. Couple that with some selenium smoke tests, and you’ll find more bugs faster, and save a lot of wasted effort in your QA process.
Related Services: Ajax Rich Internet Applications, Custom Software Development

This is some good advice John, let me offer some of my own.
Instead of po’man debugging with alerts, try blackbird js (http://www.gscottolson.com/blackbirdjs/) and log your tests. This is not unlike Firebug/Firebug lite except that its meant to be persistent in your code so that you don’t loose the knowledge you’ve gained adding your alerts.
@Adam, Thanks for the tip on blackbird js. Looks like another good one to add to the tool-belt!
[...] Javascript debugging and testing in the wild (Prototype bug when using array.flatten in IE) [...]