Package Exports
- generate-jsdoc-example-tests
- generate-jsdoc-example-tests/lib/app.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (generate-jsdoc-example-tests) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
generate-jsdoc-example-tests
Generate test files from your JSDoc @example comments. See configuration details in the vitest cookbook and adapt it to your favorite test runner π.
It offers you the certainty that your JSDoc @example are valid.
Why β TL;DR
Full announcement here.
I love documenting my functions using JSDoc examples β imo example are the best doc one can have! β but I was tired of my doc getting obsolete. And tests don't provide in-IDE doc, so I figured: why not reconcile the 2 ?
Write JSDoc examples, and generate tests from themβ¦
- To make sure examples never go obsolete π.
- β¦ And to test my stuff like I would anyway, you sillies π€
So here comesβ¦ generate-jsdoc-example-tests π.
Prior art
- tsdoc-testify β see Thanks. Very old and unmaintained project, an excellent base to build this one π.
- jsdoc-spec β both an example parser and the test runner, without the test runner goodies.
- ts-docs β same, on the test side, it is both an example parser and a test runner, without the test runner goodies. Stuck to TypeScript v4.
Installation
npm i -D generate-jsdoc-example-tests
# or directly via npx without installation:
npx generate-jsdoc-example-testsDemo
You document your functions, methods, constants & classes, for coworkers or your future self, you get tests for free:
Documenting a function
// src/date-formatter.ts
/**
* @example
* ```ts
* import { formatDateYear } from './date-formatter'
*
* expect(formatDateYear(new Date('2026-01-01')).toBe('2026')
* ```
*/
export function formatDateYear(date: Date) {β¦}Generate tests:
npx gen-jet 'src/**' \
--header 'import { expect, test } from "vitest"' \
--test-file-extension '.example.test' # do not provide the `.ts` or `.js`Generated test:
// src/date-formatter.example.test.ts
// DO NOT EDIT β¦
import { expect, test } from 'vitest' // the provided header
import { formatDateYear } from './date-formatter'
test('Example 1', () => {
expect(formatDateYear(new Date('2026-01-01'))).toBe('2026')
})Documenting a class
// src/date-formatter.ts
class DateFormatter {
/**
* @example
* ```ts
* import { DateFormatter } from './date-formatter'
*
* const formatter = new DateFormatter()
* expect(formatter.formatYear(new Date('2026-01-01')).toBe('2026')
* ```
*/
formatYear(date: Date) {β¦}
}Generate tests:
npx gen-jet 'src/**' \
--header 'import { expect, test } from "vitest"' \
--test-file-extension '.example.test' # do not provide the `.ts` or `.js`Generated test:
// src/date-formatter.example.test.ts
// DO NOT EDIT β¦
import { expect, test } from 'vitest' // the provided header
import { DateFormatter } from './date-formatter'
test('Example 1', () => {
const formatter = new DateFormatter()
expect(formatter.formatYear(new Date('2026-01-01'))).toBe('2026')
})Documenting a constant
// src/date-formatter.ts
/**
* @example
* ```ts
* import { formatDate, yearFormat } from './date-formatter'
*
* const date = new Date('2026-01-01')
* expect(formatDate(date, yearFormat)).toBe('2026')
* ```
*/
export const yearFormat = 'YYYY'
export const formatDate = (date: Date, format: string): string => {β¦}Generate tests:
npx gen-jet 'src/**' \
--header 'import { expect, test } from "vitest"' \
--test-file-extension '.example.test' # do not provide the `.ts` or `.js`Generated test:
// src/date-formatter.example.test.ts
// DO NOT EDIT β¦
import { expect, test } from 'vitest' // the provided header
import { formatDate, yearFormat } from './date-formatter'
test('Example 1', () => {
const date = new Date('2026-01-01')
expect(formatDate(date, yearFormat)).toBe('2026')
})Documenting an interface / a type
// src/date-formatter.ts
interface DateFormatter {
/**
* @example
* ```ts
* import { makeDateFormatter } from './date-formatter'
*
* const formatter = makeDateFormatter()
* expect(formatter.formatYear(new Date('2026-01-01')).toBe('2026')
* ```
*/
formatYear(date: Date): string
}
export const makeDateFormatter = (): DateFormatter => ({
formatYear: () => {β¦},
})Generate tests:
npx gen-jet 'src/**' \
--header 'import { expect, test } from "vitest"' \
--test-file-extension '.example.test' # do not provide the `.ts` or `.js`Generated test:
// src/date-formatter.example.test.ts
// DO NOT EDIT β¦
import { expect, test } from 'vitest' // the provided header
import { makeDateFormatter } from './date-formatter'
test('Example 1', () => {
const formatter = makeDateFormatter()
expect(formatter.formatYear(new Date('2026-01-01'))).toBe('2026')
})Usage
CLI
gen-jet: gen = generate ; jet = Jsdoc + Example + Tests.
$ npx gen-jet src/
$ npx gen-jet src/,other-root/
# Usage with options:
$ npx gen-jet src/ \
--test-file-extension '.example.test' \
--test-function-name 'it' \
--header 'import { it, expect } from "vitest | jest | whatever"'
--header 'import { myGlobalImport } from "~/some-global-stuff"'
--include-example-containing expect,assert,assertStrict
--watch
# For a full CLI usage, checkout
$ gen-jet --helpProgrammatic API
import { generateTests, type GenerateOptions } from 'generate-jsdoc-example-tests'
generateTests(['./src', './other-root']) // the folders are resolved from process cwd.
.then(() => console.info('tests generated'))
.catch(console.error)
const myOptions: GenerateOptions = { β¦ }
generateTests(['./src',], myOptions)
.then(() => console.info('tests generated'))
.catch(console.error)
Vitest
import { generateTests } from 'generate-jsdoc-example-tests'
generateTests(['./src', './other-root']) // the folders are resolved from process cwd.
.then(() => console.info('tests generated'))
.catch(console.error)
generateTests('./src', {
testFileExtension: '.generated.test', // default is '.example.test' ; do not provide `.ts` or `.js`
testFunctionName: 'it', // default is 'test'
headers: ['import { it, expect } from "vitest"'],
// keywords the JSDoc @example body must contain to be included in the generated tests.
includeExampleContaining: ['expect('], // default is ['assert.', 'assert(', 'expect']
watch: false, // <-- enable watch mode here.
})
.then(() => console.info('tests generated'))
.catch(console.error)FAQ
Which tests are included ?
There is a includeExampleContaining option, defaulted to ['expect(', 'assert.', 'assert('].
Any @example content containing expect(, assert. or assert( will have a generated test.
If you want to omit a test, you can omit it with @skipTest:
/**
* @example This one is included because it contains `expect`
* ```ts
* import { myFn } from './my-fn'
* expect(myFn()).toBe(true)
* ```
* @example This is omitted because there is no `expect` or `assert`
* ```ts
* myFn('toto') // invalid arg.
* ```
* @example this one is explicitly omitted {@skipTest}
* ```ts
* import { myFn } from './my-fn'
* expect(myFn()).toBe(false)
* ```
*/
export const myFn = () => trueDoes it generate JS or TS files?
The test files are generated according to their source file:
- if the source file is JS, the generated test file will be JS.
- if the source file is TS, the generated test file will be TS.
I want to name my examples
Your examples are named by default if you provide a title:
/**
* β¬οΈ example title
* @example sum 4 and 5
* ```
* import assert from "assert";
* import { sum } from "./sample";
*
* assert.equal(sum(4, 5), 9);
* ```
*/
export function sum() {β¦}Generated test file:
import assert from "assert";
import { sum } from "./sample";
test("sum 4 and 5", () => {
assert.equal(sum(4, 5), 9);
})Thanks
I based this work upon Akito Ito's awesome tsdoc-testify and pushed it further with options and test runner interop.
Many MANY thanks to you Akito Ito ππ
License
Same as the original one:
This project is licensed under the Apache License 2.0 License - see the LICENSE file for details
Contributing
First of all, thank you deeply if you want to participate. Please visit the contributing section for a detailed guide (getting started, roadmap).
If you like the project but don't have time to contribute, all good ! There are other ways to support the project and show your appreciation, which we would also be very happy about:
- Star the project
- Tweet (Bsky) about it
- Refer this project in your projectβs readme
- Mention the project at local meetups and tell your friends/colleagues