Technical previewShare feedback

Search

Search documentation

Writing tests

Updated Oct 31, 2026

Mark a code block in any .md file as a runnable test by adding test to its fence info string:

```ts test
expect(1 + 1).toBe(2)
```

Untagged ts blocks render normally and are not run, so you can freely mix illustrative snippets and runnable assertions in the same article.

Test names

The test name comes from the nearest preceding heading. Two blocks under the same heading are auto-suffixed #1, #2, and so on. A block with no preceding heading uses the file's basename; YAML front matter never counts as a heading. To override naming explicitly, see Per-block options.

Auto-injected helpers

describe, it, and expect are injected at the top of the generated test module — you don't need to import them. Doctest bodies stay clean:

```ts test
const items = [1, 2, 3]
expect(items.length).toBe(3)
```

Imports

Any import statements inside a runnable block are hoisted to the module top and shared across every test in the same .md file. Multi-line imports work because the parser uses a real TypeScript AST (ts-morph), not a regex.

Hoisted imports load before any block runs, so a block's setup code cannot prepare them; put such setup in the runner's setup files. Two blocks may import the same name from the same module, but binding one name to two different imports fails the file. An import used only by skip blocks, or by blocks an only block leaves out, is loaded on a best-effort basis, so a module that doesn't exist yet cannot fail the tests that run.

```ts test
import { sum } from "./sum.ts"

expect(sum(1, 2)).toBe(3)
```

What a block cannot hold

Each block's body runs inside an async test function, so anything that must sit at a module's top level fails there: export, a nested describe or it, and vi.mock (vitest hoists it only from the top level). Put mocks in the runner's setup files, and keep one test per block.

Supported languages

Only ts and tsx fences are picked up. js/jsx blocks render but are not run — keep tests typed. Irondocs erases the types itself, so no TypeScript transform needs to cover .md files. JSX in a tsx block compiles to the automatic React runtime, so react/jsx-runtime must resolve from the docs folder.

Created with ❤ and Livemark