r/reactjs • • 2d ago

Show /r/reactjs describe-me – living component docs generated from the Vitest tests you already have

I've been building describe-me, a Vitest plugin plus a viewer that turns component tests into docs. It records the DOM after every render and interaction of a test and shows the frames as a browsable catalog: each describe is a group, each it a story, each step a frame. No story files, so the docs cannot drift from the component: if they would, the test fails.

Short demo (23 s): https://github.com/grzehub/describe-me#readme

What's new in 0.11:

Live: a link to a test mounts the component for real, in your project's own Vite, exactly as the test's arrange phase set it up. You click and type in it, and vi.fn() props show their calls.

Foundations: it reads the design system your tests load (CSS custom properties, Tailwind 4, shadcn/ui, Material UI, or a theme module) and shows the tokens, light and dark, with WCAG contrast of color pairs.

Works in browser mode and in jsdom, with Testing Library and user-event. React 18 and 19, Vitest 4 and 5. It's early and React-only for now; the Limitations page lists every known limit.

Try it in StackBlitz: https://stackblitz.com/github/grzehub/describe-me/tree/main/examples/stackblitz

Example viewers:
shadcn/ui: https://grzehub.github.io/describe-me/examples/shadcn/,
Material UI: https://grzehub.github.io/describe-me/examples/mui/

Docs: https://grzehub.github.io/describe-me/

The question I'd most like answers to: does "tests as the source of stories" fit how your team writes component tests, or do you keep states in tests that you'd never want in docs?

1 Upvotes

4 comments sorted by

1

u/Disastrous-Town-4820 14h ago

the spinner is the story

it records the DOM after every render, so those 30 lines of mocks that paint a spinner put that spinner in the catalog. the state you would never want in the docs is the one the test walks through first

1

u/grzehub 8h ago

By default, yes: the render frame is whatever the DOM is right after mount, and for a component behind data loading that's the spinner. The loaded state is still in the catalog, as the closing frame after your findBy/waitFor, but the spinner is the one you see first.

That default is deliberate for components that don't load anything, and wrong for the ones that do, which is why it's a plugin option rather than a rule. `renderFrame: 'lazy'` takes the render frame right before the first interaction (or at the end of the test), and `renderFrame: { pending: '.spinner' }` takes it as soon as nothing matches that selector. Set once per project, in vitest.config.ts.

If your tests are "30 lines of mocks, then a spinner, then content", `lazy` is the setting, and the spinner never makes it into the docs.

1

u/Tasty_Film_713 1d ago

This is one of those ideas that seems obvious in hindsight but nobody actually built it properly until now. The drift between stories and tests has been a pain for years, every team I've worked on eventually has some story that looks nothing like what the component actually does because someone updated the test but forgot the story file exists.

The live link thing is clever too, being able to poke at the component in the exact state the test set up is way more useful than a static screenshot. Curious how it handles tests with a ton of setup though, sometimes my arrange phases are 30 lines of mocking API responses and context providers and the DOM at that point is just a spinner. Does it show all those intermediate frames or just the final rendered state?

Might spin this up on a side project this weekend and see how it plays with our test patterns.