0.1.0-alpha.7

Testing

The starter came with a test for its counter, in test/counter.browser.test.ts, and npm test runs it in a real browser with Vitest. A test for the packing list works the same way: mount the page, act on it the way a person would, and check what changed.

Create test/packing.browser.test.ts:

import { afterEach, beforeEach, expect, it } from "vitest";
import { accessibilityIssues, cleanup, mount } from "@zerolith/test";
import { Packing } from "../src/components/packing.ts";
import { items } from "../src/store.ts";

beforeEach(() =>
{
    items.set([
        { id: 1, name: "Passport", packed: true },
        { id: 2, name: "Phone charger", packed: false },
        { id: 3, name: "Rain jacket", packed: false }
    ]);
});
afterEach(cleanup);

it("checks an item off and counts it as packed", () =>
{
    const { container } = mount(Packing());
    expect(container.querySelector("p")?.textContent).toBe("1 of 3 packed");
    const boxes = container.querySelectorAll<HTMLInputElement>("li input");
    boxes[1]?.click();
    expect(container.querySelector("p")?.textContent).toBe("2 of 3 packed");
});

it("adds an item from the form", () =>
{
    const { container } = mount(Packing());
    const form = container.querySelector("form");
    const field = container.querySelector<HTMLInputElement>("input[name=name]");
    if (form === null || field === null)
    {
        throw new Error("the page has no form");
    }
    field.value = "Sunscreen";
    form.requestSubmit();
    expect(container.querySelectorAll("li")).toHaveLength(4);
    expect(field.value).toBe("");
});

it("names every control", () =>
{
    const { container } = mount(Packing());
    expect(accessibilityIssues(container)).toEqual([]);
});

mount from @zerolith/test renders a view into a container attached to the document and returns it; cleanup, registered after each test, removes every mounted view. Since a click updates the page at once, the test checks the result on the next line, with no waiting.

beforeEach resets the items, because the list is module state shared by every test in the file and kept in storage between runs. Setting it explicitly at the start of each test makes the tests independent of each other and of what you clicked in the browser.

accessibilityIssues finds the defects a test can see without an accessibility tree: images without alternative text, form controls without a label, and buttons or links without a name. The remove buttons pass because they have text; their aria-label goes further and says which item each one removes, which no test can judge. Delete the <label> around a checkbox and the third test fails, naming the control. The testing guide covers the rest of @zerolith/test.

Run the tests:

npm test

What you learned

  • mount renders a view for a test and cleanup removes it.
  • A click updates the page at once, so a test checks the result directly.
  • accessibilityIssues catches unnamed controls and missing labels in every test run.