Skip to content

[Bug]: SB10 - CSF Next issue with exported actions from storybook/test #32620

@jonniebigodes

Description

@jonniebigodes

Describe the bug

When a user attempted to migrate from CSF to CSF Next by following the documentation, and attempting to export the fn function from storybook/test to use it in various stories, they will run cryptic errors in the Storybook UI that do not provide much context about the root cause of the issue, nor how to resolve it. This is a pattern that users may want to use to avoid duplicating the same action definitions across multiple stories/tests and instead have a single source of truth for them to use throughout their projects.

This can be observed in the following screenshot:

Image

Steps to Reproduce

Reproduction link

N/A

Reproduction steps

  1. Create a small Storybook project using the latest Storybook stable version (e.g., 9.1.6)
  2. Create a component with the following code:
// src/components/Task.tsx
import type { TaskData } from "../types";

type TaskProps = {
  /** Composition of the task */
  task: TaskData;
  /** Event to change the task to archived */
  onArchiveTask: (id: string) => void;
  /** Event to change the task to pinned */
  onPinTask: (id: string) => void;
};

export default function Task({
  task: { id, title, state },
  onArchiveTask,
  onPinTask,
}: TaskProps) {
  return (
    <div className={`list-item ${state}`}>
      <label
        htmlFor={`archiveTask-${id}`}
        aria-label={`archiveTask-${id}`}
        className="checkbox"
      >
        <input
          type="checkbox"
          disabled={true}
          name="checked"
          id={`custom-archiveTask-${id}`}
          aria-label={`custom-archiveTask-${id}`}
          checked={state === "TASK_ARCHIVED"}
        />
        <span
          className="checkbox-custom"
          onClick={() => onArchiveTask(id)}
          aria-label={`custom-archiveTask-checkbox-${id}`}
        />
      </label>

      <label htmlFor={`title-${id}`} aria-label={title} className="title">
        <input
          type="text"
          value={title}
          readOnly={true}
          name="title"
          id={`title-${id}`}
          placeholder="Input title"
        />
      </label>
      {state !== "TASK_ARCHIVED" && (
        <button
          className="pin-button"
          onClick={() => onPinTask(id)}
          id={`pinTask-${id}`}
          aria-label={`pinTask-${id}`}
          key={`pinTask-${id}`}
        >
          <span className={`icon-star`} />
        </button>
      )}
    </div>
  );
}
  1. Create the following story that exports the fn function from storybook/test:
// src/components/Task.stories.tsx

import type { Meta, StoryObj } from "@storybook/react-vite";

import { fn } from "storybook/test";

import Task from "./Task";

export const ActionsData = {
  onArchiveTask: fn(),
  onPinTask: fn(),
};

const meta = {
  component: Task,
  title: "Task",
  tags: ["autodocs"],
  //👇 Our exports that end in "Data" are not stories.
  excludeStories: /.*Data$/,
  args: {
    ...ActionsData,
  },
} satisfies Meta<typeof Task>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    task: {
      id: "1",
      title: "Test Task",
      state: "TASK_INBOX",
    },
  },
};

export const Pinned: Story = {
  args: {
    task: {
      ...Default.args.task,
      state: "TASK_PINNED",
    },
  },
};

export const Archived: Story = {
  args: {
    task: {
      ...Default.args.task,
      state: "TASK_ARCHIVED",
    },
  },
};
  1. Upgrade to the latest beta (i.e., 10.0.0-beta.8)
  2. Follow the migration steps from CSF to CSF Next as outlined in the documentation and migrate the story to CSF Next using the command: npx storybook automigrate csf-factories to convert the story to CSF Next format using subpath imports that yield the following code:
// src/components/Task.stories.tsx
import preview from "#.storybook/preview";

import { fn, expect } from "storybook/test";

import Task from "./Task";

export const ActionsData = {
  onArchiveTask: fn(),
  onPinTask: fn(),
};

const meta = preview.meta({
  component: Task,
  title: "Task",
  tags: ["autodocs"],
  //👇 Our exports that end in "Data" are not stories.
  excludeStories: /.*Data$/,
  args: {
    ...ActionsData,
  },
});

export const Default = meta.story({
  args: {
    task: {
      id: "1",
      title: "Test Task",
      state: "TASK_INBOX",
    },
  },
});

export const Pinned = meta.story({
  args: {
    task: {
      ...Default.input.args.task,
      state: "TASK_PINNED",
    },
  },
});

export const Archived = meta.story({
  args: {
    task: {
      ...Default.input.args.task,
      state: "TASK_ARCHIVED",
    },
  },
});

System

Storybook Environment Info:

  System:
    OS: macOS 15.0.1
    CPU: (8) arm64 Apple M3
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.19.2 - ~/.nvm/versions/node/v20.19.2/bin/node
    Yarn: 4.9.2 - /usr/local/bin/yarn <----- active
    npm: 10.8.2 - ~/.nvm/versions/node/v20.19.2/bin/npm
  Browsers:
    Chrome: 140.0.7339.214
    Edge: 140.0.3485.94
    Safari: 18.0.1
  npmPackages:
    @storybook/addon-docs: ^10.0.0-beta.8 => 10.0.0-beta.8
    @storybook/addon-vitest: ^10.0.0-beta.8 => 10.0.0-beta.8
    @storybook/react-vite: ^10.0.0-beta.8 => 10.0.0-beta.8
    eslint-plugin-storybook: ^10.0.0-beta.8 => 10.0.0-beta.8
    msw-storybook-addon: ^2.0.5 => 2.0.5
    storybook: ^10.0.0-beta.8 => 10.0.0-beta.8

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions