
# 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`:

```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](/guide/testing/) covers the rest of `@zerolith/test`.

Run the tests:

```sh
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.

