hTest

Declarative unit testing, for everyone

Running tests in the browser (as HTML reftests)

These tests provide a UI very similar to HTML-first reftests, and can be useful for providing a unified front if your testsuite includes both.

Running JS-first tests in the browser

To run HTML-first tests in the browser, you just open the HTML file. For JS-first tests, you need to create an HTML file that runs the tests.

For a test file named test.js, the HTML file would look like this:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Tests</title>
	<link rel="stylesheet" href="https://htest.dev/htest.css" crossorigin />
</head>
<body>
	<script type="module">
		import "https://htest.dev/htest.js";
		import render from "https://htest.dev/src/js/render.js";
		import test from './test.js';
		render(test);
	</script>
</body>
</html>

You could even use a URL param to specify the test file, so you can use one HTML file for all your tests:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Tests</title>
	<link rel="stylesheet" href="https://htest.dev/htest.css" crossorigin />
</head>
<body>
	<script type="module">
		import "https://htest.dev/htest.js";
		import render from "https://htest.dev/src/js/render.js";
		let params = new URLSearchParams(location.search);
		let test = await import(params.get('test'));
		render(test);
	</script>
</body>
</html>

Then you'd run it foo.js by visiting index.html?test=foo.js.

In fact, you could configure it to output an index of tests if no test is provided, encapsulating your entire testsuite in one HTML file!

View code
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Tests</title>
	<link rel="stylesheet" href="https://htest.dev/htest.css" crossorigin />
	<script src="https://htest.dev/htest.js" type="module" crossorigin></script>
	<script>
		let params = new URLSearchParams(location.search);
		let test_url = params.get('test');

		if (test_url) {
			let test_url_ext = test_url.match(/\.(\w+)$/)?.[1];

			if (!test_url_ext) {
				test_url += '.js';
			}

			if (/^\w+\.\w+$/.test(test_url)) {
				test_url = `./${test_url}`;
			}

			Promise.all([
				import("https://htest.dev/src/js/render.js").then(m => m.default),
				import(test_url).then(m => m.default),
			]).then(([render, test]) => render(test));
		}
		else if (parent === self) {
			document.documentElement.classList.add('index');

			// Index of all tests
			fetch('./index.json').then(r => r.json()).then(index => {
				index = Object.entries(index).map(([id, name]) => ({ id, name }));

				document.body.innerHTML = `
				<section>
					<h1>Tests</h1>

					<ul id="tests">
						${index.map(test => `
							<li>
								<a href="?test=${test.id}">${test.name}</a>
							</li>
						`).join('\n')}
					</ul>
				</section>
				`;
			});
		}
	</script>
</head>
<body>
</body>
</html>

You can see this in action at Color.js. Note that this requires an index.json with test filenames to names. You can also hardcode the data in your index, or use a different format — you'd just need to tweak the code accordingly.

When viewing JS-first tests in HTML, top-level tests are rendered as sections, and all their descendants are rendered inside the same table.

Test runner UI

Isolating tests

It is often useful to isolate a single group of tests, or even a single test so you can debug a particular failure.

To isolate a group of tests (<section>), simply click the link of the section heading.

To isolate a specific test (<tr>), hold down the Alt/Option key and double click on the table row.