CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--vue3

Storybook Vue 3 renderer for developing, documenting, and testing UI components in isolation

Pending
Overview
Eval results
Files

portable-stories.mddocs/

Portable Story Composition

Create reusable story components for testing and external usage, allowing Storybook stories to be composed and rendered outside of the Storybook environment. This enables story reuse in unit tests, documentation, and other applications.

Capabilities

Set Project Annotations

Configure global Storybook project settings that apply to all composed stories, including decorators, parameters, and global configuration.

/**
 * Function that sets the globalConfig of your Storybook. The global config is the preview module of
 * your .storybook folder.
 * 
 * It should be run a single time, so that your global config (e.g. decorators) is applied to your
 * stories when using composeStories or composeStory.
 * 
 * @param projectAnnotations - E.g. (import projectAnnotations from './.storybook/preview')
 */
function setProjectAnnotations(
  projectAnnotations: NamedOrDefaultProjectAnnotations<any> | NamedOrDefaultProjectAnnotations<any>[]
): NormalizedProjectAnnotations<VueRenderer>;

Usage Example:

// setup-file.js
import { setProjectAnnotations } from "@storybook/vue3";
import projectAnnotations from "./.storybook/preview";

setProjectAnnotations(projectAnnotations);

Compose Single Story

Create a composed component from a single story and its metadata, combining all args, parameters, decorators, and configuration into a renderable Vue component.

/**
 * Function that will receive a story along with meta (e.g. a default export from a .stories file)
 * and optionally projectAnnotations e.g. (import * from '../.storybook/preview) and will return a
 * composed component that has all args/parameters/decorators/etc combined and applied to it.
 * 
 * It's very useful for reusing a story in scenarios outside of Storybook like unit testing.
 * 
 * @param story - The story object or function to compose
 * @param componentAnnotations - E.g. (import Meta from './Button.stories')
 * @param [projectAnnotations] - E.g. (import * as projectAnnotations from '../.storybook/preview')
 *   this can be applied automatically if you use setProjectAnnotations in your setup files.
 * @param [exportsName] - In case your story does not contain a name and you want it to have a name.
 */
function composeStory<TArgs extends Args = Args>(
  story: StoryAnnotationsOrFn<VueRenderer, TArgs>,
  componentAnnotations: Meta<TArgs | any>,
  projectAnnotations?: ProjectAnnotations<VueRenderer>,
  exportsName?: string
): JSXAble<ComposedStoryFn<VueRenderer, Partial<TArgs>>>;

Usage Example:

import { render } from "@testing-library/vue";
import { composeStory } from "@storybook/vue3";
import Meta, { Primary as PrimaryStory } from "./Button.stories";

const Primary = composeStory(PrimaryStory, Meta);

test("renders primary button with Hello World", () => {
  const { getByText } = render(Primary, { props: { label: "Hello world" } });
  expect(getByText(/Hello world/i)).not.toBeNull();
});

Compose Multiple Stories

Create composed components from all stories in a story module, returning an object with all stories as renderable Vue components.

/**
 * Function that will receive a stories import (e.g. import * as stories from './Button.stories')
 * and optionally projectAnnotations (e.g. import * from '../.storybook/preview') and will return
 * an object containing all the stories passed, but now as a composed component that has all
 * args/parameters/decorators/etc combined and applied to it.
 * 
 * It's very useful for reusing stories in scenarios outside of Storybook like unit testing.
 * 
 * @param csfExports - E.g. (import * as stories from './Button.stories')
 * @param [projectAnnotations] - E.g. (import * as projectAnnotations from '../.storybook/preview')
 *   this can be applied automatically if you use setProjectAnnotations in your setup files.
 */
function composeStories<TModule extends Store_CSFExports<VueRenderer, any>>(
  csfExports: TModule,
  projectAnnotations?: ProjectAnnotations<VueRenderer>
): MapToJSXAble<Omit<StoriesWithPartialProps<VueRenderer, TModule>, keyof Store_CSFExports>>;

Usage Example:

import { render } from "@testing-library/vue";
import { composeStories } from "@storybook/vue3";
import * as stories from "./Button.stories";

const { Primary, Secondary } = composeStories(stories);

test("renders primary button with Hello World", () => {
  const { getByText } = render(Primary, { props: { label: "Hello world" } });
  expect(getByText(/Hello world/i)).not.toBeNull();
});

test("renders secondary button", () => {
  const { getByRole } = render(Secondary);
  expect(getByRole("button")).toBeInTheDocument();
});

Advanced Usage

Testing with Composed Stories

import { render, fireEvent } from "@testing-library/vue";
import { composeStories } from "@storybook/vue3";
import * as stories from "./Button.stories";

const { Primary, WithAction } = composeStories(stories);

describe("Button Component", () => {
  test("primary button renders correctly", () => {
    const { getByRole } = render(Primary);
    const button = getByRole("button");
    expect(button).toHaveClass("btn-primary");
  });

  test("button click triggers action", async () => {
    const onClickSpy = vi.fn();
    const { getByRole } = render(WithAction, {
      props: { onClick: onClickSpy },
    });
    
    const button = getByRole("button");
    await fireEvent.click(button);
    expect(onClickSpy).toHaveBeenCalled();
  });
});

Story Composition with Custom Args

import { composeStory } from "@storybook/vue3";
import Meta, { Default } from "./Button.stories";

const CustomButton = composeStory(Default, Meta);

// Use with custom props
const wrapper = render(CustomButton, {
  props: {
    label: "Custom Label",
    size: "large",
    variant: "danger",
  },
});

Type Definitions

type JSXAble<TElement> = TElement & {
  new (...args: any[]): any;
  $props: any;
};

type MapToJSXAble<T> = {
  [K in keyof T]: JSXAble<T[K]>;
};

interface ComposedStoryFn<TRenderer extends Renderer, TArgs> {
  (args?: Partial<TArgs>): TRenderer['storyResult'];
  storyName?: string;
  args?: TArgs;
  parameters?: Parameters;
  argTypes?: ArgTypes;
}

Install with Tessl CLI

npx tessl i tessl/npm-storybook--vue3

docs

app-setup.md

component-testing.md

index.md

portable-stories.md

story-types.md

tile.json