Anders Tornblad, web developer

I'm all about the web

Label archive for javascript

JavaScript Csv file generator, part 2

Last week, I set up a few requirements and unit tests for my CSV file generator. Now it is time to start implementing.

First the constructor and the add method:

function Csv(propertyOrder) { this.propertyOrder = propertyOrder; this.items = []; } Csv.prototype.add = function(item) { this.items.push(item); };

There. The first three unit tests are already passing. Next up is the first version of the getFileContents method.

Csv.prototype.getFileContents = function(separator, includePropertyNames) { var textLines = []; // Add the auto-generated header if (includePropertyNames) { textLines.push(this.propertyOrder.join(separator)); } // We step through every item in the items array this.items.forEach(function(item) { // We create one line of text using the propertyOrder // First create an array of text representations var values = []; this.propertyOrder.forEach(function(name) { values.push(item[name].toString()); }); // Then join the fields together var lineOfText = values.join(separator); textLines.push(lineOfText); }).bind(this); // Return the complete file return textLines.join("\r\n"); };

The only thing left to do now is the saveAs method. Since this requires a functioning window.saveAs implementation to be available, the method is really simple.

Csv.prototype.saveAs = function(filename, separator, includePropertyNames) { var fileContents = this.getFileContents(separator, includePropertyNames); // Create a blob, adding the Unicode BOM to the beginning of the file var fileAsBlob = new Blob(["\ufeff", fileContents], {type:'text/csv'}); window.saveAs(fileAsBlob, filename); };

There! It's done! The only thing left to do is some extra text escaping, but I leave that for next part. EDIT: The finished code can be found on github at /lbrtw/csv.

JavaScript Csv file generator, part 1
JavaScript Csv file generator, part 2 (this part)
JavaScript Csv file generator, part 3

JavaScript Csv file generator, part 1

I came across the need to generate CSV files locally using JavaScript, and set out to create a simple tool for that. It should be small, simple and should just get the job done.

I would like to be able to use the CSV generator something like this:

var propertyOrder = ["name", "age", "height"]; var csv = new Csv(propertyOrder); csv.add({ name : "Anders", age : 38, height : "178cm" }); csv.add({ name : "John Doe", age : 50, height : "184cm" }); csv.saveAs("people.csv");

First things first, so let's start with some unit tests. I use the unit test framework that I have covered in earler posts.

Requirements

  1. The only parameter for the constructor is the order of the properties. This order should be saved.
  2. The add method should add one item to the list of items to export.
  3. For this purpose, the Csv object should contain an items property, containing all added items.
  4. The saveAs method should use the window.saveAs function, requiring a FileSaver shim to be in place.
  5. Mostly for testing purposes, the text content of the file to be generated should be accessible through the getFileContents method.
  6. When calling saveAs or getFileContents, I should be able to specify which field separator to use. The default should be a comma.
  7. When calling saveAs or getFileContents, I should be able to have the property names added automatically as a header row. The default should be not to.
  8. For interoperability purposes, the saved file should contain the correct Byte Order Mark.
engine.add("Csv constructor should save properties order", function(testContext, the) { // Arrange var properties = ["a", "b"]; // Act var csv = new Csv(properties); // Assert the("propertyOrder").propertyOf(csv).shouldBeSameArrayAs(properties); }); engine.add("Csv constructor should initiate the items property", function(testContext, the) { // Arrange var properties = ["a", "b"]; // Act var csv = new Csv(properties); // Assert the("items").propertyOf(csv).shouldBeArray(); the("length").propertyOf(csv.items).shouldBeExactly(0); }); engine.add("Csv.add should add one item", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); // Act csv.add({"a" : 1, "b" : 2}); // Assert the("length").propertyOf(csv.items).shouldBeExactly(1); the("a").propertyOf(csv.items[0]).shouldBeExactly(1); the("b").propertyOf(csv.items[0]).shouldBeExactly(2); }); engine.add("Csv.getFileContents should create file correctly", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); csv.add({"a" : "Abc", "b" : "Def"}); csv.add({"a" : "Ghi", "b" : "Jkl"}); // Act var file = csv.getFileContents(); // Assert the(file).shouldBeSameAs("Abc,Def\r\nGhi,Jkl"); }); engine.add("Csv.getFileContents(separator) should create file correctly", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); csv.add({"a" : "Abc", "b" : "Def"}); csv.add({"a" : "Ghi", "b" : "Jkl"}); // Act var file = csv.getFileContents(";"); // Assert the(file).shouldBeSameAs("Abc;Def\r\nGhi;Jkl"); }); engine.add("Csv.getFileContents(separator, true) should create file correctly", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); csv.add({"a" : "Abc", "b" : "Def"}); csv.add({"a" : "Ghi", "b" : "Jkl"}); // Act var file = csv.getFileContents(";", true); // Assert the(file).shouldBeSameAs("a;b\r\nAbc;Def\r\nGhi;Jkl"); }); engine.add("Csv.saveAs should call window.saveAs correctly", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); csv.add({"a" : "Abc", "b" : "Def"}); csv.add({"a" : "Ghi", "b" : "Jkl"}); var saveAsCalled = false, savedBlob, savedFilename; var mockSaveAs = function(blob, filename) { saveAsCalled = true; savedBlob = blob; savedFilename = filename; }; var oldSaveAs = window.saveAs; window.saveAs = mockSaveAs; // Act csv.saveAs("output.csv", ";", false); // Cleanup window.saveAs = oldSaveAs; // Assert the(saveAsCalled).shouldBeTrue(); the(savedBlob).shouldBeInstanceOf(window.Blob); the(savedFilename).shouldBeSameAs("output.csv"); }); engine.add("Csv.saveAs should add UTF-8 Byte Order Mark to beginning of file", function(testContext, the) { // Arrange var properties = ["a", "b"]; var csv = new Csv(properties); csv.add({"a" : "Abc", "b" : "Def"}); csv.add({"a" : "Ghi", "b" : "Jkl"}); // Mock saveAs function to store the blob that was created var savedBlob; var mockSaveAs = function(blob, filename) { savedBlob = blob; }; var oldSaveAs = window.saveAs; window.saveAs = mockSaveAs; // Act csv.saveAs("output.csv", ";", false); // Cleanup window.saveAs = oldSaveAs; // Assert (reading from a Blob is done using FileReader, which is asynchronous) var bom; testContext.actAndWait(1000, function(testContext, the) { var firstThreeBytes = savedBlob.slice(0, 3); var reader = new window.FileReader(); reader.addEventListener("loadend", function() { bom = reader.result; testContext.actDone(); }); reader.readAsArrayBuffer(firstThreeBytes); }).thenAssert(function(testContext, the) { the(bom).shouldBeSameArrayAs([0xef, 0xbb, 0xbf]); }); });

The next episode will be about implementing Csv. EDIT: The finished code can be found on github at /lbrtw/csv.

JavaScript Csv file generator, part 1 (this part)
JavaScript Csv file generator, part 2
JavaScript Csv file generator, part 3

JavaScript unit test framework, part 4

This is the last part of the first subject – my JavaScript unit test framework. The last thing I'm adding are just some extra bells and whistles:

  • Pluggable output system, using custom events

New requirements

  1. Events should be triggered on the window object at key points in time
    • When run() is called, the lbrtwUtEngineStarting event should be triggered
    • Before running one unit test, the lbrtwUtTestStarting event should be triggered
    • After running one unit test, the lbrtwUtTestEnded event should be triggered
    • After running the last unit test, the lbrtwUtEngineEnded event should be triggered
    • The lbrtwUtEngineStarting event object should contain the testCount property
    • The lbrtwUtTestStarting event object should contain the testName and testIndex properties
    • The lbrtwUtTestEnded event object should contain the testName, testIndex, testSuccess, testErrorMessage, testCount, successCount and failureCount properties
    • The lbrtwEngineEnded event object should contain the testCount, successCount, failureCount, testNames and failures properties

Tenth requirement

When testing for events being thrown to the window, it is important to clean up after oneself by removing all event listeners. These are the tests:

engine.add("Calling run() should trigger the lbrtwUtEngineStarting event once", function(testContext, the) { // Arrange var eng = new ut.Engine(); var triggered = 0; var onStarting = function(event) { window.removeEventListener("lbrtwUtEngineStarting", onStarting, false); triggered++; }; window.addEventListener("lbrtwUtEngineStarting", onStarting, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(triggered).shouldBeExactly(1); }) });   engine.add("The lbrtwUtEngineStarting event object should contain testCount", function(testContext, the) { // Arrange var eng = new ut.Engine(); eng.add("a", function() {}); eng.add("b", function() {}); var testCount; var onStarting = function(event) { window.removeEventListener("lbrtwUtEngineStarting", onStarting, false); testCount = event.testCount; }; window.addEventListener("lbrtwUtEngineStarting", onStarting, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(testCount).shouldBeExactly(2); }) });   engine.add("lbrtwUtTestStarting event should be triggered once per unit test", function(testContext, the) { // Arrange var eng = new ut.Engine(); var triggered = {}; var triggeredByIndex = []; eng.add("a", function() {}); eng.add("b", function() {}); var onTestStarting = function(event) { triggered[event.testName] = (triggered[event.testName] || 0) + 1; triggeredByIndex[event.testIndex] = (triggeredByIndex[event.testIndex] || 0) + 1; }; window.addEventListener("lbrtwUtTestStarting", onTestStarting, false);   // Act testContext.actAndWait(100, function() { eng.run().then(function() { window.removeEventListener("lbrtwUtTestStarting", onTestStarting, false); }); }).   // Assert thenAssert(function() { the("a").propertyOf(triggered).shouldBeExactly(1); the("b").propertyOf(triggered).shouldBeExactly(1); the(0).propertyOf(triggeredByIndex).shouldBeExactly(1); the(1).propertyOf(triggeredByIndex).shouldBeExactly(1); }) });   engine.add("lbrtwUtTestEnded event should be triggered once per unit test", function(testContext, the) { // Arrange var eng = new ut.Engine(); var results = {}; eng.add("a", function() {}); eng.add("b", function() { throw "Crash!"; }); var onTestEnded = function(event) { results[event.testName] = { success : event.testSuccess, errorMessage : event.testErrorMessage, successCount : event.successCount, failureCount : event.failureCount, testCount : event.testCount }; }; window.addEventListener("lbrtwUtTestEnded", onTestEnded, false);   // Act testContext.actAndWait(100, function() { eng.run().then(function() { window.removeEventListener("lbrtwUtTestEnded", onTestEnded, false); }); }).   // Assert thenAssert(function() { the("a").propertyOf(results).shouldBeTruthy(); the("success").propertyOf(results.a).shouldBeTrue(); the("errorMessage").propertyOf(results.a).shouldBeNull(); the("successCount").propertyOf(results.a).shouldBeExactly(1); the("failureCount").propertyOf(results.a).shouldBeExactly(0); the("testCount").propertyOf(results.a).shouldBeExactly(2);   the("b").propertyOf(results).shouldBeTruthy(); the("success").propertyOf(results.b).shouldBeFalse(); the("errorMessage").propertyOf(results.b).shouldBeExactly("Crash!"); the("successCount").propertyOf(results.b).shouldBeExactly(1); the("failureCount").propertyOf(results.b).shouldBeExactly(1); the("testCount").propertyOf(results.b).shouldBeExactly(2); }) });   engine.add("The lbrtwUtEngineEnded event object should contain testCount, failureCount, successCount, testNames and failures", function(testContext, the) { // Arrange var eng = new ut.Engine(); eng.add("a", function() {}); eng.add("b", function() {}); eng.add("c", function() { throw "Crash!"; }); var testCount; var failureCount; var successCount; var testNames; var failures;   var onEnded = function(event) { window.removeEventListener("lbrtwUtEngineEnded", onEnded, false); testCount = event.testCount; failureCount = event.failureCount; successCount = event.successCount; testNames = event.testNames; failures = event.failures; }; window.addEventListener("lbrtwUtEngineEnded", onEnded, false);   // Act testContext.actAndWait(100, function() { eng.run(); }).   // Assert thenAssert(function() { the(testCount).shouldBeExactly(3); the(failureCount).shouldBeExactly(1); the(successCount).shouldBeExactly(2); the("length").propertyOf(testNames).shouldBeExactly(3); the("length").propertyOf(failures).shouldBeExactly(1); }) });

The implementation is actually pretty easy. I write a helper function for dispatching my custom events, and then I use it at various points during the test process. Here is the helper function. Download the code below to see the complete implementation.

var triggerEvent = function(name, properties) { var event = document.createEvent("HTMLEvents"); event.initEvent(name, true, false);   for (var key in properties) { event[key] = properties[key]; }   window.dispatchEvent(event); };   // call like this: // triggerEvent("lbrtwUtTestStarting", { // testName : name, // testIndex : index // });

Making the output pretty

By listening to the four events dispatched by the framework it is possible to create any output you like. When you download the code below, you will find an example of what you can do.

Sending results to your server

By listening to the lbrtwUtEngineEnded event, I can now send the complete test results to any server using XMLHttpRequest directly or using any of the jQuery.ajax shortcuts.

$.on("lbrtwUtEngineEnded", function(event) { var oev = event.originalEvent;   $.post("http://myserver/unit-test-results", { userAgent : navigator.userAgent, testCount : oev.testCount, successCount : oev.successCount, failureCount : oev.failureCount });   // Of course, you can also use the failures and testNames properties });   myEngine.run();

The finished code can be found on github at /lbrtw/ut

JavaScript unit test framework, part 1
JavaScript unit test framework, part 2
JavaScript unit test framework, part 3
JavaScript unit test framework, part 4 (this part)

JavaScript unit test framework, part 3

Any mature unit test framework contains lots of helper methods for validating requirements. Instead of manually validating values and references using if and then throwing errors when requirements aren't satisfied, one should use helper methods.

I would like to be able to do this:

eng.add("Test stuff", function(testContext, the) { // Arrange var obj = new SomeClass();   // Act var result = obj.doStuff(1, 2, 3);   // Assert the(result).shouldNotBeNull(); the(result).shouldBeInstanceOf(ResultClass); the("foo").propertyOf(result).shouldBeLessThan(1000); the("bar").propertyOf(result).shouldBeExactly(1); the("abc").propertyOf(result).shouldBeTrue(); // and so on... });   eng.add("Test that a method throws an error", function(testContext, the) { // Arrange var obj = new SomeClass();   // Act and assert the("doStuff").methodOf(obj).withArguments(1, 2, 3).shouldThrowAnError(); });

When some should* method discovers a failing test, it should throw an exception to signal the failure to the test engine. I'm thinking that the shouldThrowAnError track would be the best to develop first, because it would make all future tests easier to write.

If the object or function being tested is named through the methodOf() or propertyOf() methods, and the requirement is not fulfilled, the error message should contain the name of the object or function being tested.

New requirements

  1. The second argument to a test function should be the ut.The function
  2. The ut.The function should simplify unit testing
    • calling the(func).shouldThrowAnError() should call func()
    • calling the(func).withArguments(...).shouldThrowAnError() should call func(...)
    • calling the(func).shouldThrowAnError() should throw an error if and only if func() does not throw an error
    • calling the(methodname).methodOf(object).shouldThrowAnError() should call object[methodname]()
    • calling the(x).shouldBeNull() should throw an error if x is not null
    • calling the(x).shouldNotBeNull() should throw an error if x is null
    • calling the(x).shouldBeExactly(y) should throw an error if x is not exactly y
    • calling the(x).shouldNotBeExactly(y) should throw an error if x is exactly y
    • calling the(x).shouldBeTrue() should throw an error if x is not true
    • calling the(x).shouldBeFalse() should throw an error if x is not false
    • calling the(x).shouldBeTruthy() should throw an error if x is not truthy
    • calling the(x).shouldBeFalsy() should throw an error if x is not falsy
    • calling the(x).shouldBeGreaterThan(y) should throw an error if x is not greater than y
    • calling the(x).shouldBeLessThan(y) should throw an error if x is not less than y
    • calling the(x).shouldBeGreaterThanOrEqualTo(y) should throw an error if x is not greater than or equal to y
    • calling the(x).shouldBeLessThanOrEqualTo(y) should throw an error if x is not less than or equal to y
    • calling the(x).shouldBeInstanceOf(y) should throw an error if x is not an instance of the y class
    • calling the("x").methodOf(y) should make an error message contain the text The x method
    • calling the("x").propertyOf(y) should make an error message contain the text The x property

Eighth requirement

engine.add("second argument to test functions should be the ut.The function", function() { // Arrange var eng = new ut.Engine(); var theThe; eng.add("Get the The", function(first, second) { theThe = second; });   // Act eng.run();   // Assert if (!theThe) { throw "Second argument null or missing"; } if (theThe !== ut.The) { throw "Second argument isn't ut.The"; } }); var utThe = function() { };   var runOneTestOrAssertFunc = function(engine, func, context) { try { func.call(null, context, utThe); if (!engine.actAndWaitFunc) { ++engine.successCount; } } catch(e) { engine.failures.push({ name : engine.currentTestName, message : e.toString() }); ++engine.failureCount; } };   window.ut = { "Engine" : utEngine, "TestContext" : utTestContext, "The" : utThe };

Ninth requirement

Requirement 9 – first batch

The first batch focuses on the shouldThrowAnError function. It should simply call the function passed to the the function, then examine the results. If there is an error, everything is fine, otherwise an error should be thrown because the expected error did not occur. This batch also deals with extracting a named method from an object using the methodOf function, and passing arguments to a function using the withArguments function.

engine.add("calling the(func).shouldThrowAnError() should call func()", function() { // Arrange var called = false; var func = function() { called = true; };   // Act try { ut.The(func).shouldThrowAnError(); } catch(e) { }   // Assert if (!called) { throw "func() wasn't called!"; } });   engine.add("calling the(func).withArguments(...).shouldThrowAnError() should call func(...)", function() { // Arrange var theA, theB, theC; var func = function(a, b, c) { theA = a; theB = b; theC = c; };   // Act try { ut.The(func).withArguments(1, 2, 3).shouldThrowAnError(); } catch(e) { }   // Assert if (theA !== 1) { throw "First argument was not passed"; } if (theB !== 2) { throw "Second argument was not passed"; } if (theC !== 3) { throw "Third argument was not passed"; } });   engine.add("the('foo').methodOf(bar) should be the same as the(bar.foo)", function() { // Arrange var called = false; var bar = { foo : function() { called = true; } };   // Act try { ut.The("foo").methodOf(bar).shouldThrowAnError(); } catch(e) { }   // Assert if (!called) { throw "bar.foo() was not called"; } });   engine.add("the('foo').methodOf(bar) should be the same as the(bar.foo), part 2", function() { // Arrange var theA, theB; var bar = { foo : function(a, b) { theA = a; theB = b; } };   // Act try { ut.The("foo").methodOf(bar).withArguments(1, 2).shouldThrowAnError(); } catch(e) { }   // Assert if (theA !== 1 || theB !== 2) { throw "bar.foo(1, 2) was not called"; } });   engine.add("shouldThrowAnError() should not throw an error if some error was thrown", function() { // Arrange var errorThrown = false; var func = function() { throw "Expected failure"; };   // Act try { ut.The(func).shouldThrowAnError(); } catch (e) { errorThrown = true; }   // Assert if (errorThrown) { throw "An error was thrown!"; } });   engine.add("shouldThrowAnError() should throw an error if no error was thrown", function() { // Arrange var errorThrown = false; var func = function() { };   // Act try { ut.The(func).shouldThrowAnError(); } catch (e) { errorThrown = true; }   // Assert if (!errorThrown) { throw "No error was thrown!"; } }); var asserter = function(arg) { this.target = arg; };   asserter.prototype = { methodOf : function(obj) { this.target = (function(obj, name) { return obj[name]; })(obj, this.target);   return this; },   withArguments : function() { var args = [].slice.call(arguments);   this.target = (function(method, args) { return function() { method.apply(null, args); }; })(this.target, args);   return this; },   shouldThrowAnError : function() { var threw = false; try { this.target.call(); } catch (e) { threw = true; } if (!threw) { throw "Did not throw an error"; } } };   var utThe = function(arg) { return new asserter(arg); };

Requirement 9 – second batch

The last few tests for this time validate all the comparison functions, like shouldBeNull() and shouldBeGreaterThan(). These should throw descriptive error messages, so I test the actual message texts.

Instead of writing every single unit test separately for the validation methods (which would have me writing 30+ unit tests by hand), I write a helper function to create three unit tests per validation method: One for validating that no error is thrown when no error should be thrown, and two for validating the correct error messages.

engine.add("the('foo').methodOf(bar) should be the same as the(bar.foo), but change the error message", function() { // Arrange var errorThrown = false; var bar = { foo : function() { } };   // Act try { ut.The("foo").methodOf(bar).shouldThrowAnError(); } catch (e) { errorThrown = e.toString(); }   // Assert if (!errorThrown) { throw "No error was thrown!"; } var expected = "The foo method did not throw an error"; if (errorThrown != expected) { throw "The wrong error was thrown! Expected: '" + expected + "', actual: '" + errorThrown + "'"; } });   var addAssertTestsForMethod = function(engine, methodName, goodValue, badValue, arg, expectedError) { if (typeof goodValue != "undefined") { var passingTestName = methodName + "() should not throw an error";   var passingTestFunc = (function(methodName, goodValue, arg) { return function() { // Act ut.The(goodValue)[methodName](arg); }; })(methodName, goodValue, arg);   engine.add(passingTestName, passingTestFunc); } if (typeof badValue != "undefined") { var failingTestName = methodName + "() should throw an error"; var namedFailingTestName = methodName + "() should throw an error with correct name";   var failingTestFunc = (function(methodName, badValue, arg, expectedError) { return function() { // Arrange var errorThrown = false;   // Act try { ut.The(badValue)[methodName](arg); } catch (e) { errorThrown = e; }   // Assert if (!errorThrown) { throw "Did not throw an error"; } if (errorThrown != expectedError) { throw "Did not throw the correct error. Expected: '" + expectedError + "', actual: '" + errorThrown + "'"; } }; })(methodName, badValue, arg, expectedError.replace(/%/, "The value"));   var namedFailingTestFunc = (function(methodName, badValue, arg, expectedError) { return function() { // Arrange var errorThrown = false; var obj = { prop : badValue };   // Act try { ut.The("prop").propertyOf(obj)[methodName](arg); } catch (e) { errorThrown = e; }   // Assert if (!errorThrown) { throw "Did not throw an error"; } if (errorThrown != expectedError) { throw "Did not throw the correct error. Expected: '" + expectedError + "', actual: '" + errorThrown + "'"; } }; })(methodName, badValue, arg, expectedError.replace(/%/, "The prop property"));   engine.add(failingTestName, failingTestFunc); engine.add(namedFailingTestName, namedFailingTestFunc); } };   var testClass = function() { this.foo = "bar"; };   addAssertTestsForMethod(engine, "shouldBeNull", null, 123, undefined, "% is not null"); addAssertTestsForMethod(engine, "shouldNotBeNull", 123, null, undefined, "% is null"); addAssertTestsForMethod(engine, "shouldBeExactly", 1, true, 1, "Expected: exactly 1, %: true"); addAssertTestsForMethod(engine, "shouldNotBeExactly", true, 1, 1, "% is exactly 1"); addAssertTestsForMethod(engine, "shouldBeLessThan", 1, 2, 2, "Expected: less than 2, %: 2"); addAssertTestsForMethod(engine, "shouldBeGreaterThan", 3, 2, 2, "Expected: greater than 2, %: 2"); addAssertTestsForMethod(engine, "shouldBeLessThanOrEqualTo", 2, 3, 2, "Expected: less than or equal to 2, %: 3"); addAssertTestsForMethod(engine, "shouldBeGreaterThanOrEqualTo", 2, 1, 2, "Expected: greater than or equal to 2, %: 1"); addAssertTestsForMethod(engine, "shouldBeTrue", true, 1, undefined, "% is not true"); addAssertTestsForMethod(engine, "shouldBeTruthy", 1, false, undefined, "% is not truthy"); addAssertTestsForMethod(engine, "shouldBeFalse", false, 0, undefined, "% is not false"); addAssertTestsForMethod(engine, "shouldBeFalsy", 0, true, undefined, "% is not falsy"); addAssertTestsForMethod(engine, "shouldBeInstanceOf", new testClass(), testClass, testClass, "% is not of correct type"); var asserter = function(arg) { this.target = arg; this.valueName = "The value"; this.methodName = "The function"; };   asserter.prototype = { methodOf : function(obj) { this.methodName = "The " + this.target + " method";   this.target = (function(obj, name) { return obj[name]; })(obj, this.target);   return this; },   withArguments : function() { var args = [].slice.call(arguments);   this.target = (function(method, args) { return function() { method.apply(null, args); }; })(this.target, args);   return this; },   shouldThrowAnError : function() { var threw = false; try { this.target.call(); } catch (e) { threw = true; } if (!threw) { throw this.methodName + " did not throw an error"; } },   propertyOf : function(obj) { this.valueName = "The " + this.target + " property"; this.target = obj[this.target];   return this; },   shouldBeNull : function() { if (this.target !== null) { throw this.valueName + " is not null"; } },   shouldNotBeNull : function() { if (this.target === null) { throw this.valueName + " is null"; } },   shouldBeExactly : function(arg) { if (this.target !== arg) { throw "Expected: exactly " + arg + ", " + this.valueName + ": " + this.target; } },   shouldNotBeExactly : function(arg) { if (this.target === arg) { throw this.valueName + " is exactly " + arg; } },   shouldBeLessThan : function(arg) { if (!(this.target arg)) { throw "Expected: greater than " + arg + ", " + this.valueName + ": " + this.target; } },   shouldBeLessThanOrEqualTo : function(arg) { if (!(this.target = arg)) { throw "Expected: greater than or equal to " + arg + ", " + this.valueName + ": " + this.target; } },   shouldBeTrue : function() { if (this.target !== true) { throw this.valueName + " is not true"; } },   shouldBeTruthy : function() { if (!this.target) { throw this.valueName + " is not truthy"; } },   shouldBeFalse : function() { if (this.target !== false) { throw this.valueName + " is not false"; } },   shouldBeFalsy : function() { if (this.target) { throw this.valueName + " is not falsy"; } },   shouldBeInstanceOf : function(theClass) { if (!(this.target instanceof theClass)) { throw this.valueName + " is not of correct type"; } } };

There we go. This is actually all I'm going to do for the actual testing of things. The next and final post will deal with better output of test results, including sending test result data to your server of choice. I will also add some pluggability to the unit test framework.

The finished code can be found on github at /lbrtw/ut

JavaScript unit test framework, part 1
JavaScript unit test framework, part 2
JavaScript unit test framework, part 3 (this part)
JavaScript unit test framework, part 4

JavaScript unit test framework, part 2

While adding new features to a project, you're supposed to add only one or a few unit tests at a time, see them fail, and then implement as little code as possible to make it/them pass. The current version of the unit test framework only shows the number of passing/failing tests, so you cannot be sure about which test is actually failing – especially after making some code changes. Also, there is no way of seeing the actual error message thrown when running a failing unit test. I really need to improve on this, so I introduce the failures property, which will contain names and error messages of all failing tests.

There is also the case of testing asynchronous code, such as timers, event handlers, AJAX requests, CSS transitions and so on. The way the run method is currently implemented, there is no way of properly waiting for it to complete if some unit test contains event handlers or timers.

New requirements

  1. The engine should keep track of failing tests and their error messages
    • after eng.run() is done, the eng.failures should contain a list of objects containing the name and error message of each failing test
  2. Each test function should be called with a ut.TestContext object as its first argument
    • testContext.actAndWait(timeout, actFunc) should run the actFunc function, and wait timeout milliseconds before continuing
    • if the actFunc function crashes, the test is marked as a failed test
    • the actFunc function should receive a ut.TestContext object as its first argument
    • calling testContext.actDone() from within an asynchronous test function stops the waiting immediately
    • the testContext.actAndWait method should return the test context object itself, for call chaining purposes
    • calling testContext.thenAssert(assertFunc) should make the assertFunc function get called after the actFunc function is either timed out, or marked as done using the actDone() method
    • the assertFunc function should receive a ut.TestContext object as its first argument
  3. The framework should provide a function hook to be called after all unit tests have been run
    • the run() method of ut.Engine should return the engine itself
    • the then(func) method should register a function to be run after all unit tests are done running, passing the engine as the first parameter to the func function
    • If none of the registered unit tests contain any asynchronous code, calling run() should run all tests before returning, and the caller of run() shouldn't need to use the then() method

Fifth requirement

engine.add("The engine should keep track of which tests fail/succeed", function() { var eng = new ut.Engine(); var failFunc = function() { throw "I did crash!"; }; eng.add("Failing", failFunc);    // Act eng.run();    // Assert if (eng.failures.length !== 1) { throw "failures.length should be 1, but is " + eng.failures.length; } if (eng.failures[0].name != "Failing") { throw "failures[0].name should be 'Failing', but is " + eng.failures[0].name; } if (eng.failures[0].message != "I did crash!") { throw "failures[0].message should be 'I did crash!', but is " + eng.failures[0].message; } });

The implementation is pretty simple, and after this refactoring, I can use the failures property to list failing tests and their error messages.

run : function() { this.failures = []; for (var name in this.tests) { var testfunc = this.tests[name]; try { testfunc.call(); ++this.successCount; } catch(e) { this.failures.push({ name : name, message : e.toString() }); ++this.failureCount; } } } engine.run();    console.log(engine.failureCount + " failures and " + engine.successCount + " successes, out of " + engine.testCount + " tests");    for (var i = 0; i < engine.failures.length; ++i) { var fail = engine.failures[i]; console.log(fail.name + ": " + fail.message); }

Sixth and seventh requirement

Requirements for an API or a framework are often expressed better in code. This is how I want to be able to use the features described in requirements 6/7:

var eng = new ut.Engine();    eng.add("Asynch test", function(testContext) { // Arrange var someObj = new SomeClass(); var result = null;    // Act testContext.actAndWait(1000, function(tc) { someObj.someAsyncMethod({ success : function(r) { result = r; tc.actDone(); } }); }). // < -- notice the dot here... call chaining!    // Assert thenAssert(function(tc) { if (result == null) { throw "Timeout!"; } if (result.foo !== "bar") { throw "Expected 'bar', but found " + result.foo; } }); });    eng.run().then(function(engine) { // Display test results });

Requirements 6/7 – first batch

The first batch of eight unit tests addresses every piece of the sixth requirement except for the actDone() function.

engine.add("Test functions should be called with a ut.TestContext as its first argument", function() { // Arrange var innerTc; var eng = new ut.Engine(); eng.add("set inner test context", function(tc) { innerTc = tc; });   // Act eng.run();   // Assert if (!(innerTc instanceof ut.TestContext)) { throw "innerTc is not a ut.TestContext object"; } });   engine.add("testContext.actAndWait should return the testContext itself", function() { // Arrange var innerTc; var returnedTc; var eng = new ut.Engine(); eng.add("set inner test context", function(tc) { innerTc = tc; returnedTc = tc.actAndWait(1, function() {}); });   // Act eng.run();   // Assert if (innerTc !== returnedTc) { throw "actAndWait did not return the testContext itself"; } });   engine.add("actAndWait(timeout, actFunc) should run the actFunc, and wait (at least) timeout milliseconds", function(testContext) { // Arrange var timeout = 100; var calledAt = 0, endedAt = 0; var eng = new ut.Engine(); var actFunc = function() { calledAt = new Date().getTime(); } var testFunc = function(tc) { tc.actAndWait(timeout, actFunc); }; eng.add("actAndWait should wait correct amount of ms", testFunc);   // Act testContext.actAndWait(timeout + 100, function() { eng.run().then(function() { endedAt = new Date().getTime(); }); }).   // Assert thenAssert(function() { if (calledAt == 0) { throw "Did not call the actFunc function"; } if (endedAt == 0) { throw "Did not finish running the tests properly"; } // Minor timing issue: one or two milliseconds off is not a big deal if (endedAt < calledAt + timeout) { throw "Did not wait enough ms (waited " + (endedAt - calledAt) + " ms"; } }); });   engine.add("thenAssert(func) should called the assert function after (at least) the registered number of milliseconds", function(testContext) { // Arrange var timeout = 100; var calledAt = 0, assertedAt = 0; var eng = new ut.Engine(); var actFunc = function() { calledAt = new Date().getTime(); }; var assertFunc = function() { assertedAt = new Date().getTime(); }; var testFunc = function(tc) { tc.actAndWait(timeout, actFunc).thenAssert(assertFunc); } eng.add("thenAssert should wait correct amount of ms", testFunc);   // Act testContext.actAndWait(timeout + 100, function() { eng.run(); }).   // Assert thenAssert(function() { if (calledAt == 0) { throw "Did not call the actFunc function"; } if (assertedAt == 0) { throw "Did not call the assertFunc function"; } if (assertedAt < calledAt + timeout) { throw "Did not wait enough ms (waited " + (assertedAt - calledAt) + " ms"; } }); });   engine.add("if the actFunc for actAndWait crashes, the test should be failed", function(testContext) { // Arrange var eng = new ut.Engine(); eng.add("This should crash", function(tc) { tc.actAndWait(100, function() { throw "Crashing!"; }); });   // Run testContext.actAndWait(200, function() { eng.run(); }).   // Assert thenAssert(function() { if (eng.failures.length !== 1) { throw "Did not register exactly one failure"; } }); });   engine.add("then(func) should run func immediately if there are no asynchronous unit tests", function() { // Arrange var thenCalled = false; var eng = new ut.Engine(); eng.add("no-op test", function() { });   // Run eng.run().then(function() { thenCalled = true; });   // Assert if (!thenCalled) { throw "the thenFunc was not called"; } });   engine.add("then(func) should NOT run func immediately if there are some asynchronous unit test", function() { // Arrange var thenCalled = false; var eng = new ut.Engine(); eng.add("async test", function(tc) { tc.actAndWait(100, function() { }); });   // Run eng.run().then(function() { thenCalled = true; });   // Assert if (thenCalled) { throw "the thenFunc was called, but shouldn't!"; } });   engine.add("then(func) should run func after all asynchronous tests are done", function(testContext) { // Arrange var thenCalled = false; var eng = new ut.Engine(); eng.add("async test", function(tc) { tc.actAndWait(100, function() { }); });   // Run testContext.actAndWait(200, function() { eng.run().then(function() { thenCalled = true; }); }).   // Assert thenAssert(function() { if (!thenCalled) { throw "the thenFunc wasn't called"; } }); });   // new way of printing successes/failures engine.run().then(function() { console.log(engine.failureCount + " failures and " + engine.successCount + " successes, out of " + engine.testCount + " tests");   for (var i = 0; i < engine.failures.length; ++i) { var fail = engine.failures[i]; console.log(fail.name + ": " + fail.message); } });

This takes a pretty big piece of refactoring. I'm essentially transforming a sequential traversal of the tests property into a "wait-and-continue" loop using window.setTimeout to save engine state, halt the unit test engine, let a test run its course, then continue with the assert function or the next test.

First, the new ut.TestContext class:

var utTestContext = function(engine) { this.engine = engine; };   utTestContext.prototype = { actAndWait : function(timeout, actFunc) { this.engine.actAndWaitFunc = actFunc; this.engine.actAndWaitContext = this; this.engine.actAndWaitTimeout = timeout; return this; },   thenAssert : function(assertFunc) { this.engine.thenAssertFunc = assertFunc; this.engine.thenAssertContext = this; } };   window.ut = { "Engine" : utEngine, "TestContext" : utTestContext };

Then the new ut.Engine implementation:

var utEngine = function() { this.tests = {}; this.testCount = this.successCount = this.failureCount = 0; };   // private function, not exposed var runOneTestOrAssertFunc = function(engine, func, context) { try { func.call(null, context); if (!engine.actAndWaitFunc) { ++engine.successCount; } } catch(e) { engine.failures.push({ name : engine.currentTestName, message : e.toString() }); ++engine.failureCount; } };   utEngine.prototype = { add : function(name, testfunc) { this.tests[name] = testfunc; ++this.testCount; },   run : function() { if (this.initialized !== true) { this.initialized = true;   this.failures = [];   this.testNameIndex = 0; this.testNames = []; for (var name in this.tests) this.testNames.push(name); }   this.running = true;   if (this.actAndWaitFunc) { runOneTestOrAssertFunc(this, this.actAndWaitFunc, this.actAndWaitContext);   delete this.actAndWaitFunc; var self = this;   // pause the engine for a number of milliseconds this.actAndWaitTimeoutId = window.setTimeout(function() { self.run(); }, this.actAndWaitTimeout);   return this; }   if (this.thenAssertFunc) { runOneTestOrAssertFunc(this, this.thenAssertFunc, this.thenAssertContext);   delete this.thenAssertFunc; delete this.thenAssertContext; }   while (this.testNameIndex < this.testNames.length) { var name = this.testNames[this.testNameIndex++]; var testFunc = this.tests[name]; var context = new ut.TestContext(this); this.currentTestName = name;   runOneTestOrAssertFunc(this, testFunc, context);   if (this.actAndWaitFunc) { var self = this; window.setTimeout(function() { self.run(); }, 0); return this; } }   this.running = false;   if (this.thenFunc) { this.thenFunc.call(null, this); }   return this; },   then : function(thenFunc) { if (this.running) { this.thenFunc = thenFunc; } else { thenFunc.call(null, this); } } };

The unit test framework is starting to be really useful now, but is still only just over a hundred lines of production code, and about 350 lines of test code.

Requirements 6/7 – second batch

If you have lots of asynchronous unit tests where you need a large timeout value, but the tests still might finish quickly, it doesn't really feel good to always wait for the maximum expected timeout before moving on to the next test. You should be able to move on instantly if a test finishes early. That's why I add the actDone() method to tell the engine to move on to assertion and/or the next test instantly.

engine.add("actDone() should move immediately to the thenAssert assert function", function(testContext) { // Arrange var calledAt = 0, assertAt = 0; var eng = new ut.Engine(); eng.add("10ms func with 10000ms timeout", function(tc) { tc.actAndWait(10000, function() { calledAt = new Date().getTime(); window.setTimeout(function() { tc.actDone(); }, 10); }).thenAssert(function() { assertAt = new Date().getTime(); }); });   // Act testContext.actAndWait(500, function() { eng.run(); }).   // Assert thenAssert(function() { if (assertAt === 0) { throw "Assert wasn't called!"; } if (assertAt > (calledAt + 100)) { throw "Assert took way too long to get called!"; } }); }); The solution is to add this to the ut.TestContext prototype: actDone : function() { var engine = this.engine; if (engine.actAndWaitTimeoutId) { window.clearTimeout(engine.actAndWaitTimeoutId); delete engine.actAndWaitTimeoutId;   window.setTimeout(function() { engine.run(); }, 0); } }

There it is. Sixteen unit tests have rendered 137 lines of production code, which actually makes a pretty decent unit test framework. What is missing is a group of convenient helper functions and hooks for asserting and pretty output. If you want to use this in a automated testing environment it is already pretty much good to go. In the then() function, you could add a jQuery.ajax call to automatically post the test results to some server. Then just add a post-build script to your CI environment of choice to run your unit tests in a number of different browsers.

Next part will focus on assert helper functions and output hooks. Then I will look into mocking and some inversion of control magic.

The finished code can be found on github at /lbrtw/ut.

JavaScript unit test framework, part 1
JavaScript unit test framework, part 2 (this part)
JavaScript unit test framework, part 3
JavaScript unit test framework, part 4

JavaScript unit test framework, part 1

I have been involved in lots of agile and non-agile development projects, so I have experienced enough benefits of agile test-driven development to see a pattern. Smaller codebase, higher quality code, less bugs, more fun, lower startup threshold for adding new developers to the team, more efficient refactoring, maintainability, etc.

One key tool in achieving all of that is a unit test framework, which is why my first LBRTW project is to develop such a framework in JavaScript. Also, I will use the very unit test framework I'm writing to unit-test the framework itself.

The intended users of this framework are developers, so I can use pretty technical language in the specs, but I will still focus on keeping requirements short and concise. The first group of requirements look like this:

  1. eng = new ut.Engine() should create a new unit testing engine
    • upon creating a new engine, the eng.testCount should be zero
  2. eng.add(name, testfunc) should add a new unit test to the engine
    • eng.tests[name] should point to the testfunc function
    • the eng.testCount property should be increased
  3. eng.run() should run the test function of each added unit test once
    • if running the test function throws an exception, that indicates a failed unit test
    • all unit tests should always run, even if some unit test function crashes (or even all of them)
  4. The engine should keep track of the number of failed/succeeded tests
    • after eng.run() is done, the eng.successCount and eng.failureCount should contain the number of succeeded/failed unit tests respectively, and the sum of them should be the same as eng.testCount

First requirement

After establishing this, writing and running the first test is easy:

var engine = new ut.Engine();    engine.add("new ut.Engine should create a new unit testing engine", function() { // Act var eng = new ut.Engine();    // Assert if (eng.testCount !== 0) { throw "Did not set testCount to zero"; } });    engine.run();

Of course, when I try to run this, I will get a reference error saying ut is not defined. Also, I won't be able to actually run any tests before both add and run are somewhat implemented, so here is iteration zero of ut.Engine:

var utEngine = function() { this.tests = []; };    utEngine.prototype = { add : function(name, testfunc) { this.tests.push(testfunc); },    run : function() { for (var i = 0; i < this.tests.length; ++i) { var testfunc = this.tests[i]; testfunc.call(); } } };    window.ut = { "Engine" : utEngine };

Now it's possible to add and run unit tests, so it actually produces the first failing unit test output, albeit only visible in the developer console: Did not set testCount to zero. One small code change, and no errors are thrown:

var utEngine = function() { this.tests = []; this.testCount = 0; };

Second requirement

The next requirement deals with adding test functions to the tests collection and increasing the testCount property. This is what those tests look like:

engine.add("add() should set tests[name] to func", function() { // Arrange var eng = new ut.Engine(); var bar = function() {};    // Act eng.add("foo", bar);    // Assert if (eng.tests["foo"] !== bar) { throw "tests.foo does not point to bar"; } });    engine.add("add() should increase testCount", function() { // Arrange var eng = new ut.Engine(); var func = function() {};    // Act eng.add("foo", func);    // Assert if (eng.testCount !== 1) { throw "Did not increase testCount"; } });

The first test is made to pass by refactoring the tests array into an anonymous object, changing the add method to add by name instead of by index, and the run method to traverse the object using for ... in. The second test passes after a small refactoring of the add method:

var utEngine = function() { this.tests = {}; this.testCount = 0; };    utEngine.prototype = { add : function(name, testfunc) { this.tests[name] = testfunc; ++this.testCount; },    run : function() { for (var name in this.tests) { var testfunc = this.tests[name]; testfunc.call(); } } };

Third requirement

If this one isn't satisfied, any failing test will stop all concurrent tests from running, which will only allow us to deal with one failing test at a time.

engine.add("run() should run each added test once", function() { // Arrange var eng = new ut.Engine(); var called = [0, 0]; var func1 = function() { called[0]++; }; var func2 = function() { called[1]++; }; eng.add("func1", func1); eng.add("func2", func2);    // Act eng.run();    // Assert if (called[0] !== 1) { throw "Did not call func1"; } if (called[1] !== 1) { throw "Did not call func2"; } });    engine.add("run() should run all tests even when crash", function() { // Arrange var eng = new ut.Engine(); var called = 0; var func = function() { ++called; throw "Crash!"; } eng.add("Going once", func); eng.add("Going twice", func);    // Act eng.run();    // Assert if (called !== 2) { throw "Did not call both added tests"; } });

The first test of this requirement already passes, but the second one does crash. It doesn't even run all the way to the assertion part. It's the test function itself that produces the developer console output – not the unit test framework. To make the test pass, I simply wrap calling test functions in try ... catch.

run : function() { for (var name in this.tests) { var testfunc = this.tests[name]; try { testfunc.call(); } catch(e) { } } }

Fourth requirement

When writing the unit test for this requirement, I also adopt the new way of checking for failing unit tests. Instead of just letting the developer console print out the exception message, I print out the values of failureCount, successCount and testCount after run() has been called.

engine.add("The engine should count successes and failures", function() { // Arrange var eng = new ut.Engine(); var failFunc = function() { throw "Crash!"; }; var successFunc = function() { }; eng.add("One fail", failFunc); eng.add("Two fails", failFunc); eng.add("Three fails", failFunc); eng.add("One success", successFunc);    // Act eng.run();    // Assert if (eng.successCount !== 1) { throw "successCount should be 1, but is " + eng.successCount; } if (eng.failureCount !== 3) { throw "failureCount should be 3, but is " + eng.failureCount; } });    engine.run();    console.log(engine.failureCount + " failures and " + engine.successCount + " successes, out of " + engine.testCount + " tests"); When running all unit tests now, the output simply says undefined failures and undefined successes, out of 6 tests, simply because the engine does not yet count failures or successes. A small refactoring of the constructor and the run method later: var utEngine = function() { this.tests = {}; this.testCount = this.successCount = this.failureCount = 0; };    // inside prototype definition: run : function() { for (var name in this.tests) { var testfunc = this.tests[name]; try { testfunc.call(); ++this.successCount; } catch(e) { ++this.failureCount; } } }

There it is – the first iteration of my JavaScript unit test framework. Feel free to use it. There is still lots of important stuff to do, like a way of knowing which tests are failing and not just how many of them.

The finished code can be found on github at /lbrtw/ut.

JavaScript unit test framework, part 1 (this part)
JavaScript unit test framework, part 2
JavaScript unit test framework, part 3
JavaScript unit test framework, part 4

Almost pure CSS page flip

Google Chrome still has a long way to go when rendering stuff that is a just little bit more advanced. Experimenting with CSS3 3D Transforms.

I placed a video element on a div element that rotates in 3D using CSS3 3D Transforms and CSS3 Transitions. In Safari, everything renders smoothly, even when the video is playing! In Chrome (I’m using Google Chrome 9 Beta), the entire div containing the video disappears while the transition is running.

That experiment led to more experimenting, and suddenly I had a fully functioning page flipping effect – almost purely done in CSS. Using a simple combination of -webkit-tranform-origin and -webkit-transform went a very long way.

Actually, the contents of the project explains itself. If you are using Google Chrome or Desktop Safari on Windows or Mac, use the scroll wheel to flip through pages, or click on a page. If you are on an iOS device, flick a page to go forward or backward.

Please note! This works best on Safari and on iOS devices. Google Chrome does support CSS 3D Transforms, but only if you actively enable it. Type about:flags into the address bar of your Google Chrome browser, and click Enable for GPU Accelerated Compositing to enable 3D CSS in Chrome, then restart the browser.

The experiment is called CSS Flip. Please try it out now on demo.atornblad.se/cssflip and tell me what you think.

Using addTouch to emulate webkit touch events in desktop browser

Testing multitouch code on the iPhone is a bit tedious. I don’t have a Mac, so I don’t have access to the iPhone emulator. I do have a tiny web server installed on my development machine, so I mostly open the project on my iPhone over Wi-Fi using a local IP address. That way I can test my new features instantly, and verify that some bug has been fixed.

This has been enough for simple experiments, but when the code is to be used in a production environment, I really need the debugging capabilities of a modern desktop browser. Mobile Safari does have a developer mode, but that only catches JavaScript exceptions and presents them in a list. I need to be able to set breakpoints, and step through my code in a debugger. I need FireBug, or the Developer Tools of Google Chrome or Internet Explorer 9.

That is why I wrote addTouch. AddTouch started out as a Google Chrome Extension to simulate touch events using the mouse in any web page. Unfortunately, the security model of Google Chrome doesn’t allow me to take over the mouse input completely using an Extension, so instead it is now a JavaScript file to be included before all other JavaScript in the page. It also comes with a CSS stylesheet.

When you visit a touch-enabled web page that includes addTouch.js on an iOS device, nothing out of the ordinary happens. The web page simply works as intended. But when you visit the same web page using any modern desktop browser, adding ?_addTouch=webkit to the URL, all mouse-based interaction is hijacked and turned into touch events that the JavaScript in your web page can handle.

Simply add addTouch.js to your head element before any other javascript is included. Also add the addTouch.css stylesheet.

<html> <head> <script type="text/javascript" src="addTouch.js"></script> <!– Your other scripts here –> <link rel="stylesheet" type="text/css" href="addTouch.css"> </head> <body> <!– Your markup here –> </body> </html>

Here is a demo app:
Without addTouch – visit with iPhone or iPad
With addTouch – visit with any modern desktop browser

This version of addTouch only emulates the raw touchstart, touchmove and touchend events. You will not get any gesture* events. I will probably add gesture emulation in later versions. Also on the “to do” list is support for Mozilla-style touch events like MozTouchDown.

The addTouch project is available on github: /lbrtw/addTouch

Let me know what you think, and if this helps you. NB: addTouch supports pretty much any version of Chrome and Safari. I have only tested it in Firefox 4, but I would be surprised if it didn’t work in Firefox 3.6. For Internet Explorer, you’ll need a recent Platform Preview of IE9.

IMG problems in Firefox

While having some time to spare, I wanted to put some common JavaScript functions together in a library, but everything didn't work out the way I had hoped. At work everything went fine in all web browsers I had access to – IE 5.5, IE 6, IE 7, Firefox 1.5, Netscape 8.1 and Opera 8.5 – but when testing at home, nothing worked in Firefox. Nothing I did seemed to help.

The problem was image preloading – a common and simple method that I had used several times before:

var myimg = new Image(); myimg.src = "image-filename";

To solve the problem, I tried lots of different approches...

The first step – creating the Image object – was varied these ways:

var myimg = document.createElement("img"); var myimg = new Image(width, height); var myimg = new Image();

The second step – setting the src attribute value – was done like this:

myimg.src = "image-filename"; myimg.setAttribute("src", "image-filename"); myimg.attributes["src"] = "image-filename"; myimg.getAttributeNode("src").value = "image-filename"; myimg["src"] = "image-filename"; /* and finally: */ var srcAttr = document.createAttribute("src"); srcAttr.value = "image-filename"; myimg.setAttributeNode(srcAttr);

...but nothing worked. All different options worked perfectly in Internet Explorer and in Opera (a couple of variants didn't work in IE 5.5), but in Firefox nothing happened – no image was loaded, no matter what I did!

Finally I used the big guns:

var outer = document.createElement("div"); outer.innerHTML = ""; var myimg = outer.childNodes[0];

This solution actually worked! But since it has a really bad code smell, I didn't want to use it. It reminded too much about eval().

I Googled like a mad-man and found similar questions on several different forums and discussion groups from people in the same situation as I, but no answers. Finally I searched for preload images firefox problems, and just happened to stumble upon the Firefox development teams's configuration documentation. For some reason, my browser's value of the dom.disable_image_src_set setting was true – probably made that way by a security plugin that I had installed previously.

The dom.disable_image_src_set setting prevents JavaScript code from setting the value of the src attribute of img elements. When changing the setting from true to false (by first typing about:config in the Firefox address box), preloading images started working without a glitch in Firefox.