1# @effect/doctest
2
3`@effect/doctest` extracts marked TypeScript examples from JSDoc comments, Markdown, and MDX files, then runs each example as an isolated Vitest module.
4
5## Installation
6
7Install Vitest 5 (`>=5.0.0 <6.0.0`) and Vite `>=8.1.5 <9.0.0` alongside the package:
8
9```sh
10npm install -D @effect/doctest vitest@^5 vite@^8.1.5
11```
12
13Vitest 5 supports Node.js `^22.12.0 || ^24.0.0 || >=26.0.0`. Existing snippet markers and doctest configuration remain supported. When upgrading, import reporter types from `vitest/node`; JSON reports now default to a file, so configure `outputFile` explicitly when consuming the report. See the [Vitest migration guide](https://vitest.dev/guide/migration/) for upstream changes.
14
15## Links
16
17- [Website](https://effect.website): documentation, guides, and news.
18- [Reference](https://effect.website/docs/v4/api/doctest): API documentation for this package.
19- [Discord](https://discord.gg/effect-ts): ask questions, share what you're building, and talk to the core team.
20- [Community](https://effect.website/community-hub): meetups and events, or bring Effect to your own.
21- [Issues](https://github.com/Effect-TS/effect/issues): bug reports and feature requests.
22
23## Let's talk
24
25Whether your team is considering Effect, rolling it out, or already running it in production, we'd love to hear from you: what you're building, what works, and what you need from Effect next.
26
27- **Talk to the maintainers.** Introduce your team on [Discord](https://discord.gg/effect-ts) or email [contact@effectful.co](mailto:contact@effectful.co). We're happy to connect privately on Slack or Discord for feedback and help with adoption.
28- **Production support.** We're exploring how to better support teams running Effect in production. If your organization has specific support needs, let's discuss them.
29- **Adoption help.** Our [adoption partners](https://effect.website/adoption-partners) offer implementation, consulting, team extension, training, and commercial support.
30
31## Usage
32
33Mark runnable fences with `import.meta.vitest`:
34
35````ts
36/**
37 * ```ts import.meta.vitest name="adds two numbers"
38 * 1 + 1 // => 2
39 * ```
40 */
41export const value = 1
42````
43
44The optional `name="..."` metadata labels the test without appearing in the example body. Unnamed examples use the opening fence line, such as `line 12`; Vitest displays the containing file alongside it.
45
46## Inline assertions
47
48Add a trailing `// =>` comment to assert the value of an expression:
49
50````ts
51/**
52 * ```ts import.meta.vitest
53 * import { Array, Option } from "effect"
54 *
55 * Array.get([1, 2, 3], 1) // => Option.some(2)
56 * Array.get([1, 2, 3], 10) // => Option.none()
57 * ```
58 */
59export const value = 1
60````
61
62The expected value is a TypeScript expression evaluated in the same lexical scope. Values are compared with Effect's `Equal.equals` semantics, so the convention supports primitives, arrays, plain objects, and Effect data types such as `Option`, `Result`, `Exit`, and `HashMap` without converting them to console output.
63
64Prefer asserting the API call directly instead of introducing a binding used only by the assertion. Keep bindings for reuse or meaningful multi-step setup, with a blank line before a later assertion block. Keep the call and assertion on one line when it fits within 120 characters, and format expected arrays densely, for example `[1, 2]`, `[[1], [2]]`, and `Option.some([1, 2])`. Preserve runnable markers on type-level examples without adding tautological runtime assertions.
65
66An assertion may also trail a single initialized `const` declaration with an identifier binding. The initializer is evaluated once and the binding remains available to subsequent code:
67
68````ts
69/**
70 * ```ts import.meta.vitest
71 * import { Effect, Option } from "effect"
72 *
73 * const result = await Effect.runPromise(Effect.succeed(Option.some(1))) // => Option.some(1)
74 * Option.isSome(result) // => true
75 * ```
76 */
77export const value = 1
78````
79
80Markers must trail a complete expression statement or supported `const` declaration on the same line. Standalone markers, destructuring declarations, multiple declarations, and `let` or `var` declarations are not supported. The transform does not implicitly await promises, run Effects, or consume iterators; write those operations explicitly. Ordinary comments are ignored. Await asynchronous work so all assertions and cleanup occur before the snippet module finishes evaluating.
81
82Regular tests can use `include` in the same project. Documentation sources use `includeSource`, which lets Vitest discard files without the marker before collection. The plugin resolves imports relative to each example's original TypeScript, Markdown, or MDX file:
83
84```ts
85import * as Doctest from "@effect/doctest/Plugin"
86import { defineConfig } from "vitest/config"
87
88export default defineConfig({
89 plugins: [Doctest.plugin()],
90 test: {
91 include: ["test/**/*.test.ts"],
92 includeSource: ["src/**/*.ts", "docs/**/*.{md,mdx}"]
93 }
94})
95```
96
97Source files selected by `includeSource` are collected through generated doctest collectors and are not executed. Native in-source tests using `import.meta.vitest` are therefore not supported by this plugin. Regular test files included through `test.include` continue to run normally.
98
99The plugin configures `@effect/doctest/Runner` when no test runner is specified. If `test.runner` is already configured, the plugin leaves it unchanged; that runner is then responsible for integrating doctest collection when required.