Testing with Vitest

Develop with calm and joy through component testing.

We now have TypeScript for Eleventy with TSX as a template language. This lets us use component-driven development in 11ty. This also opens up the possibility to write tests for our components, validating they behave (and keep behaving) as expected.

For example, we can work on small chunks -- in isolation -- and work happily in tests, using Vitest. We'll start by adding a dependency and a script:

{
	"name": "eleventy-tsx",
	"version": "1.0.0",
	"description": "Demo of Eleventy 3, ESM, and TS/TSX",
	"scripts": {
		"build": "tsx node_modules/@11ty/eleventy/cmd.cjs --config=eleventy.config.ts",
		"start": "tsx node_modules/@11ty/eleventy/cmd.cjs --config=eleventy.config.ts --serve --incremental",
		"test": "vitest run"
	},
	"keywords": [],
	"author": "Paul Everitt <pauleveritt@me.com>",
	"license": "ISC",
	"type": "module",
	"dependencies": {
		"@11ty/eleventy": "^3.0.0-alpha.6",
		"jsx-async-runtime": "^0.1.8"
	},
	"devDependencies": {
		"prettier": "^3.2.5",
		"tsx": "^4.7.0",
		"vitest": "^1.5.0"
	}
}

We need to wire up Vitest in a vitest.config.js file at the root:

import { defineConfig } from "vitest/config";

export default defineConfig({
	esbuild: {
		jsx: "transform",
		jsxInject: "import { jsx } from 'jsx-async-runtime/jsx-runtime'",
		jsxFactory: "jsx",
		jsxImportSource: "jsx-async-runtime",
	},
	test: {
		include: ["./site/**/*.test.tsx"],
	},
});

This overrides the same settings used by tsx for running Eleventy builds. Vitest uses esbuild (as does tsx) but for whatever reason, doesn't respect the tsconfig.json settings without help. Big shoutout to Joaquín Sánchez from Vite/Vitest fame for figuring this out for me.

Next, let's rewrite index.11ty.tsx to have a named-export component, which we then re-export for Eleventy's render protocol for templates. This is for convenience, so you don't have all of your components named render:

export function Index(): JSX.Element {
	return <h1>Hello TSX</h1>;
}

export const render = Index;

Now we can write a test of the Index component, using Vitest. Save this in site/index.test.tsx:

import { expect, test } from "vitest";
import { renderToString } from "jsx-async-runtime";
import { Index } from "./index.11ty";

test("render index", async () => {
	const result = <Index />;
	const rendered = await renderToString(result);
	expect(rendered).toEqual("<h1>Hello TSX</h1>");
});

This test passes when we run npm test. Even better, we get full integration into the IDE's Vitest support:

Running Vitest

Having tests is great, but we can do better. We're doing string-based testing, and ideally we want to validate the resulting DOM. Curious? Find out more in the next step!