JavaScript Category

Emulating ZX Spectrum graphics in JavaScript

TL;DR: I'm using EcmaScript 6 Proxy objects to keep track of dirty blocks in emulated ZX Spectrum video RAM. Try it out at lbrtw.github.com/zx-spectrum-bitmap or dive into the code by cloning or forking github.com/lbrtw/zx-spectrum-bitmap.git.

Emulating 1982 video RAM in JavaScript

Animated GIF screenshot made using gifcreator.me It's no secret that I have a sweet spot for the Sinclair ZX Spectrum. One of the things I was amazed by as an eight-year-old was the (then) incredible 256×192 pixel colour graphics. Using only 6.75 kilobytes of video RAM, the custom Ferranti ULA chip pieced together the video signal 50 (or 60) times per second.

Software emulation of the Ferranti ULA has been done a lot of times, but reinventing the wheel is a great way of learning new (or old) things, so I decided to make an attempt of my own.

JavaScript, CANVAS and Proxies, oh my!

First of all, I'm using a CANVAS element and the CanvasRenderingContext2D object to draw graphics in the browser's window. I'm also using a Uint8ClampedArray to store the 6912 bytes of raw video RAM. For a more detailed description of the memory layout, scroll down a little. Each byte of the array corresponds exactly to one byte of ZX Spectrum RAM, so changing the contents of a single byte should trigger a redrawing of at least a part of the canvas.

I decided to redraw the canvas in blocks of 8×8 pixels, because this is close to how the ZX Spectrum ULA worked. Changing any one of the 8 bitmap bytes inside a block, or its attribute byte, should mark that block as "dirty" and when the next animation frame comes along, all dirty blocks should be rerendered. Because of this, there is also a Uint8Array of length 728 (32×24) keeping track of dirty blocks, so that I don't have to redraw all blocks every frame.

Using a Proxy object, I'm able to use the array normally, while correctly marking dirty blocks as needed. Without a Proxy, I would have to expose a setter method for changing the RAM contents.

// Without a Proxy object:
data.set(address, newValue);

// With a Proxy object:
data[address] = newValue;

The Uint8ClampedArray and Proxy construction looks like this:

var data = new Uint8ClampedArray(6912);

var dataProxy = new Proxy(data, {
    "set" : function(target, property, value, receiver) {
        if (property >= 0 && property < 6912) {
            data[property] = value;

            var dirtBlockIndex;

            if (property >= 6144) {
                // The index is inside the attribute section
                dirtBlockIndex = property - 6144;
            } else {
                // The index is inside the bitmap section
                dirtBlockIndex = blockIndexFromOffset(property);
            }

            dirtyBlocks[dirtBlockIndex] = 1;

            return true;
        }

        // Not a numeric index inside the boundaries
        return false;
    }
});

This creates a Proxy that, when written to, sets the value of the hidden array, calculates what block is changed, and marks that block as dirty, so that the next call to the renderer only redraws the dirty blocks. This speeds up the rendering process a lot.

The ZX.Spectrum.Bitmap object exposes the following public functions:

  • poke(address, value): Changes one byte of video RAM (valid adresses are within the 16384..23295 range)
  • peek(address): Reads one byte of video RAM
  • ink(value): Sets the current INK colour (0..7)
  • paper(value): Sets the current PAPER colour (0..7)
  • bright(value): Sets the current BRIGHT value (0..1)
  • flash(value): Sets the current FLASH colour (0..1)
  • cls(): Clears the screen using the current settings
  • plot(x, y): Sets one pixel, affecting the colour block
  • unplot(x, y): Clears one pixel, affecting the colour block
  • line(x1, y1, x2, y2): Draws a one pixel line, affecting colour blocks

Try it out

Go to lbrtw.github.io/zx-spectrum-bitmap and try it out for yourself. You can also clone or fork the code and play around. Pull requests are more than welcome!

Back to the 1980s

The ZX Spectrum was an amazing computer for its time. An advanced BASIC interpreter fit snugly into 16 kilobytes of ROM, and the 48 kilobytes of RAM included 6.75 kilobytes of graphics memory. Using BASIC commands like PLOT, INK and CIRCLE, you could write algorithms to draw things of beauty on the screen, but you had to look out for attribute clash.

The video RAM consisted of monochrome bitmap data containing one bit per pixel for a total of 256×192=49152 bits, fitting into 49152/8=6144 bytes, starting at address 16384. The order of pixel rows inside this memory area is a little strange, as rows are not placed linearly (each line of 256 pixels is not exactly 256 bits after the one above it). To calculate the screen address of the first pixel of a Y coordinate, you encode the address as follows:

1514131211109876543210
010Y7Y6Y2Y1Y0Y5Y4Y300000

This effectively divided the screen vertically into three blocks of 256×64 pixels, within which it is easy to get to the next line of characters, and also easy to get to the next line within a character block by simply adding one to the high byte of the address, but calculating the screen position from a pixel coordinate is really convoluted.

Directly after that monochrome bitmap, at address 22528, was one attribute byte per 8×8 block, containing the colour values for the "ones" and "zeros" of the bitmap data. Each attribute byte is encoded like this:

76543210
FBP2P1P0I2I1I0
  • F holds a one for FLASH mode, where INK and PAPER alternates every 32 frames
  • B holds a one for BRIGHT mode, where both INK and PAPER are a little brighter
  • P0..P2 holds a value between 0 and 7 for the PAPER colour, which is used for zeroes in the bitmap
  • I0..I2 holds a value between 0 and 7 for the INK colour, which is used for ones in the bitmap

Avoiding the "attribute clash" was tricky, and you had to really plan your artwork or your graphics algorithm to make sure that you only ever needed two distinct colours inside each 8×8 block of pixels. Artist Mark Schofield wrote an article in 2011, describing his process of planning and creating a piece of ZX Spectrum artwork.

If you are looking for more ZX Spectrum art, here are a couple of sites you might have a look at:

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Finally got around to fixing Sparky

Back in 2010, the iPad was all the rage. The first really usable tablet device, sporting lightning-fast multitouch capabilities, that were even exposed as (then non-standard) HTML5 Web APIs for JavaScript developers to play with.

It wasn't always easy to keep up with the latest trends, and trying out multitouch experiments then required an iPad. So I wrote a touch event simulation tool, called addTouch. The principle was to use the mouse to add, manipulate and remove multiple touch points, dispatching touch events to the browser.

This made it possible to develop multitouch experiences and test them using an average desktop browser. I supported both Apple's touch events and the (now extinct) Mozilla touch events.

Sparky : just a demo app

Sparky in action To demonstrate what addTouch could do, I put together a simple demonstration app called Sparky. Its principle is very simple — put your fingers on the screen to create sparking lines that you can move around. This was in the really early days of HTML5 support, and I hadn't learned Canvas drawing yet, so my implementation uses a few <div> elements and some CSS transform magic.

This has worked fine for almost six years now. I just had to fix a small issue when Apple released the iPhone 4, which had a high-dpi ("Retina") display. There were a few versions of the iOS Safari browser that had bugs when it came to handling device pixels vs logical pixels. I had to do a lot of experimenting with this, and wrote a StackOverflow answer that is currently my third most up-voted one.

What started out as just a demo app for addTouch, now has a life of its own. After my latest complete blog remake, I have been keeping a close look at the IIS logs that I get from Azure. It turns out that Sparky still gets a few hundred visits per week from different incoming sources, which is nice, but it prompted me to do some much-needed spring cleaning.

Update

The code was actually not too shabby, but some of it was really outdated. It still supported the old Mozilla touch events, and it used screen coordinates instead of local coordinates, which made it look pretty horrible in a non-fullscreen view, especially on the Android Chrome browser. That needed to change.

It was written in a certain style, specifically catered to my own old JavaScript minifier which I no longer use. Also, it polluted the global window object, which I never do anymore, unless I'm writing public APIs. That needed to change, too.

Other than that, it was just the small issue of estimating screen DPI, where I had used hard-coded values directly adapted for various iOS devices. That needed to be replaced with some more general-purpose code, and after a little head-scratching, I just decided to go with the same hard-coded value for all devices. At least for now.

The result is now live and kicking on spark.attrakt.se, and the complete code is available in the lbrtw/sparky GitHub repository.

Next step

I will try to fix some of the issues with addTouch as well, but I'll save that exercise for later. It is not very useful anymore, because the major browsers and development environments have really good touch emulation built in.

Try it out: spark.attrakt.se

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

JavaScript countdown using localStorage

New StackOverflow user Pankaj Badera asked how to create a simple JavaScript timer that would survive reboots of the user's computer.

For the specific question, this is not a good idea, because it seems as if the problem needs to deal with some sort of timing rules (school assignment deadlines, maybe?), in which case a server-side solution is a must. However, the technical aspects is pretty intriguing. As it turns out, I have already faced this problem before, when building Tajmkiper, which handles all of its timing client-side.

In principal, you can start any type of countdown, or "countup", allowing the counter to continue between browser and computer restarts, by simply recording the current time for when an event starts, and then continuously checking the difference between that saved value and the current time of a later event.

In Tajmkiper, this is done for many projects at the same time, but the principle is the same. In the case of this StackOverflow question, there is only one timer. Also, Tajmkiper is meant to be a tool for the user, and not for keeping track of (from the browser's perspective) externally checked rules like deadlines, so a solution involving local storage is fine.

My answer looks like this:

(function() {
    var started = localStorage['started'];

    if (started) {
        // This is not the first time the user opens this file
        // How long has it been?

        var diff = Date.now() - started;

        if (diff >= 1000 * 60 * 60 * 24 * 7) {
            // At least one week has passed. Do something here.
        } else {
            // Less than a week has passed. Do something else here.
        }
    } else {
        // This is the first time the user opens this file

        localStorage['started'] = Date.now();

        // Do something else here to welcome the user...
    }
})();

For my project time clock, this happens every time you select a different project. I combine this with keeping track of how much time has been clocked total.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Formatting a truncated float in JavaScript

StackOverflow user nips asked how to format a floating point number in JavaScript, truncating the value instead of rounding it.

According to the question, this was the desired result:

  • The value 12.999 must be displayed as 12.99
  • The value 14 must be displayed as 14.00

The developer.mozilla.org page gives a thorough example of how to perform decimal rounding correctly, avoiding any rounding errors in the process. For this question, however, a simple multiplication-division pair is surely good enough:

// truncate-and-format.js

// Step by step
var multiplied = value * 100;
var truncated  = Math.floor(multiplied);
var divided    = truncated * 0.01;
var output     = divided.toFixed(2);

// One-liner version
var output = (Math.floor(value * 100) * 0.01).toFixed(2);

The above code performs the following steps, in order:

  • Value is multiplied by 100
    • 12.999 => 1299.9
    • 14 => 1400
  • Value is truncated using the Math.floor function
    • 1299.9 => 1299
    • 1400 => 1400
  • Value is multiplied by 0.01
    • 1299 => 12.99
    • 1400 => 14
  • Value is formatted for printing using two decimal places using the Number.prototype.toFixed function
    • 12.99 => "12.99"
    • 14 => "14.00"

Most languages do have functions called round, ceil, floor or similar ones, but almost all of them round to the nearest integer, so the multiply-round-divide chain (or divide-round-multiply for rounding to tens, hundreds, thousands...) is a good pattern to know.

In JavaScript there is no float datatype like in a lot of other languages, but the Number type is used for both integers and floating point numbers. Internally, numbers are just 64 bit floating point numbers (source: ecma262-6.com), conforming to the IEEE 754 standard. So when dealing with numbers in JavaScript, you always need to take floating point precision into consideration!

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

About to solve an old THREE.js bug and move on with Artsy

I really need to pay more attention. Almomst a year ago, THREE.js released the r67 version, which removed the concept of centroids. This made the unfinished part 3 of Artsy break. I used centroids and the Mesh.calculateCentroid function, not because I needed to, but because some tutorial told me I should.

When the concept of centroids was removed, in April 2014, my JavaScript demos was very low on my list of priorities, but soon I will make time for fixing and advancing. Who knows, I might even be able to finish Artsy once and for all. I started working on it in October of 2013, so it's really overdue!

For now, I have removed the calls to calculateCentroid and done some small changes to at least get Part 3 to start.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Tajmkiper now public

Today I launch Tajmkiper as a publicly available, free-to-use, utility. You are welcome to use it as much as you want. If you like it, please tell your friends and colleagues about it.

It is designed mobile-first, so it really looks its best on a smaller screen, but works fine in any modern browser.

Try it out: Tajmkiper.com

Read more about it:

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Tajmkiper, part 3

Thanks to my earlier efforts (part 1, part 2 and part 3), exporting all projects to a CSV file that is saved locally is really easy. That is also the only part of the tajmkiper.com utility that I'll blog about. The complete code will be available on Github when the utility is launched for public use, so you are free to check out the complete code.

// Export to CSV

function exportToCsv() {
    // Order of properties
    var propertyOrder = ["name", "time"];

    // Create CSV exporter
    var csv = new Csv(propertyOrder);

    // Add header line
    csv.add({
        "name" : "Project",
        "time" : "Total time"
    });

    // Add all projects, using the same (omitted) formatTime function from before
    allProjects.forEach(function(project) {
        csv.add({
            "name" : project.name,
            "time" : formatTime(project.getTotalTime())
        });
    });

    // TODO: Create a formatTodaysDate function
    var filename = "tajmkiper-" + formatTodaysDate() + ".csv";
    csv.saveAs(filename);
}

Future possibilities

There is actually something that I would like to do on the server-side, that doesn't really take anything away from the beauty (?) of a purely client-side codebase. I would like to be able to transfer my projects from one browser to another. One possibility would be to simple take the JSON representation saved locally, and expose it for copy/paste, but that's not really smooth enough. I want to be able to save my projects to a server, and then load them up on another browser.

One use case for this is when I use the utility on a mobile device, but my company's time report utility uses Excel. Then I would like to open the projects on my desktop computer, export to CSV, which I then import in Excel, and keep working there.

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.

Try it: tajmkiper.com

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Tajmkiper, part 2

One important feature that I want for Tajmkiper ("time keeper" written phonetically in Swedish) is that everything should run locally, including data storage. The data model is really simple: I'll write a class called Project with only three properties: name, totalFinishedTime and startedOn

  • Project.name : The name of the project.
  • Project.totalFinishedTime : Number of seconds the project has been running in completed chunks. If this project's time clock is currently running, the totalFinishedTime does not include the number of seconds on the running clock.
  • Project.startedOn : The Unix timestamp (Date.getTime()) for when this project's time clock started, or null if the time clock is not started.

This way the project objects don't need to be constantly update every second. To present the currently elapsed time on the clock, the user interface simply adds totalFinishedTime to the number of seconds that passed since startedOn. Assuming most people don't fiddle around with the system clock, this will also make it perfectly possible to close the browser and reopen it any amount of time later, and the time clock will remember when it was started.

// First version implementation

function Project(name, totalFinishedTime, startedOn) {
    this.name = name;
    this.totalFinishedTime = totalFinishedTime;
    this.startedOn = startedOn;
}

Project.prototype.getTotalTime = function() {
    if (this.startedOn) {
        // Bitwise or with zero forces the result to be an integer
        return this.totalFinishedTime + (Date.now() - this.startedOn) / 1000 | 0;
    } else {
        return this.totalFinishedTime;
    }
};

Project.prototype.stop = function() {
    this.totalFinishedTime = this.getTotalTime();
    this.startedOn = null;
};

Project.prototype.start = function() {
    this.totalFinishedTime = this.getTotalTime();
    this.startedOn = Date.now();
};

var allProjects = [];

function saveProjects() {
    var json = JSON.stringify(allProjects);

    localStorage["projects"] = json;
}

function loadProjects() {
    allProjects = [];

    var json = localStorage["projects"];

    if (json) {
        var temp = JSON.parse(json);
        temp.forEach(function(item) {
            allProjects.push(new Projects(item.name, item.totalFinishedTime, item.startedOn));
        });
    }
}

The next step is to connect the Project objects up to some HTML generation. I could do this using a jQuery templating plugin, but i choose to do it myself, just for the hell of it.

// Second version implementation

function createProjectsHtml() {
    // First, clear any existing content of the #projects UL element
    var ul = document.getElementById("projects");
    ul.innerHTML = "";

    // Go through all projects, create html elements for each, and add them to the UL
    allProjects.forEach(function(project) {
        var li = createProjectElement(project);
        ul.appendChild(li);
    });

    // TODO: Add the #total LI element
}

function createProjectElement(project) {
    // Create an LI element, and store a reference to it in the Project object for later use
    var li = document.createElement("LI");
    project.element = li;

    // Store a reference to the Project object in the clickable link
    var a = document.createElement("A");
    a.href = "#";
    a.projectReference = project;
    li.appendChild(a);
    a.addEventListener("click", onProjectClick, false);

    var header = document.createElement("HEADER");
    header.textContent = project.name;
    a.appendChild(header);

    var timeSpan = document.createElement("SPAN");
    timeSpan.className = "time";
    a.appendChild(timeSpan);

    updateProjectElement(project);

    return li;
}

function updateProjectElement(project) {
    var li = project.element;
    var timeSpan = li.querySelector("span.time");

    var totalTime = project.getTotalTime();
    var timeText = formatTime(totalTime);
    timeSpan.textContent = timeText;

    li.className = (project.startedOn) ? "running" : "";
}

// TODO: Write a formatTime function

var running = null;

function onProjectClick(e) {
    e.preventDefault();

    // Stop the currently running project first
    if (running) {
        running.stop();
        updateProjectElement(project);
    }

    // Start the clicked project
    var project = this.projectReference;
    project.start();
    updateProjectElement(project);
    running = project;

    saveProjects();
}

Now, the projects are clickable, and the html gets updated at each click. What is still missing is a timer function to repeatedly update the running project's time display. Also, now that a reference to the LI element is stored in the Project object, we need to modify the saveProjects function, so that is doesn't try to store any HTML elements.

// Third version implementation

function saveProjects() {
    // Filter out properties to only include array indices, and those properties that need storing
    var json = JSON.stringify(array, function(key, value) {
        if (key == "name" || key == "totalFinishedTime" || key == "startedOn" || key >= 0) {
            // Only certain properties of Project are saved
            return value;
        }

        return undefined;
    });

    localStorage["projects"] = json;
}

function timerFunc() {
    if (running) {
        updateProjectElement(running);
    }

    // Wait for the next second on the clock
    var now = Date.now();
    var millis = now % 1000;
    var wait = 1000 - millis;
    window.setTimeout(timerFunc, wait);
}

document.addEventListener("DOMContentLoaded", function() {
    loadProjects();
    createProjectsHtml();
    timerFunc();
}, false);

Next step

There you have it. What is still missing?

  • Export to CSV
  • Working stop button
  • Clear all timers
  • Remove all projects

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.

Try it: tajmkiper.com

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Tajmkiper, part 1

There are lots of project time clock apps out there for those who want to keep track of how much time they spend in different projects. There's a genuine need for this type of app, but I have still not found one that is as simple and easy to use as it could be. Most of them either cost money, require that I install some piece of software on my smartphone, or demand my email address for some reason.

I would like a minimal functional web interface, designed mobile-first, that runs completely local to my browser of choice. I don't want my project data to be stored anywhere but locally in the browser. And I want to be able to export my projects and time spent to Excel. That shouldn't be so difficult to create, so I'll start hacking.

Mockup

After som pen-on-paper action, I came up with the following html/css mockup:

Tajmkiper mockup 1

<html>
<head>
  <link rel="stylesheet" href="tajmkiper.css">
</head>
<body>
  <ul id="projects">
    <li class="running">
      <a href="#">
        <header>Creating Tajmkiper</header>
        <span class="time">1:37:11</span>
      </a>
    </li>
    <li>
      <a href="#">
        <header>Killing some time</header>
        <span class="time">2:12:54</span>
      </a>
    </li>
    <li id="total">
      <a href="#">
        <header>Total:</header>
        <span class="time">3:50:05</span>
      </a>
    </li>
  </ul>
  <nav>
    <ul>
      <li id="addProject"><a href="#addProject"><header>Add new project</header></a></li>
      <li id="stopTimer" class="hideSpan"><a href="#stopTimer"><span>Stop timer</span></a></li>
      <li id="settings" class="hideSpan"><a href="#settings"><span>Tools</span></a></li>
    </ul>
  </nav>
</body>
</html>
ul, li {
    margin: 0;
    padding: 0;
    list-style: none none inside;
    text-indent: 0;
}
a {
    color: inherit;
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}
#projects {
    position: fixed;
    top: 2px;
    left: 2px;
    right: 2px;
    bottom: 36px;
    overflow-x: hidden;
    overflow-y: auto;
}
#projects li {
    position: relative;
    background: #259;
    color: #fff;
    height: 32px;
    margin-top: 2px;
    overflow: hidden;
}
#projects a.activate {
    position: absolute;
    display: block;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
}
#projects header {
    position: absolute;
    display: block;
    left: 8px;
    top: 0;
    right: 80px;
    bottom: 0;
    overflow: hidden;
    font-weight: normal;
    line-height: 32px;
    text-decoration: inherit;
}
#projects .time {
    position: absolute;
    display: block;
    right: 0px;
    width: 70px;
    padding-right: 8px;
    top: 0;
    bottom: 0;
    border-left: solid 1px #fff;
    overflow: hidden;
    line-height: 32px;
    font-size: 80
    text-decoration
    text-align: right;
    background-color: #259;
    transition: background-color 0.25s ease-in-out;
}
#projects .running .time {
    background-color: #5f3;
    color: #281;
    font-weight: bold;
}
#projects #total {
    background: #ddd;
}
#projects #total header {
    text-align: right;
}
#projects #total .time {
    color: #000;
    font-weight: bold;
}
#projects li:first-child {
    margin-top: 0;
}
li {
    border-radius: 3px;
}
nav {
    position: fixed;
    bottom: 2px;
    left: 2px;
    right: 2px;
    height: 32px;
}
nav > ul {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
}
nav li#addProject {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    right: 68px;
    background: #4a2;
    color: #fff;
}
nav li#stopTimer {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 34px;
    width: 32px;
    background: #a31 url('stop.png') no-repeat scroll 50
    color
}
nav li#settings {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 32px;
    background: #259 url('settings.png') no-repeat scroll 50
    color
}
nav a {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    padding: 0 8px 0 8px;
    line-height: 32px;
}
.hideText span {
    display: none;
}

My thought is to simply click on a project to start that project's time clock (and stop any other running time clock), click on Add new project to pop up a dialog where I enter my project's name, and then the new project's time clock starts. When I go to lunch, I click the stop button. If I want to remove all projects, set all time clocks to zero, or export everything as a CSV file (using the CSV framework I've made), I click the "settings" button in the lower right corner.

I think this looks good for now. Next time, I'll start writing some JavaScript

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.

Try it: tajmkiper.com

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Artsy part 3, SoundBox

Wow, it's been a really long time since I wrote anything about my demo projects here. I apologize for that, but the good news is that I have finally found some extra time to spend on "pleasure programming" again.

First, there's the unfinished business of Artsy (and slightly insane). About five months ago, I was finished with the first two parts, and I haven't had time to start on part three until now. So far, it looks pretty good. Unfortunately it doesn't yet work on iOS devices, but feel free to check it out:

Artsy part 3

SoundBox

Someone in the CODEF Facebook group linked to the SoundBox tool, and I find it quite amazing. It's a JavaScript-based chip tune tracker, with a really nice and small playback routine. I have been experimenting a bit with it, and here are some results:

Lots of unfinished business in this update, and I'm sorry about that.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Artsy (and slightly insane), code now public

I just made the Artsy repository public. You can check out all my commits from the first boilerplate code to the current two-part demo beta on github at /lbrtw/artsy.

In a couple of weeks I will continue adding the third part of the demo. Stay tuned! :)

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Artsy (and slightly insane), first two parts now in beta

A couple of minutes ago, I uploaded a new version of Artsy (and slightly insane). It includes JavaScript remakes of the first two parts of the iconic Amiga Demo Arte by Sanity.

The code is pure JavaScript and Canvas. There is no Flash, no Silverlight, no WebGL stuff, and there are no frameworks involved. When I'm done remaking the third and final part of Arte, my plans are to release the full source code for the demo. Also, I'll take the "plumbing" parts of the code and release as a JavaScript demo framework in itself, and I'll open-source it.

But for now, enjoy the first two parts of "Artsy (and slightly insane)". The adress is demo.atornblad.se/artsy.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Phenomenal & Enigmatic, part 4

TV Cube remake I remember seeing the "TV Cube" part of Enigma for the first time – and not really being able to figure out how it was made. Heck, I couldn't even do the math for a proper backface culling, so back in the 1990s my occational 3D objects were never any good. So the thought of making 2D and 3D objects appear on the surfaces of another 3D object was way beyond my understanding of math.

Once again, I am aware that the prettier way of doing this is by manipulating a transformation matrix to rotate, translate and project coordinates from different branches of a hierarchical coordinate system. But I ignored that and rolled it all by hand.

Star field

The stars on the front of the cube might look as if there is some depth, but that's just an illusion. Each star has an (X,Y) coordinate, and a third constant (which I called Z) that governs speed along the X axis and also the alpha component of its color. The lower the speed, the dimmer the light. When observed face on, it gives the impression of a 3D space, but it's really just a form of parallax scroller.

Pseudo code

// Star field
for (var star, i = 0; star = stars[i++];) {
    // Move the star a bit to the "right"
    star.x += (star.z * star.z * speedConstant);

    // Limit x to (-1 .. 1)
    if (star.x > 1) star.x -= 2;

    // Left out: Project the star's coordinates to screen coordinates
    var screenCoords = ( /* left out */ );

    // Draw the star, using Z to determine alpha and size
    context.fillStyle = "rgba(255,255,255," + (star.z * star.z).toFixed(3) + ")";
    context.fillRect(screenCoords.x, screenCoords.2, star.z * 2, star.z * 2);
}

Hidden line vector

Back in the days, I could never do a proper hidden line vector, because I didn't know how to properly cull back-facing polygons. For the Phenomenal & Enigmatic "TV Cube" part, I arranged all polygons in the hidden line pyramid so that when facing the camera, each polygon is to be drawn clockwise. That way I could use a very simple algorithm to determine each polygon's winding order.

I found one really efficient algorithm on StackOverflow, and I learned that since all five polygons are convex (triangles cannot be concave, and the only quadrangle is a true square), it's really enough to only check the first three coordinates, even for the quadrangle.

Rotating the pyramid in 3D space was exactly the same as with the intro part of the demo, and after all coordinates are rotated, I simple use the polygon winding order algorithm to perform backface culling, then drawing all polygons' outlines. Voilá, a hidden line vector.

Pseudo code

///Hidden line vector
// Points
var points = [
    { x : -40, y : -40, z :  70 }, // Four corners at the bottom
    { x :  40, y : -40, z :  70 },
    { x :  40, y :  40, z :  70 },
    { x : -40, y :  40, z :  70 },
    { x :   0, y :   0, z : -70 }  // And finally the top
];

// Each polygon is just an array of point indices
var polygons = [
    [0, 4, 3], // Four triangle sides
    [1, 4, 0],
    [2, 4, 1],
    [3, 4, 2],
    [3, 2, 1, 0] // And a quadrangle bottom
];

// First rotate the points in space and project to screen coordinates
var screenCoords = [];

for (var point, i = 0; point = points[i++];) {
    screenCoords.push(rotateAndProject(point)); // rotateAndProject is left out
}

// Then go through each polygon and draw those facing forward
for (var polygon, i = 0; polygon = polygons[i++];) {
    var edgeSum = 0;
    for (var j = 0; j < 3; ++j) {
        var pointIndex = polygon[j];
        var pointIndex2 = polygon[(j + 1) % 3];

        var point = screenCoords[pointIndex];
        var point2 = screenCoords[pointIndex2];

        edgeSum += (point2.x - point.x) * (point2.y + point.y);
    }

    if (edgeSum < 0) {
        // This polygon is facing the camera
        // Left out: Draw the polygon using screenCoords, context.moveTo and context.lineTo
    }
}

Plane vector

The plane vector is super-simple. Just rotating a plane around its center and then using the code already in place to project it to screen coordinates.

Projection

The function responsible for translating coordinates in the 3D space to screen coordinates is not particularly complex, since it's basically the exact same thing as for the intro part of the demo. Also, to determine which faces of the cube that are facing the camera, I just used the same backface culling algorithm as for the hidden line vector. I was really pleased with the end result.

The entire demo, including non-minified JavaScript, is available on GitHub: /lbrtw/enigmatic

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Artsy (and slightly insane), first part now in beta

In between writing about the Phenomenal & Enigmatic JavaScript demo, I'm also doing a JavaScript remake of the Arte demo by Sanity from 1993. The first effect I made was the "disc tunnel" effect, seen 2min 9sec into the YouTube clip, and the entire first part is now live, but still in beta.

The address is demo.atornblad.se/artsy, but I haven't tested it that many browsers and devices yet. I do know that it crashes on Windows Phone 7.8 after just a few scenes, but it works really nicely on my iPad 3, especially in fullscreen mode. Add a shortcut to your iPad's start screen for fullscreen mode.

I will make some changes to the bitmap tunnel effect, and make sure that the demo runs correctly on most browsers and devices. Also, stay tuned for parts 2 and 3 of Sanity Arte, and of course there will be a blow-by-blow description here on atornblad.se when the whole thing is complete.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Phenomenal & Enigmatic, part 3

When I made Phenomenal & Enigmatic, I didn't want to reuse any of the graphics art from the original demo. I had decided to take the music and some sense of the overall demo design, but didn't want to infringe on the original graphics artist's creativity, so for the Enigmatic logo, I turned to rendering the logo using code.

Original Enigma logotype One popular technique of creating good-looking logos back in the Amiga days was to first draw the logo flat, then duplicating it in a new layer a few pixels off, making that new layer translucent. Then one would paint in side surfaces and edge lines with varying levels of opacity. After adding some final touches, like surface textures or lens flares, the end result would be a glossy, glassy look, much like the original Enigma logo by Uno of Scoopex.

Enigmatic logotype The logo scene uses the same technique, painting the front, back and sides of the word ENIGMATIC as filled polygons with slightly different colors and opacity levels. During the scene, I animate some of the transformation vectors for effect. Of course, the original artwork by Uno is much better in exactly every way, but it was a fun exercise.

Pseudocode

// Logo renderer
function transformLogoCoord(chapterId, time, x, y) {
    // Left out: Perform a simple coordinate transformation
    return { x : transformedX, y : transformedY };
}

function logoMoveTo(chapterId, time, x, y, xOffset, yOffset) {
    var coords = transformLogoCoords(chapterId, time, x, y);
    context.moveTo(coords.x, coords.y);
}

function logoLineTo(chapterId, time, x, y, xOffset, yOffset) {
    var coords = transformLogoCoords(chapterId, time, x, y);
    context.lineTo(coords.x, coords.y);
}

function renderLogo(chapterId, time) {
    var xOffset, yOffset;
    // Left out: Calculate xOffset and yOffset from the chapterId and time values

    // Draw bottom surfaces
    context.beginPath();
    for (var block, i = 0; block = logoPolygons[i++];) {
        logoMoveTo(chapterId, time, block[0].x, block[0].y, 0, 0);
        for (var coord, j = 1; j = block[j++];) {
            logoLineTo(chapterId, time, coord.x, coord.y, 0, 0);
        }
        logoLineTo(chapterId, time, block[0].x, block[0].y, 0, 0);
    }
    context.closePath();
    context.fill();

    // Left out: draw side surfaces

    // Left out: draw top surfaces
}

The entire demo, including non-minified JavaScript, is available on GitHub: /lbrtw/enigmatic

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Phenomenal & Enigmatic, part 2

Stars and text intro

Phenomenal Phenomena The opening scene of Enigma by Phenomena starts out looking like an average side-scrolling star parallax, which was very normal in 1991. Nice way to lower people's expectations. :) But after just a couple of seconds, the stars begin twisting and turning in space around all three axes.

Back in 1991 I knew how to rotate a plane around the origo, by simply applying the angle sum identities of Sine and Cosine. I also realized that rotating any 3D coordinate in space could by done by simply the case of rotating around more than one axis, one axis a time.

Flash-Free enigmatic In the Phenomenal & Enigmatic demo, I only rotate the stars around two axes. First I rotate the (x,z) components around the Y axis to get (x',z'), and then the (y,z') components around the X axis to get (y',z''). I also translate each star along the X axis before the rotation takes place. To finally get the 2D screen coordinates of the 3D space coordinate, I take the (x',y') coordinate and multiply by (2/(2+z'')) for a pseudo-distance feel. The z'' value controls both the color alpha component, and the size of the rectangle being drawn.

The even better way of doing this is through vector addition and multiplication, but I'm sticking to the math that I know. :) After all this math is in place, the trick is to change the offset and rotation variables in a nice way.

Rendering text is just a couple of calls to the context.fillText method and animating the value of context.globalAlpha.

Pseudo code

// Prefetch sin and cosine of angles
var cosY = Math.cos(yAngle);
var sinY = Math.sin(yAngle);
var cosX = Math.cos(xAngle);
var sinX = Math.sin(xAngle);

for (var star, i = 0; star = stars[i++]; ) {
    // Fetch x, y, z and translate x
    var x = star.x + xOffset;
    var y = star.y;
    var z = star.z;

    // Limit x to [-1 .. 1]
    while (x > 1) x -= 2;
    while (x < -1) x += 2;

    // Rotate (x, z) around Y axis
    var x2 = x * cosY + z * sinY; // x'
    var z2 = z * cosY - x * sinY; // z'

    // Rotate (y, z') around X axis
    var y2 = y * cosX + z2 * sinX; // y'
    var z3 = z2 * cosX - y * sinX; // z''

    // Transform to screen coordinates
    var screenX = x2 * 2 / (2 + z3) * halfScreenWidth + halfScreenWidth;
    var screenY = y2 * 2 / (2 + z3) * halfScreenWidth + halfScreenHeight;

    // Draw the star
    context.fillRect(screenX, screenY, 2 - z3, 2 - z3);
}

The entire demo, including non-minified JavaScript, is available on GitHub: /lbrtw/enigmatic

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

Phenomenal & Enigmatic, part 1

Twenty-two years ago, the Amiga demo scene was extremely active. The boundaries of the little 16-bit miracle machine were stretched a little bit more for each new release, and a special breed of programmers was created. We loved efficient algorithms, we enjoyed squeezing out as much as possible from every CPU clock cycle, and we really liked showing off to each other.

Back then, I was a decent 68000 assembler programmer, but nowhere near being among the greatest. I knew my way around the Copper and the Blitter, I knew how trigonometry and vector mathematics worked for creating 3D worlds, and I understood that the "shadebobs" effect on the Amiga was nothing more than repeated full-adders, using the "Fat Agnus" chip's dedicated memory block manipulation instruction set.

My favorite demo from 1991 was Enigma by Phenomena, programmed by Olof "Azatoth" Lindroth, with music by the amazing Jimmy "Firefox" Fredriksson and Robert "Tip" Österbergh. The combination of music and direction with some really good programming set a new standard for demos on the Amiga.

First attempt

About four years ago, I started replicating the Enigma demo in C# and Silverlight 2, just as a side-project. I got as far as the opening scene and the "TV Cube" effect, which I must say I really nailed! But then I grew tired of the effort, and put the whole project aside. It just wasn't rewarding enough, but I did re-awaken some of my old "hacking the bare metal" programming skills.

Come 2013

For the last couple of weeks, I've been working from scratch, exploring what is possible using just a HTML5 AUDIO element, a CANVAS element, and a truck-load of JavaScript. Instead of trying to recreate the exact Enigma experience, I "borrowed" the amazing music, and did something of my own, inspired by Enigma.

I'll write a bit about each scene in the following weeks, but for now you're welcome to check out the fruit of my effort at demo.atornblad.se/enigmatic

The entire demo, including non-minified JavaScript, is available on GitHub: /lbrtw/enigmatic

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

JavaScript Csv file generator, part 3

The Csv file generator is almost done. All we need to do is to enclose and escape certain special characters. Values containing the separator need to be enclosed in double quotes. Values containing double quotes need to be escaped and, just to be sure, enclosed in double quotes.

Two more requirements and two more unit tests coming up...

Requirements

  • Values containing the current separator must be enclosed in double quotes.
  • Values containing double quotes must be escaped (by doubling the double quote characters), and also enclosed in double quotes.
// Tests

engine.add("Csv.getFileContents should quote fields containing the separator",
function(testContext, the) {
    // Arrange
    var properties = ["name", "age"];
    var csv = new Csv(properties);
    csv.add({"name" : "Doe, John", "age" : "Unknown"});
    csv.add({"name" : "Bunny", "age" : "2"});

    // Act
    var file = csv.getFileContents();

    // Assert
    the(file).shouldBeSameAs("\"Doe, John\",Unknown\r\nBunny,2");
});

engine.add("Csv.getFileContents should quote and escape fields containing double quotes",
function(testContext, the) {
    // Arrange
    var properties = ["model", "size"];
    var csv = new Csv(properties);
    csv.add({"model" : "Punysonic", "size" : "28\""});
    csv.add({"model" : "Philip", "size" : "42\""});

    // Act
    var file = csv.getFileContents();

    // Assert
    the(file).shouldBeSameAs('Punysonic,"28"""\r\nPhilip,"42"""');
});
// Implementation

// Helper function to create one line of text
function createTextLine(values, separator) {
    var result = [];
    var doubleQuotes = new RegExp("\"", "g");

    values.forEach(function(value) {
        var text = value.toString();

        if (text.indexOf(separator) == -1 && text.indexOf("\"") == -1) {
            result.push(text);
        } else {
            result.push("\"" + text.replace(doubleQuotes, "\"\"") + "\"");
        }
    });

    return result.join(separator);
}

// New getFileContents implementation
Csv.prototype.getFileContents = function(separator, includePropertyNames) {
    separator = separator || ",";

    var textLines = [];
    if (includePropertyNames) {
        textLines.push(createTextLine(this.propertyOrder, separator));
    }

    this.items.forEach((function(item) {
        var values = [];
        this.propertyOrder.forEach(function(propertyName) {
            values.push(item[propertyName]);
        });

        textLines.push(createTextLine(values, separator));
    }).bind(this));

    return textLines.join("\r\n");
};

And it's done! EDIT: The finished code can be found on github at /lbrtw/csv.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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:

// Csv Implementation

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.

// getFileContents implementation

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.

// saveAs implementation

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.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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:

/// Requirement code vision

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

  • The only parameter for the constructor is the order of the properties. This order should be saved.
  • The add method should add one item to the list of items to export.
  • For this purpose, the Csv object should contain an items property, containing all added items.
  • The saveAs method should use the window.saveAs function, requiring a FileSaver shim to be in place.
  • Mostly for testing purposes, the text content of the file to be generated should be accessible through the getFileContents method.
  • When calling saveAs or getFileContents, I should be able to specify which field separator to use. The default should be a comma.
  • 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.
  • For interoperability purposes, the saved file should contain the correct Byte Order Mark.
/// Tests

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.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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

  • 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 triggered on the window, it is important to clean up after oneself by removing all event listeners. These are the tests:

// 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.

// 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.

// Example

$.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

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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:

// Requirement code vision

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

  • The second argument to a test function should be the ut.The function
  • 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 type
    • 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

// Tests

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";
        }
    });
// Implementation

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.

// Tests

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!";
        }
    });
// Implementation

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.

// Tests

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");
// Implementation

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

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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

  • 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
  • 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
  • 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

// Tests

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.

// Implementation

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;
        }
    }
}
// Test run

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:

// Requirement code vision

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. `

// Tests

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:

// Implementation – TestContext

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:

// Implementation – Engine

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.

// Tests

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:

// Implementation

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

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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 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:

  • eng = new ut.Engine() should create a new unit testing engine
    • upon creating a new engine, the eng.testCount should be zero
  • 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
  • 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)
  • 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:

// Tests

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:

// Implementation

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:

// Implementation

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:

// Tests

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:

// Implementation

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.

// Tests

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.

// Implementation

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.

// Tests

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:

// Implementation

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

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this

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 = "<img src=\"image-filename\">";
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.

Posted by Anders Tornblad on Category JavaScript Labels
Tweet this