> [Agent navigation index](/llms.txt)

# Testing components and HTTP consumers

Use managed Chromium component tests for Preact components, islands, stores, and browser hooks. Use contract-aware MSW handlers when the subject calls HTTP; do not substitute them for route-harness tests.

## Render a browser component

Name the file `*.client.test.tsx` and import browser helpers through Brix:

```tsx
import { describe, expect, it } from "brix/test";
import { render, userEvent } from "brix/test/browser";
import { BookingButton } from "./BookingButton.tsx";

describe("BookingButton", () => {
  it("submits a booking", async () => {
    const screen = render(<BookingButton />);
    await userEvent.click(screen.getByRole("button", { name: "Book" }));
    await expect.element(screen.getByText("Booked")).toBeVisible();
  });
});

```

`brix/test/browser` exports `render`, `renderWithTranslations`, `page`, and `userEvent`. Locators match text exactly and case-sensitively by default; use `toMatchTextContent` for partial text or regular expressions. Await interactions and browser assertions.

Mount the production translation provider when the component uses translation hooks:

```tsx
renderWithTranslations(<CheckoutButton />, {
  locale: "sv",
  messages: { "checkout.submit": "Fortsätt" },
  isProduction: false,
});

```

The renderer is Preact-only. Managed React applications test their components in the React application's browser toolchain.

## Test stores and hooks through a consumer

Render a small component that calls the hook and exposes meaningful state through accessible UI. Wrap it in the real generated provider when testing a store:

```tsx
function CartSummary() {
  const cart = useCartStore();
  return <button onClick={() => cart.add("sku-123")}>Items {cart.items.value.length}</button>;
}

render(
  <CartStoreProvider initialItems={[]}>
    <CartSummary />
  </CartStoreProvider>,
);

await userEvent.click(page.getByRole("button", { name: "Items 0" }));
await expect.element(page.getByRole("button", { name: "Items 1" })).toBeVisible();

```

This verifies provider setup, signals, rerendering, and user-visible behavior together. For a hook without visible output, use a tiny local test component to expose its result, but prefer behavior over internal implementation state. Use `vi.fn` or `vi.spyOn` for callback boundaries; do not replace modules.

## Mock HTTP consumers with MSW

`mockContract` and `mockContractError` produce typed MSW handlers for `executeApi`, `useApi`, browser components, and other HTTP-consuming code. Initialize the version-matched browser worker once:

```sh
brix msw init public --save

```

```tsx
import { afterAll, afterEach, beforeAll, expect, it } from "brix/test";
import { mockContract, mockContractError } from "brix/test";
import { setupWorker } from "brix/msw/browser";
import { page, render, userEvent } from "brix/test/browser";
import { getItemsApiContract } from "./contract.ts";
import { ItemList } from "./ItemList.tsx";

const worker = setupWorker();

beforeAll(() => worker.start({ onUnhandledRequest: "error" }));
afterEach(() => worker.resetHandlers());
afterAll(() => worker.stop());

it("loads items", async () => {
  worker.use(
    mockContract(getItemsApiContract, {
      data: [{ id: "item-1", name: "Backpack" }],
    }),
  );

  render(<ItemList />);
  await userEvent.click(page.getByRole("button", { name: "Load items" }));
  await expect.element(page.getByText("Backpack")).toBeVisible();
});

it("shows an API error", async () => {
  worker.use(mockContractError(getItemsApiContract, 503));
  render(<ItemList />);
  await userEvent.click(page.getByRole("button", { name: "Load items" }));
  await expect.element(page.getByRole("alert")).toBeVisible();
});

```

For an HTTP consumer running in the Node project, use the same contract mocks with `setupServer` from `brix/msw/node`, then `listen` in `beforeAll`, `resetHandlers` in `afterEach`, and `close` in `afterAll`.

Mock success `data` is required when the contract declares a response and omitted for an empty response such as `{ status: 204 }`. Fixtures use response-schema input, are JSON-normalized and validated, and produce parsed output in the client. A request-aware sync or async resolver receives typed `params`, parsed `body` and `query`, plus the original `request`:

```ts
mockContract(updateItemApiContract, {
  status: 201,
  data: ({ params, body, request }) => ({
    id: params.itemId,
    name: body.name,
    requestId: request.headers.get("x-request-id"),
  }),
});

```

Use `baseUrl` to pin a handler to an external origin; the default matches any origin. `mockContractError(contract, status, { data?, baseUrl? })` preserves explicit falsy JSON error bodies and otherwise returns `{ error: "Mock <status> error" }`.

Do not use these handlers to test the corresponding Brix route. Use [the route harness](/docs/testing-routes), which exercises middleware, environment, auth, rendering, and server error handling.

## Debug browser tests

Run one component file or the browser project:

```sh
brix test src/features/checkout/BookingButton.client.test.tsx
brix test --project browser
brix test src/features/checkout/BookingButton.client.test.tsx --browser.traceView --reporter=html

```

Generated traces, reports, screenshots, and attachments go under `.vitest/`. Commit reference screenshots under `__screenshots__/`, not generated `.vitest/` output. Use `brix test --repeats 20 <test-file>` to reproduce order-independent flakes.
