Tested live documentation is a thing with MDX, Docz and Cypress

You can quickly write live interactive component documentation and make sure it works as expected by testing it.

I love documenting things. I hate when my documentation is out of date. For unit tests I have built a tool called xplain to convert unit tests into Markdown examples to be included in README. But what about UI components? Turns out you can have an interactive documentation for your components with a new tool called docz that is just a pleasure to use. Currently docz supports only React components, but support for other frameworks might be coming.

Live documentation with Docz

docz uses MDX document format which is a Markdown enhanced with JSX. If you want to see Docz in action, take a look at the short intro video by clicking the link below the image.

See docz preview video by clicking on this link

Great, so we can write component documentation really quickly, and our documentation site is interactive! Take a look at the example repo bahmutov/cy-docz - just a couple of components, with Button component showing "onClick" handler that does "alert" message. Clone the repo, start the docz server with npm run dev and open localhost:3000 - the documentation is live

Button onClick triggers alert

Here is the relevant part from the Button.mdx

1
2
3
4
5
6
7
8
9
10
11
12
13
---
name: Button
menu: Components
---

import { Playground } from 'docz'
import Button from './Button'

## With onclick handler

<Playground>
<Button onClick={() => alert('hi there')}>Shows alert</Button>
</Playground>

Tip: you can create "index" page by creating a MDX file with "route /" meta setting.

1
2
3
4
5
6
7
---
name: Index page
route: /
order: 1
---

This will be the index page

And now let us make sure our documentation is never out of date with respect to the source component. We will test it - we will add end-to-end tests to our live documentation using the Cypress.io test runner.

Testing documentation

I will place my spec files right in components folder to be next to the source code Button.jsx and its documentation Button.mdx files.

1
2
3
4
5
6
components/
Alert.jsx
Alert.mdx
Button-spec.js
Button.jsx
Button.mdx

And I will point Cypress to load *-spec.js files from this folder, while ignoring .jsx or .mdx files using cypress.json configuration file. I also set the viewport width to be wide to avoid Docz menu covering the buttons (docz output is not yet responsive)

cypress.json
1
2
3
4
5
6
{
"integrationFolder": "components",
"ignoreTestFiles": "*.*x",
"viewportWidth": 1400,
"baseUrl": "http://localhost:3000"
}

Here is a test to confirm the documentation page shows alert message when the user clicks the button. The test assumes the docz size is running locally via npm run dev command.

Button-spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// <reference types="cypress" />
//@ts-check
describe('Button', () => {
beforeEach(() => {
cy.visit('/components-button')
})

it('shows alert on click', () => {
const stub = cy.stub()
cy.on('window:alert', stub)

cy.get('h2#with-onclick-handler + button')
.click()
.then(() => {
expect(stub).to.have.been.calledOnce
})
})
})

When we run this test it ... shows no alert dialog, because Cypress automatically intercepts it.

Button alert test

But you can still see that the Button documentation behaves as expected - the Cypress command list shows the stubbed invocation.

Alert was stubbed and called

Great! We can sleep better knowing that our documentation is really working. You can find a few more tests I wrote against Button.mdx in components/Button-spec.js

Running tests

How can we run docz and test it quickly? We need to start the docz server, wait for it to finish bundling (it might take a few seconds even for small projects), then run Cypress tests. There is a utility start-server-and-test I wrote that makes it easy. After installing it as a dev dependency, here are my package scripts

1
2
3
4
5
6
7
8
9
{
"scripts": {
"test": "start-test dev http-get://localhost:3000 cy:run",
"dev": "docz dev",
"build": "docz build",
"cy:open": "cypress open",
"cy:run": "cypress run"
}
}

Two observations:

  • I just need to execute npm test to make sure my documentation is correct
  • docz is using webpack dev server under the hood, which does not reply to HEAD requests. Thus I need to use GET requests to ping the server to know when it's ready. Thus the url to ping is http-get://localhost:3000 and not simply http://localhost:3000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
$ npm t

> [email protected] test /Users/gleb/git/cy-docz
> start-test dev http-get://localhost:3000 cy:run

starting server using command "npm run dev"
and when url "http-get://localhost:3000" is responding
running tests using command "cy:run"

> [email protected] dev /Users/gleb/git/cy-docz
> docz dev

β„Ή info Removing old app files
β„Ή info Creating new docz files
β„Ή info Setup entries socket on port 8089


DONE Compiled successfully in 4766ms 10:58:27

I You application is running at http://localhost:3000

> [email protected] cy:run /Users/gleb/git/cy-docz
> cypress run



=================================================================================

(Run Starting)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Cypress: 3.0.1 β”‚
β”‚ Browser: Electron 59 (headless) β”‚
β”‚ Specs: 1 found (Button-spec.js) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


─────────────────────────────────────────────────────────────────────────────────

Running: Button-spec.js... (1 of 1)
βœ“ has menu bar (narrow screen) (1124ms)


4 passing (6s)


(Results)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Tests: 4 β”‚
β”‚ Passing: 4 β”‚
β”‚ Failing: 0 β”‚
β”‚ Pending: 0 β”‚
β”‚ Skipped: 0 β”‚
β”‚ Screenshots: 0 β”‚
β”‚ Video: true β”‚
β”‚ Duration: 5 seconds β”‚
β”‚ Spec Ran: Button-spec.js β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


(Video)

- Started processing: Compressing to 32 CRF
- Finished processing: /Users/gleb/git/cy-docz/cypress/videos/Button-spec.js.mp4 (0 seconds)


=================================================================================

(Run Finished)


Spec Tests Pass… Fail… Pend… Skip…
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ βœ” Button-spec.js 00:05 4 4 - - - β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
All specs passed! 00:05 4 4 - - -

Deploying docs

docz comes with build command that can package entire documentation and components into a static site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ npm run build

> [email protected] build /Users/gleb/git/cy-docz
> docz build

β–Ά start Creating an optimized production build...
βœ” success Compiled successfully.

File sizes after gzip:

138.67 KB dist/static/js/vendors.360b5044.js
1.36 KB dist/static/js/components-button.d13b64c5.js
1.15 KB dist/static/js/runtime~app.0f53f534.js
1023 B dist/static/js/components-alert.b0a4263a.js
950 B dist/static/js/app.7db074eb.js

We can deploy the docs to GitHub pages using gh-pages. Here are the relevant scripts from package.json, and because I plan to host the docs at https://glebbahmutov.com/cy-docz I need to pass base parameter so that all resources are prefixed with /cy-docz in the generated HTML

package.json
1
2
3
4
5
6
7
{
"scripts": {
"build": "docz build --base /cy-docz/",
"predeploy": "npm run build",
"deploy": "gh-pages -d .docz/dist"
}
}

You can see the deployed documentation at https://glebbahmutov.com/cy-docz url. Best feature - we can test the deployed documentation just to make sure the build process did not break our documentation (that would be awful, right?)

So we add one more command to our package.json file to run Cypress tests against the public site by passing baseUrl as a CLI argument - check out the script "test-deployed" below:

package.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"test": "start-test dev http-get://localhost:3000 cy:run",
"test-deployed": "cypress run --config baseUrl=https://glebbahmutov.com/cy-docz",
"dev": "docz dev",
"build": "docz build --base /cy-docz/",
"cy:open": "cypress open",
"cy:run": "cypress run",
"predeploy": "npm run build",
"deploy": "gh-pages -d .docz/dist"
}
}

After each deploy we validate the documentation using Cypress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ npm run test-deployed

> [email protected] test-deployed /Users/gleb/git/cy-docz
> cypress run --config baseUrl=https://glebbahmutov.com/cy-docz

Running: Button-spec.js... (1 of 1)


Button
βœ“ shows on the docz page (1099ms)
βœ“ shows alert on click (1953ms)


2 passing (3s)

Great, live interactive documentation that is tested with very little effort - just use docz and Cypress together.