describe(me) docs
early v0.12.1 browser mode · jsdom Live · Foundations
Examples npm GitHub

API

Most projects need only the plugin and now and then step(). This page lists every entry point of the four packages and everything each one exports.

@describe-me/vitest #

The Vitest integration: the plugin, the two setup files, the reporter, step(), and the server and page runtime of the live preview. Your vitest.config.ts imports the plugin, and your tests import step().

@describe-me/vitest #

Import from it in a test or in a setup file of your own. Both names come from @describe-me/core, re-exported so that a test needs only this package.

Export Signature What it is for
step step<T>(label: string, fn: () => T | Promise<T>): Promise<T> Names a phase of the test and takes a frame after fn resolves. Writing stories shows how to use it.
recorder const recorder: Recorder The test recorder of the page. A setup file of your own calls recorder.configure(). Its methods are listed under @describe-me/core.

@describe-me/vitest/setup #

The setup file for browser mode. It exports nothing. Importing it:

The plugin adds it to setupFiles in browser mode. Without the plugin shows how to list it yourself.

@describe-me/vitest/setup-dom #

The setup file for jsdom and other DOM environments. It exports nothing and never imports vitest/browser. Importing it:

The plugin adds it to setupFiles in DOM environments. Without the plugin shows how to list it yourself.

@describe-me/vitest/reporter #

The Vitest reporter that writes the output directory. The plugin creates it for you. You import it in vitest.config.ts only to wire describe-me by hand.

Export Signature What it is for
DescribeMeReporter new DescribeMeReporter(options?: DescribeMeReporterOptions) The default export, a Vitest reporter. It writes manifest.json, foundations.json, snapshots/, styles/ and assets/, as How it works explains.
DescribeMeReporterOptions { outDir?: string; include?: string | string[]; exclude?: string | string[]; stories?: 'all' | 'tagged'; previewHead?: string; setupWarnings?: string[]; foundations?: false | { theme?: string }; sidebar?: { groups?: Record<string, string | string[]> } } Its options, all optional. Without the plugin explains each one.

@describe-me/vitest/plugin #

The Vite plugin, in plugins of vitest.config.ts.

Export Signature What it is for
describeMe describeMe(options: DescribeMeOptions = {}): VitePlugin The whole integration in one line. It adds the setup file and the reporter, and redirects the render import to the recording adapter. What the plugin changes lists every change. It exposes api.live.protocol and api.live.enabled, false with live: false, for the live preview, whose server loads the config with the plugin in live mode.
DescribeMeOptions interface DescribeMeOptions { … } Its options, all optional. Options lists each one.
DescribeMeEnvironment 'browser' | 'dom' Where the tests run: Vitest browser mode, or a simulated DOM such as jsdom. The type of the environment option.

@describe-me/vitest/live #

The server of the live preview and its static build, for Node. They are used by the CLI, not for import in tests, and they never load the runtime of the live page.

Export Signature What it is for
createLiveServer createLiveServer(options: LiveServerOptions): Promise<LiveServer> Starts a second Vite dev server on the project's own Vite and Vitest config, in mode test, on localhost. It throws when the config has no describeMe() plugin, or one of another version. With live: false in the config it throws an error whose code is DESCRIBE_ME_LIVE_OFF, and starts nothing.
LiveServerOptions { root: string; configFile?: string; port?: number } The project root, the config file, relative to root or absolute, and the port. Without a config file it takes the one Vitest picks, and without a port a free one.
LiveServer { base: string; protocol: number; close(): Promise<void> } base is the URL the live page is under, such as http://localhost:5173/__describe-me/. protocol is the plugin's api.live.protocol.
buildLive buildLive(options: LiveBuildOptions): Promise<LiveBuild> Builds the live page into static files with the project's own Vite and config, in mode test, as a development build with a relative base (the static live preview). It throws as createLiveServer does, before anything is bundled. With live: false it writes nothing.
LiveBuildOptions { root: string; configFile?: string; outDir: string; testFiles: string[] } The project root, the config file as for the server, where the page goes (emptied first) and the test files the page can import: the module ids of the manifest, relative to root.
LiveBuild { protocol: number } The plugin's api.live.protocol.

@describe-me/vitest/live-runtime #

The runtime of the live page. The modules that the plugin generates in live mode import it. It is used by the live preview, not for import in tests.

Export Signature What it is for
liveExport liveExport(module: 'vitest' | 'vitest/browser' | '@describe-me/vitest', name: string): unknown One export of a module the page swaps for a shim: describe, it, the hooks, vi or step, and an inert value for every other name.
startLivePage startLivePage(options: LivePageOptions): Promise<void> Runs the page: reads its URL, imports the test file, runs the arrange phase until the first render and reports how it ended.
LivePageOptions { importTest(file: string): Promise<unknown>; globals: boolean; testTimeout: number; setupFiles?: { file: string; load(): Promise<unknown> }[]; vitestMajor?: number } How to import a test file, the project's test.globals and test.testTimeout, the setup files to import first, each with its name for messages (default: none), and the major version of Vitest whose rules name each and for rows (default: 5).

@describe-me/react #

The recording render for React and the live preview's stand-ins for it, the only code of describe-me that knows React. Your tests keep importing vitest-browser-react or @testing-library/react, and the plugin sends those imports here. You import this package yourself only without the plugin.

@describe-me/react #

For tests in browser mode. The plugin redirects imports of vitest-browser-react here, so your tests keep their import.

Export Signature What it is for
render render(ui: ReactElement, options?: RenderOptions): Promise<Screen> Drop-in for render of vitest-browser-react, async like it, with its options and its screen. It records a frame after mount and after every rerender, and names the component and its props.
step step<T>(label: string, fn: () => T | Promise<T>): Promise<T> The same step() as in @describe-me/vitest.

Everything else of vitest-browser-react is re-exported as it is, so the adapter can stand in for the whole module.

@describe-me/react/testing-library #

For tests in jsdom and other DOM environments. There, the plugin redirects imports of @testing-library/react here, so your tests keep their import.

Export Signature What it is for
render render(ui: ReactElement, options?: RenderOptions): RenderResult Drop-in for Testing Library's render, synchronous like it. It records a frame after mount and after every rerender.
fireEvent const fireEvent: FireEvent Testing Library's fireEvent, the function and each method. Every call records one frame, labelled like click(button "Save"). The enter and over events, such as mouseEnter, hover their element in the frame, and the leave and out events end that hover.
cleanup cleanup(): void Takes the closing frame, then unmounts. With afterEach(cleanup) in a test file, the last frame shows the last state, not an empty page.
step step<T>(label: string, fn: () => T | Promise<T>): Promise<T> The same step() as in @describe-me/vitest.

Everything else of @testing-library/react is re-exported as it is. Importing this entry point hands unmounting to the recorder, which runs it after the closing frame. The plugin switches Testing Library's own cleanup off with RTL_SKIP_AUTO_CLEANUP. Importing it also lets the setup file take user-event frames without a wait, while Testing Library's asyncWrapper stays configured.

@describe-me/react/live #

The live preview's stand-in for vitest-browser-react in browser mode. The plugin's live mode redirects imports of vitest-browser-react here. It is used by the live preview, not for import in tests, and it imports neither vitest-browser-react nor Testing Library.

Export Signature What it is for
render render(ui: ReactNode, options?: LiveMountOptions): Promise<unknown> Mounts ui with createRoot, in options.wrapper, then rejects with the stop signal, which ends the test at its first render. For an empty UI it resolves to a stand-in screen whose rerender mounts.
renderHook renderHook(): Promise<never> Stops the test without a mount, so the page reports no-render.
cleanup cleanup(): void Does nothing. The component stays mounted.

@describe-me/react/live-testing-library #

The live preview's stand-in for @testing-library/react in jsdom projects. The plugin's live mode redirects imports of @testing-library/react here. It is used by the live preview, not for import in tests, and it never imports vitest/browser.

Export Signature What it is for
render render(ui: ReactNode, options?: LiveMountOptions): unknown Synchronous like Testing Library's render: it mounts, then throws the stop signal. For an empty UI it returns a stand-in result with Testing Library's queries, whose rerender mounts.
renderHook renderHook(): never Throws the stop signal without a mount, so the page reports no-render.
cleanup cleanup(): void Does nothing. The component stays mounted.

Everything else of @testing-library/react is re-exported as it is.

@describe-me/core #

The part that knows no framework: the recorder, step(), the types of the manifest and the state of the live preview page. The other packages depend on it, so tests rarely import it. Adapters for other frameworks and tools that read manifest.json do.

@describe-me/core #

For adapters, setup files and tools. A test imports step and recorder from @describe-me/vitest instead.

Export Signature What it is for
cssReferences cssReferences(cssText: string): CssReference[] Every url() and @import target in CSS text, in source order. Also in @describe-me/core/css-references.
CssReference { url: string; start: number; end: number; kind: 'url' | 'import' } One target, as written, with its offsets in the scanned text.
describeComponentType describeComponentType(type: unknown): Pick<ComponentInfo, 'name' | 'file'> Names a component type the way its author would: the export the plugin registered, then displayName, then the function name, then the same for what a memo or forwardRef wraps. Anonymous when nothing names it.
elementLabel elementLabel(target: unknown): string | undefined A readable target for a frame label, such as button "show details": the role and the name of an element. undefined for anything that is not an Element.
manifestDiagnostics manifestDiagnostics(manifest: Manifest): ManifestDiagnostics What a manifest could not document. Also in @describe-me/core/diagnostics.
recorder const recorder: Recorder The test recorder. There is one per page, kept on globalThis, even when two copies of this package are loaded. Its methods are below.
serializeValue serializeValue(value: unknown, depth = 0): unknown Turns props into readable, JSON-safe values. A function becomes ƒ name, a React element <Name />, and anything nested deeper than three levels ….
step step<T>(label: string, fn: () => T | Promise<T>): Promise<T> Names a phase of the test and takes a frame after fn resolves.

The methods of recorder that a test or a setup file calls:

Method Signature What it is for
configure configure(options: RecorderOptions): void Sets when the render frame is taken, what runs around each wait and where the tests run. The setup files call it with the plugin's options. Without the plugin, you call it in a setup file of your own.
setComponent setComponent(info: ComponentInfo): void Names the component the test documents. The first name stays, unless it is Anonymous and the new one is not, or the new one adds a file.
flush flush(): Promise<boolean> Waits for the captures in flight, then takes a render frame that renderFrame held back. Resolves to whether it took one.

Adapters and setup files call the rest of class Recorder in packages/core/src/recorder.ts: begin(), capture(), beforeInteraction(), noteGesture(kind, target?), end(), onTeardown() and teardown(), and they read generation, isActive and protocol. An adapter calls noteGesture() after each interaction and before its frame, with what the interaction did to hover and focus. The recorder's protocol is 3. One test, step by step shows when each one runs.

The root entry point also exports the 49 names of @describe-me/core/types, which describes each one: FrameKind, ComponentInfo, RegisteredExport, EXPORT_REGISTRY_KEY, ASSET_URL_PREFIX, STYLE_URL_PREFIX, PSEUDO_STATES_ATTRIBUTE, PseudoState, RecorderEnvironment, GestureKind, RenderFrameMode, AroundWait, RecorderOptions, CaptureOptions, Frame, TestRecord, META_KEY, DOCS_TAG, NO_DOCS_TAG, TestState, ManifestFrame, ManifestTest, ManifestModule, PropKind, PropDoc, ComponentDoc, ManifestGenerator, Manifest, FontMissing, RemoteStylesheet, ANONYMOUS_COMPONENT, ManifestDiagnostics, DesignSystemId, FoundationGroup, FoundationTokenType, FoundationToken, FoundationsSheet, FoundationsSystem, Foundations, ManifestFoundations, LiveStatus, LIVE_MESSAGE_SOURCE, LIVE_PROTOCOL, LiveReport, LiveMessage, VIEWER_MESSAGE_SOURCE, DarkVariantRoot, ViewerRequest and ViewerMessage.

@describe-me/core/types #

For tools that read manifest.json, and for adapters. It holds no DOM code, so it is safe to import from Node.

Export Shape What it is for
FrameKind 'render' | 'action' | 'step' | 'end' What took a frame: a render, an interaction, a step() or the end of the test.
ComponentInfo { name: string; props: Record<string, unknown>; file?: string } The component a test documents and the props it was rendered with. file is its source file, when the plugin registered its exports.
RegisteredExport { name: string; file: string } What the plugin registers for each top-level export of a project module: the export name and the module, relative to the project root.
EXPORT_REGISTRY_KEY 'describe-me.exports' The Symbol.for() key of the registry on globalThis, a WeakMap<object, RegisteredExport>. A global, because a project module cannot always resolve @describe-me/core.
ASSET_URL_PREFIX 'describe-me-asset:' The prefix of project asset URLs in stored snapshots, followed by the stored file name. The viewer resolves it against __data/assets/.
STYLE_URL_PREFIX 'describe-me-style:' The prefix of stylesheet references in stored snapshots, followed by the hashes of the sheet's chunks, joined with +.
PSEUDO_STATES_ATTRIBUTE 'data-describe-me-states' The attribute that holds an element's states in a stored snapshot, as names separated by spaces, always in the order hover focus focus-visible focus-within. Only the serialized tree has it, never the page.
PseudoState 'hover' | 'focus' | 'focus-visible' | 'focus-within' A pseudo-class state that a frame records for an element.
RecorderEnvironment 'browser' | 'dom' Where the tests run, which decides how a frame reads hover and focus. These are the values of the plugin's environment option.
GestureKind 'hover' | 'unhover' | 'pointer' | 'keyboard' What a gesture did, for recorder.noteGesture(). hover rests the pointer on an element, unhover ends the hover, pointer also ends keyboard mode and keyboard starts it.
RenderFrameMode 'eager' | 'lazy' | { pending: string; timeout?: number } When the render frame is taken. Render frame timing explains each mode.
AroundWait (wait: () => Promise<void>) => Promise<void> Runs one wait of the recorder and must call wait once. The jsdom setup file uses it to switch React's act flag off while the recorder waits.
RecorderOptions { renderFrame?: RenderFrameMode; aroundWait?: AroundWait; environment?: RecorderEnvironment } The options of recorder.configure(). aroundWait and environment are kept when omitted, and environment is 'dom' until a setup file sets it.
CaptureOptions { settle?: boolean; generation?: number } The options of recorder.capture(). settle: false takes the snapshot at once, for DOM that is already committed. A generation from an earlier test drops the capture.
Frame { id: string; kind: FrameKind; label: string; at: number; meta?: Record<string, unknown>; snapshot: string } A frame as the recorder took it. snapshot is the serialized document as JSON text, at the time since the test began, in ms.
TestRecord { frames: Frame[]; component?: ComponentInfo; origin?: string } What a test hands to the reporter. origin is the page's location.origin, which the reporter needs to find project assets.
META_KEY 'describeMe' The key of task.meta that carries a test's record.
DOCS_TAG 'docs' The tag that picks a test for the docs under stories: 'tagged'. The viewer leaves it out of a test's tags.
NO_DOCS_TAG 'no-docs' The tag that keeps a test out of the docs. The viewer leaves it out too.
TestState 'passed' | 'failed' | 'skipped' | 'pending' The result of a test in the manifest.
ManifestFrame { id: string; kind: FrameKind; label: string; at: number; meta?: Record<string, unknown>; snapshot: string } A frame in the manifest. snapshot is the path of its file, relative to the manifest. The manifest describes it.
ManifestTest { id, vitestId?, name, path, fullName, tags?, state, duration?, errors?, component?, frames } A test in the manifest, with an id that stays the same across runs and machines. The manifest describes each field.
ManifestModule { id: string; group?: string[]; tests: ManifestTest[] } A test file, by its path from the project root, its sidebar group and its recorded tests.
PropKind 'literals' | 'boolean' | 'number' | 'string' | 'function' | 'node' | 'other' How tests can cover a prop, decided by its type.
PropDoc { name: string; type: string; required: boolean; defaultValue?: string; kind: PropKind; values?: string[]; description?: string } One prop of a component, read from its TypeScript type. Writing stories shows how the viewer uses it.
ComponentDoc { name: string; file: string; props: PropDoc[] } What the viewer shows about a component beyond its tests.
ManifestGenerator { name: string; version: string } The package and the version that wrote a manifest.
Manifest { version, generator?, generatedAt, root, configFile?, project?, modules, sidebar?, components, assetsMissing?, head?, fontsMissing?, remoteStylesheets?, setupWarnings?, foundations? } The whole of manifest.json. The manifest describes every field.
FontMissing { family: string; tests: number; testId: string } A font family that frames use but nothing loads, how many tests miss it, and the first of them.
RemoteStylesheet { host: string; frames: number } A host that frames load stylesheets from, and how many frames do.
ANONYMOUS_COMPONENT 'Anonymous' The name adapters give a component that has no usable name.
ManifestDiagnostics { anonymous, undocumented, assetsMissing, fontsMissing, remoteStylesheets, setupWarnings, foundationsErrors } What keeps a manifest from documenting everything its tests rendered. manifestDiagnostics() computes it, and Troubleshooting explains each key.
DesignSystemId 'tailwind' | 'shadcn' | 'mui' | 'chakra' | 'mantine' | 'radix-themes' | 'styled-components' | 'emotion' | 'antd' A design system the reporter can name.
FoundationGroup 'colors' | 'typography' | 'spacing' | 'radius' | 'shadows' | 'breakpoints' | 'motion' | 'other' The section of the Foundations view a token belongs to.
FoundationTokenType 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'number' | 'duration' | 'cubicBezier' | 'shadow' | 'typography' What a token's value is, named after the $type values of DTCG.
FoundationToken { name, group, type?, value, modes?, origin, modeOrigins?, source, parts? } One token from the Tailwind theme or the theme module. value is CSS text, the light one when modes holds light and dark. origin is default for a framework default: Tailwind's DEFAULT flag, or a Material UI value that equals Material UI's own default in every mode and every part. modeOrigins holds the origin per color scheme when exactly one mode equals the default, and origin is then project.
FoundationsSheet { cssText: string; source?: string } A recorded stylesheet, as the snapshots keep it, with the data-vite-dev-id of its <style>.
FoundationsSystem { id: DesignSystemId; package?: string; version?: string } A design system the project uses, and the installed version of its package.
Foundations { version, systems, shadcn?, tokenSheets, fontSheets, cssSources, tailwind?, theme?, tokens, errors, duration } The whole of foundations.json. The output directory says what it holds.
ManifestFoundations { systems: DesignSystemId[]; tokens: number; errors?: string[] } What the manifest says about foundations.json. The manifest describes it.
LiveStatus 'mounted' | 'no-render' | 'not-found' | 'import-failed' | 'setup-failed' | 'failed' How the live preview page ended up. The live preview explains each one.
LIVE_MESSAGE_SOURCE 'describe-me-live' The source of every message the live page posts.
LIVE_PROTOCOL 1 The version of the contract between the viewer, the plugin's live mode and the live page. createLiveServer() refuses a plugin whose api.live.protocol differs. An additive message such as dark-variant or background keeps it, because the side that does not know a message ignores it.
LiveReport { type: 'status'; status; detail? } | { type: 'size'; height } | { type: 'action'; name; args } | { type: 'error'; message } | { type: 'notice'; message } | { type: 'background'; html; body } What the live page reports, without the source field.
LiveMessage LiveReport & { source: typeof LIVE_MESSAGE_SOURCE } A message the live page posts to the viewer around it.
VIEWER_MESSAGE_SOURCE 'describe-me-viewer' The source of every message the viewer posts to the live page.
DarkVariantRoot { className?: string; attribute?: { name: string; value: string } } What turns a project's dark variant on at the root element: a class such as 'dark', a data-* attribute such as data-theme="dark", or both.
ViewerRequest { type: 'dark-variant'; root: DarkVariantRoot | null } What the viewer asks of the live page, without the source field. root: null takes back what the page applied.
ViewerMessage ViewerRequest & { source: typeof VIEWER_MESSAGE_SOURCE } A message the viewer posts to the live page in its frame. A page ignores a type it does not know.

@describe-me/core/diagnostics #

For a tool that reads manifest.json and wants the same list of problems as the reporter and the viewer.

Export Signature What it is for
manifestDiagnostics manifestDiagnostics(manifest: Manifest): ManifestDiagnostics Lists anonymous components, named ones without props, assets that will not load, fonts that nothing loads, hosts of remote stylesheets, setup warnings and problems reading the design-system foundations (foundationsErrors). The reporter prints the list after a run, and the viewer shows it in its header.

@describe-me/core/css-references #

For a tool that scans CSS text for the files it loads. The reporter and describe-me build use it to find assets and fonts.

Export Signature What it is for
cssReferences cssReferences(cssText: string): CssReference[] Every url() and @import target in CSS text, in source order, remote, data: and #fragment URLs included. Comments and other strings are skipped, so content: "url(x)" yields nothing.
CssReference { url: string; start: number; end: number; kind: 'url' | 'import' } One target. url is the URL as written, without quotes. start and end are its offsets in the scanned text. kind is import for an @import target.

@describe-me/core/live #

The state of the live preview page and its messages to and from the viewer. It is used by the live preview, not for import in tests. The state lives on globalThis, so two copies of this package share it, as they share the recorder.

Export Signature What it is for
postLiveMessage postLiveMessage(report: LiveReport): void Posts a report to window.parent, only to the origin that the page URL names, and only when that is exactly the origin of an http: or https: URL.
reportLiveStatus reportLiveStatus(status: LiveStatus, detail?: string): void Reports how the page ended up. The first status is final. It sets <html data-describe-me-live>, applies the viewer's held request, starts the background observer, posts the status, writes any status but mounted on the page and starts the height observer.
listenToViewer listenToViewer(): void Listens to the viewer's dark-variant requests, only from window.parent and only from the origin that the page URL names. It holds a request until the page has a status, then puts its class and data-* attribute on <html>, and takes back only what it applied. It runs once per page.
liveStatus liveStatus(): LiveStatus | null The status reported so far.
liveStop liveStop(): Error The page's one stop signal, which the live render throws.
isLiveStop isLiveStop(value: unknown): boolean Tells the stop signal apart from a failure.
liveInert liveInert(): unknown A value that takes any call, new and property, and ends an await. The shims give it for every name they do not implement.
observeLiveHeight observeLiveHeight(): void Posts the height of the content now and whenever it changes: the bottom of the lowest painted element, plus a margin. It runs once per page.

describe-me #

The viewer and its CLI. The package has no entry point to import: its package.json has a bin and no exports. You run it from a script or the command line.

describe-me #

The binary describe-me, in a package.json script or through your package manager. Viewer and CLI lists its commands and flags.

dev
Serves the viewer on your output directory and updates it whenever the reporter writes a new manifest. It also starts the live preview, which mounts tests in Live.
build
Builds a static site: the viewer, with the output directory copied in as __data/. With --live it builds the live preview into the site too, so the site shows Live.
--help
Prints the usage, as does describe-me without a command.