JavaScript Templates

While playing around with autotest-style QUnit, I needed something to work on that actually needed some tests. Naturally, I settled on the same problem that they did in the video: Given a string with $-placeholders, replace the placeholders with values from a context object. Here's the code I came up with:

var template = (function(){
    return {
        render: function(source, context){
            return source.replace(/\$\w+/g, function(match){
                var key = match.substring(1);
                return context[key] || "";
            }) ;
        }
    };
})();

Dead simple, right? Four actual lines of code and a bunch of setup to put it in a fake module. Here are the tests:

$(function(){
    function replace(source, expected, context, msg){
        equals(template.render(source, context), expected, msg);
    }

    module("template.render");
    test("with no tokens", function() {
        replace("", "", {},
            "returns blank string if the given string is blank"
        );

        replace("hello, world", "hello, world", {},
            "returns a non-blank string unchanged"
        );
    });

    test("with one token", function(){
        replace("$name", "", {},
            "replaces token with empty string if no value in context"
        );

        replace("$name", "Aaron", {name: "Aaron"},
            "replaces token with value from the context"
        );

        replace("Hello $name!", "Hello Aaron!", {name: "Aaron"},
            "leaves the rest of the string unchanged"
        );
    });

    test("with two tokens", function(){
        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a wonderful day.",
            {name: "Aaron", attitude: "wonderful"},
            "replaces each token with the value from the context"
        );

        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a  day.",
            {name: "Aaron"},
            "replaces each token without a value in the context with the blank string"
        );

        replace("$me$you", "AaronJohn", {me: "Aaron", you: "John"},
            "replaces adjacent tokens with their respective values"
        );
    });
});

Kind of verbose, but they're actually kind of interesting. They're written in a BDD style, or as much of a BDD style as QUnit will allow. You can easily imagine a tool that extracts:

template.render
    with no tokens
        - returns blank string if the given string is blank
        - returns a non-blank string unchanged

... from tests like these. Indeed, such tools exists for RSpec and there is a BDD JavaScript testing framework, imaginatively named: JSpec, which I expect provides similar tools for it's tests.

It was an interesting experience to work my way through the tests one at a time, implementing that simplest thing that could possibly work. Though, It is kind of discouraging that the test suite is so much longer than the implementation, especially considering this code is so easily tested.