11ty (pronounced eleventy) is the most incredible static site generator available to the JavaScript community. It’s powerful, flexible, and has just enough opinions to make you immediately productive while letting you and your team build the development workflow of your dreams.

In this post, we’ll explore unit testing your 11ty views if you’ve decided to use a combination of Typescript/JavaScript templates mixed with string literals. Of course, this approach also works with JSX, which I’ll use in the code below. Let’s get started.

JavaScript Template Language

While 11ty offers a multitude of template languages, folks who are comfortable writing JavaScript and appreciate tooling help should consider using TypeScript and the JavaScript template language features of 11ty.

While I adore templating languages like Liquid, Nunjucks, and Mustache, tooling can sometimes fall short in these contexts, providing you with little information about your data model and what fields are accessible. On the other hand, when working with JavaScript templates, tooling can effectively introspect types, function parameters, and much more. The JavaScript template approach can help you identify issues before they spiral out of control. You can take it further and create all your templates using TypeScript.

Let’s see what a JavaScript template declaration might look like.

class Test {
  // or `async data() {`
  // or `get data() {`
  data() {
    return {
      name: "Ted",
      layout: "teds-rad-layout",
      // … other front matter keys
    };
  }

  render({name}) {
    // will always be "Ted"
    return `<p>${name}</p>`;
  }
}

module.exports = Test;

As you can see, a few things are happening in the previous code.

  1. There is a data getter that returns the template’s “frontmatter”.
  2. There is a render method that returns HTML.
  3. All methods are in a class definition.
  4. All methods support sync and async implementations.
  5. data() has access to this, an EleventyPage instance; the render method also has access to this along with the page’s frontmatter.

Awesome! So how do you write a test for this class?

Writing Tests For 11ty JavaScript Templates

First of all, you need to pick a unit testing environment. For simplicity’s sake, I used a combination of Vite and Vitest. I will also be using TypeScript to get an enhanced tooling experience.

Let’s look at the template we’ll be unit testing. I’ve created an Example.tsx file to use JSX.

import h, {JSX} from "vhtml";

export class Example {
    data(): Context {
        return {
            // @ts-ignore, 11ty shortcode
            greeting: this.lower("Hi")
        }
    }

    render(this: MyThis, context: Context) : JSX.Element {
        // VIP over here
        return this.name === 'Khalid'
            ? (<h1>{context.greeting} {this.name}</h1>)
            : (<h6>{context.greeting} {this.name}</h6>)
    }
}

export type MyThis = { name: string; lower: Function; }
export type Context = { greeting : string }

module.exports = Example;

I’m also defining expected types for this, which you can get from the parameters list of each method, or you can use this implicitly.

Knowing that we’re looking for specific data, we can now use our types for better tooling support and a better idea of how to stub out functionality and required data. So now, let’s take a look at the test.

// @ts-ignore
import h, {JSX} from "vhtml";
import {expect, it} from "vitest";
import { Example } from "./Example";

it('can set this on render method', () => {
    const example = new Example();
    const myThis = {
        name: "Khalid",
        lower: (i: string) => i.toLowerCase()
    };

    const data = example.data.apply(myThis);
    const result = example.render.apply(myThis, [ data ]);

    expect(result).to.equal("<h1>hi Khalid</h1>")
});

The vital part of the test is taking the methods of our template, in this case, it’s data and render, and using the apply method to change the this on each method call.

The advantage to this approach is that you can now stub and replace any functionality within your templates. In addition, using types makes it relatively straightforward to test different data scenarios, such as no elements in a collection instead of some elements.

Conclusion

There are a lot of template language choices in the 11ty toolbox, and I think you should try them all. They each have strengths and weaknesses, but it’s great that they’re all built on top of a core set of 11ty data objects. If you need to unit test your templates for complex visual logic, consider the JavaScript template language, as it’s easy to verify and fix bugs using this approach. I should note that unit tests should be an addition to your testing strategy and that you should always confirm your work by running it through 11ty itself.

Well, I hope you enjoyed this post, and as always, thank you for reading.