testing-react icon indicating copy to clipboard operation
testing-react copied to clipboard

`StoryContext.title` Is Always An Empty String

Open DonIsaac opened this issue 3 years ago • 0 comments

Describe the bug Within a decorator, StoryContext.title is always an empty string.

I have a global decorator function that selectively decorates stories based on their top-level group, as specified by the story title. It looks something like this

import React from "react";
import { DecoratorFn } from "@storybook/react";
import { Foo, FooContext } from "../src/foo.context";

const defaultFooValue: Foo = {};

const myDecorator: DecoratorFn = (Story, ctx) => {
    const storyGroup = ctx.title.split("/")[0];
    const isFooStory: boolean = storyGroup == "Foo";

    return isFooStory
        ? <FooContext.Provider value={defaultFooValue}><Story /></FooContext.Provider>
        : <Story />
}

However, even when I test a story whose title is something along the lines of "Foo/Bar/BazComponent", the context is never provided because isFooStory is always false. After digging around with a debugger, it appears that title (and other properties) are always set to an empty string:

// .../node_modules/@storybook/react-testing/dist/testing-react.cjs.development.js, line 181
  var context = {
    componentId: '',
    kind: '',
    title: '',
    id: '',
    name: '',
    story: '',
    argTypes: globalConfig.argTypes || {},
    globals: defaultGlobals,
    parameters: combinedParameters,
    initialArgs: combinedArgs,
    args: combinedArgs,
    viewMode: 'story',
    originalStoryFn: renderFn,
    hooks: new addons.HooksContext()
  };

Note that this remains true regardless of whether composeStory or composeStories(stories) are used. Explicitly providing a story meta object, such as below, additionally has no effect.

import { composeStory } from "@storybook/react-testing";
import Meta, { FooStory } from "./Foo.stories";

const FooTestComponent = composeStory(FooStory, Meta);

To Reproduce

NOTE: Assumes usage of typescript, jest, and @testing-library/react Steps to reproduce the behavior:

  1. Create the global decorator above.
  2. Export it from preview.(js|ts) as normal, use setGlobalConfig as normal
  3. Create the following component
// src/MyComponent.tsx

import React, { FC, useContext } from "react";
import { FooContext } from "foo.context";

export const MyComponent: FC = props => {
    const foo = useContext(FooContext}
    if (!foo) throw new Error("FooContext is missing, no provider found")

    return <div>{String(foo)}</div>
}

(note: I just wrote this inline, it's likely this doesn't work)

  1. Create the FooContext
// src/foo.context.ts

import React from "react";

export type Foo = { foo: string }
export const FooContext = React.createContext<Foo | undefined>(undefined); 
  1. Create a basic Foo Story (similar to the second example on the What's a Story? Documentation Page)
  2. Create the following spec file
// src/MyComponent.spec.tsx
import React from "react";
import { render } from "@testing-library/react";
import { composeStory } from "@storybook/testing-react";
import Meta, { MyComponentStory } from "./MyComponent.stories";

describe("<MyComponent />", () => {
    it("renders successfully", () => {
        const MyComponent = composeStory(MyComponentStory, Meta);
        const view = render(<MyComponent />);
        expect(view.container.firstElement).toBeInTheDocument();
    })
})

When this test file is run, an error is thrown from the assertion within MyComponent.

Expected behavior The story's title should be available within the StoryContext, allowing the FooContext to be provided.

Screenshots If applicable, add screenshots to help explain your problem.

Additional context Add any other context about the problem here.

DonIsaac avatar Aug 05 '22 21:08 DonIsaac