Documentation tests
Ende can evaluate the code snippets written in your JSDoc comments and markdown files and run them as tests. This keeps the examples in your documentation honest: when an API changes, its outdated examples fail in CI instead of misleading readers.
Example code blocks
/**
* # Examples
*
* ```ts
* import { assertEquals } from "jsr:@ende/assert/equals";
*
* const sum = add(1, 2);
* assertEquals(sum, 3);
* ```
*/
export function add(a: number, b: number): number {
return a + b;
}The triple backticks mark the start and end of code blocks, the language is determined by the language identifier attribute which may be one of the following:
jsjavascriptmjscjsjsxtstypescriptmtsctstsx
If no language identifier is specified then the language is inferred from media type of the source document that the code block is extracted from.
ende test --doc example.ts
The above command will extract this example, turn it into a pseudo test case that looks like below:
import { assertEquals } from "jsr:@ende/assert/equals";
import { add } from "file:///path/to/example.ts";
Ende.test("example.ts$4-10.ts", async () => {
const sum = add(1, 2);
assertEquals(sum, 3);
});and then run it as a standalone module living in the same directory as the module being documented.
Want to type-check only?
If you want to type-check your code snippets in JSDoc and markdown files
without actually running them, you can use
ende check command with --doc option (for
JSDoc) or with --doc-only option (for markdown) instead.
Exported items are automatically imported
Looking at the generated test code above, you will notice that it includes the
import statement to import the add function even though the original code
block does not have it. When documenting a module, any items exported from the
module are automatically included in the generated test code using the same
name.
Let's say we have the following module:
/**
* # Examples
*
* ```ts
* import { assertEquals } from "jsr:@ende/assert/equals";
*
* const sum = add(ONE, getTwo());
* assertEquals(sum, 3);
* ```
*/
export function add(a: number, b: number): number {
return a + b;
}
export const ONE = 1;
export default function getTwo() {
return 2;
}This will get converted to the following test case:
import { assertEquals } from "jsr:@ende/assert/equals";
import { add, ONE }, getTwo from "file:///path/to/example.ts";
Ende.test("example.ts$4-10.ts", async () => {
const sum = add(ONE, getTwo());
assertEquals(sum, 3);
});Hashbang and narrowed permissions
If a code example starts with a hashbang, it is
validated against supported Ende cli flags, and permissions flags are forwarded
to the generated Ende.test.
/**
* Prints the value of an environment variable.
*
* ```ts
* #!/usr/bin/env -S ende run --allow-env=MY_ENV_VAR
* console.log(Ende.env.get("MY_ENV_VAR"));
* ```
*/
Below is how each permission flag is interpreted:
| Flag | Generated permissions |
|---|---|
--allow-all |
Inherits all permissions from ende test |
--allow-* |
Inherits the specified permission from ende test |
--allow-*=… |
Restrict the specified permission to provided values |
--deny-* |
Explicitly revoke the specified permission |
--deny-*=… |
Currently unsupported (results in a test failure) |
--permission-set |
Currently unsupported (ignored) |
--ignore-* |
Currently unsupported (ignored) |
| No permission flags | None |
Note
A code example never runs with broader permissions than what ende test was
granted, even if the hashbang itself specifies broader ones.
Skipping code blocks
You can skip the evaluation of code blocks by adding the ignore attribute.
/**
* This code block will not be run.
*
* ```ts ignore
* await sendEmail("[email protected]");
* ```
*/
export async function sendEmail(to: string) {
// send an email to the given address...
}