Giter VIP home page Giter VIP logo

testing-react's Introduction

Storybook React Testing

Testing utilities that allow you to reuse your stories in your unit tests


⚠️ Attention!

If you're using Storybook 7, you need to read this section. Otherwise, feel free to skip it.

@storybook/testing-react has been promoted to a first-class Storybook functionality. This means that you no longer need this package. Instead, you can import the same utilities, but from the @storybook/react package. Additionally, the internals of composeStories and composeStory have been revamped, so the way a story is composed is more accurate. The @storybook/testing-react package will be deprecated, so we recommend you to migrate.

Please do the following:

  1. Uninstall this package
  2. Update your imports
- import { composeStories } from '@storybook/testing-react';
+ import { composeStories } from '@storybook/react';

// OR
- import { setProjectAnnotations } from '@storybook/testing-react';
+ import { setProjectAnnotations } from '@storybook/react';

The problem

You are using Storybook for your components and writing tests for them with jest, most likely alongside Enzyme or React testing library. In your Storybook stories, you already defined the scenarios of your components. You also set up the necessary decorators (theming, routing, state management, etc.) to make them all render correctly. When you're writing tests, you also end up defining scenarios of your components, as well as setting up the necessary decorators. By doing the same thing twice, you feel like you're spending too much effort, making writing and maintaining stories/tests become less like fun and more like a burden.

The solution

@storybook/testing-react is a solution to reuse your Storybook stories in your React tests. By reusing your stories in your tests, you have a catalog of component scenarios ready to be tested. All args and decorators from your story and its meta, and also global decorators, will be composed by this library and returned to you in a simple component. This way, in your unit tests, all you have to do is select which story you want to render, and all the necessary setup will be already done for you. This is the missing piece that allows for better shareability and maintenance between writing tests and writing Storybook stories.

Installation

This library should be installed as one of your project's devDependencies:

via npm

npm install --save-dev @storybook/testing-react

or via yarn

yarn add --dev @storybook/testing-react

Setup

Storybook 6 and Component Story Format

This library requires you to be using Storybook version 6, Component Story Format (CSF) and hoisted CSF annotations, which is the recommended way to write stories since Storybook 6.

Essentially, if you use Storybook 6 and your stories look similar to this, you're good to go!

// CSF: default export (meta) + named exports (stories)
export default {
  title: 'Example/Button',
  component: Button,
};

const Primary = args => <Button {...args} />; // or with Template.bind({})
Primary.args = {
  primary: true,
};

Global config

This is an optional step. If you don't have global decorators, there's no need to do this. However, if you do, this is a necessary step for your global decorators to be applied.

If you have global decorators/parameters/etc and want them applied to your stories when testing them, you first need to set this up. You can do this by adding to or creating a jest setup file:

// setupFile.js <-- this will run before the tests in jest.
import { setProjectAnnotations } from '@storybook/testing-react';
import * as globalStorybookConfig from './.storybook/preview'; // path of your preview.js file

setProjectAnnotations(globalStorybookConfig);

For the setup file to be picked up, you need to pass it as an option to jest in your test command:

// package.json
{
  "test": "react-scripts test --setupFiles ./setupFile.js"
}

Usage

composeStories

composeStories will process all stories from the component you specify, compose args/decorators in all of them and return an object containing the composed stories.

If you use the composed story (e.g. PrimaryButton), the component will render with the args that are passed in the story. However, you are free to pass any props on top of the component, and those props will override the default values passed in the story's args.

import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories'; // import all stories from the stories file

// Every component that is returned maps 1:1 with the stories, but they already contain all decorators from story level, meta level and global level.
const { Primary, Secondary } = composeStories(stories);

test('renders primary button with default args', () => {
  render(<Primary />);
  const buttonElement = screen.getByText(
    /Text coming from args in stories file!/i
  );
  expect(buttonElement).not.toBeNull();
});

test('renders primary button with overriden props', () => {
  render(<Primary>Hello world</Primary>); // you can override props and they will get merged with values from the Story's args
  const buttonElement = screen.getByText(/Hello world/i);
  expect(buttonElement).not.toBeNull();
});

composeStory

You can use composeStory if you wish to apply it for a single story rather than all of your stories. You need to pass the meta (default export) as well.

import { render, screen } from '@testing-library/react';
import { composeStory } from '@storybook/testing-react';
import Meta, { Primary as PrimaryStory } from './Button.stories';

// Returns a component that already contain all decorators from story level, meta level and global level.
const Primary = composeStory(PrimaryStory, Meta);

test('onclick handler is called', () => {
  const onClickSpy = jest.fn();
  render(<Primary onClick={onClickSpy} />);
  const buttonElement = screen.getByRole('button');
  buttonElement.click();
  expect(onClickSpy).toHaveBeenCalled();
});

setting project annotations to composeStory or composeStories

setProjectAnnotations is intended to apply all the global configurations that are defined in your .storybook/preview.js file. This means that you might get unintended side-effects in case your preview.js imports certain mocks or other things you actually do not want to execute in your test files. If this is your case and you still need to provide some annotation overrides (decorators, parameters, etc) that normally come from preview.js, you can pass them directly as the optional last argument of both composeStories and composeStory functions:

composeStories:

import * as stories from './Button.stories'

// default behavior: uses overrides from setProjectAnnotations
const { Primary } = composeStories(stories)

// custom behavior: uses overrides defined locally
const { Primary } = composeStories(stories, { decorators: [...], globalTypes: {...}, parameters: {...})

composeStory:

import * as stories from './Button.stories'

// default behavior: uses overrides from setProjectAnnotations
const Primary = composeStory(stories.Primary, stories.default)

// custom behavior: uses overrides defined locally
const Primary = composeStory(stories.Primary, stories.default, { decorators: [...], globalTypes: {...}, parameters: {...})

Reusing story properties

The components returned by composeStories or composeStory not only can be rendered as React components, but also come with the combined properties from story, meta and global configuration. This means that if you want to access args or parameters, for instance, you can do so:

import { render, screen } from '@testing-library/react';
import { composeStory } from '@storybook/testing-react';
import * as stories from './Button.stories';

const { Primary } = composeStories(stories);

test('reuses args from composed story', () => {
  render(<Primary />);

  const buttonElement = screen.getByRole('button');
  // Testing against values coming from the story itself! No need for duplication
  expect(buttonElement.textContent).toEqual(Primary.args.children);
});

CSF3

Storybook 6.4 released a new version of CSF, where the story can also be an object. This is supported in @storybook/testing-react, but you have to match one of the requisites:

1 - Your story has a render method 2 - Or your meta has a render method 3 - Or your meta contains a component property

// Example 1: Meta with component property
export default {
  title: 'Button',
  component: Button, // <-- This is strictly necessary
};

// Example 2: Meta with render method:
export default {
  title: 'Button',
  render: args => <Button {...args} />,
};

// Example 3: Story with render method:
export const Primary = {
  render: args => <Button {...args} />,
};

Interactions with play function

Storybook 6.4 also brings a new function called play, where you can write automated interactions to the story.

In @storybook/testing-react, the play function does not run automatically for you, but rather comes in the returned component, and you can execute it as you please.

Consider the following example:

export const InputFieldFilled: Story<InputFieldProps> = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByRole('textbox'), 'Hello world!');
  },
};

You can use the play function like this:

const { InputFieldFilled } = composeStories(stories);

test('renders with play function', async () => {
  const { container } = render(<InputFieldFilled />);

  // pass container as canvasElement and play an interaction that fills the input
  await InputFieldFilled.play({ canvasElement: container });

  const input = screen.getByRole('textbox') as HTMLInputElement;
  expect(input.value).toEqual('Hello world!');
});

Batch testing all stories from a file

Rather than specifying test by test manually, you can also run automated tests by using test.each in combination with composeStories. Here's an example for doing snapshot tests in all stories from a file:

import * as stories from './Button.stories';

const testCases = Object.values(composeStories(stories)).map((Story) => [
  // The ! is necessary in Typescript only, as the property is part of a partial type
  Story.storyName!,
  Story,
]);
// Batch snapshot testing
test.each(testCases)('Renders %s story', async (_storyName, Story) => {
  const tree = await render(<Story />);
  expect(tree.baseElement).toMatchSnapshot();
});

Typescript

@storybook/testing-react is typescript ready and provides autocompletion to easily detect all stories of your component:

component autocompletion

It also provides the props of the components just as you would normally expect when using them directly in your tests:

props autocompletion

Type inference is only possible in projects that have either strict or strictBindApplyCall modes set to true in their tsconfig.json file. You also need a TypeScript version over 4.0.0. If you don't have proper type inference, this might be the reason.

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "strict": true, // You need either this option
    "strictBindCallApply": true // or this option
    // ...
  }
  // ...
}

Disclaimer

For the types to be automatically picked up, your stories must be typed. See an example:

import React from 'react';
import { Story, Meta } from '@storybook/react';

import { Button, ButtonProps } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
} as Meta;

// Story<Props> is the key piece needed for typescript validation
const Template: Story<ButtonProps> = args => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  children: 'foo',
  size: 'large',
};

License

MIT

testing-react's People

Contributors

alejandronanez avatar dependabot[bot] avatar diegohaz avatar elevatebart avatar eric-burel avatar ianvs avatar imgbotapp avatar jonniebigodes avatar kasperpeulen avatar ljcl avatar nativedone avatar ndelangen avatar payapula avatar saurabhssahu avatar shilman avatar tatsushitoji avatar teasealancs avatar tmeasday avatar yannbf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

testing-react's Issues

Doesn't support SCSS import in preview

I use SCSS and I import my stylesheet in ./storybook/preview.js

import "../src/styles/globals.scss";

This will break, even though both Jest and Storybook handles SCSS when used before setting up storybookjs/testing-react.

    C:\PROJECT_PATH\src\styles\globals.scss:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){@import "./utils.scss";
                                                                                      ^

I expected storybookjs/testing-react to use the same configuration as jest and Storybook, and support what ever configuration they support.

@storybook/preview-web stops exporting WebProjectAnnotations at 6.5.0

Describe the bug

../../node_modules/@storybook/testing-react/dist/types.d.ts:2:15 - error TS2305: Module '"@storybook/preview-web"' has no exported member 'WebProjectAnnotations'.

2 import type { WebProjectAnnotations } from '@storybook/preview-web';

To Reproduce
Use any @storybook version above v6.5.0

Should be ~6.4.0

"@storybook/preview-web": ">=6.4.0",

Looks like they remove WebProjectAnnotations export at v6.5.0-alpha.53. Seems like a breaking change to me. 🤷🏽‍♂️
storybookjs/storybook@bcf7e4d#diff-a10f71fb35c8a250ffdfe123e528271e550f05469a3e09e1ae746555707972a5

What is the TypeScript return type of within()?

  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByRole('textbox'), 'Hello world!');
  },
};

In this example, what is the type of canvas? I tried BoundFunctions<HTMLElement> and that did not work. Please let me know :D

Peer dependency failure with beta storybook

Describe the bug

I am using storybook @ 6.3.0-beta.2, and when trying to install @storybook/testing-react with npm 7 I get the following:

pm ERR! Found: @storybook/[email protected]
npm ERR! node_modules/@storybook/addons
npm ERR!   dev @storybook/addons@"^6.3.0-beta.2" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer @storybook/addons@"^6.0.0" from @storybook/[email protected]
npm ERR! node_modules/@storybook/testing-react
npm ERR!   dev @storybook/testing-react@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

To Reproduce
Steps to reproduce the behavior:
Install storybook beta version, use npm 7, try to install @storybook/testing-react.

Expected behavior
It should install successfully.

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

Additional context
I propose setting the peer dependency ranges as loosely as possible, perhaps:

  "peerDependencies": {
    "react": ">=16.8.0",
    "@storybook/react": ">=6.0.0",
    "@storybook/addons": ">=6.0.0",
    "@storybook/client-api": ">=6.0.0"
  },

@storybook/testing-react type errors

Describe the bug
I am getting the following ts errors with my code while trying to use the new composeStories.

image
image

I was able to "fix" it by using casting, but I don't think this is right.

image

What should I do?

Storybook version: 6.4.4

JSX element type does not have any construct or call signatures

Hi, sorry if this is a newbie question. I'm new to Storybook, Testing Library, and TypeScript. I'm trying to avoid using any as much as possible and am wondering why I am getting this typing error for my component story.

Also, would you be open to showing discussions on this repository? I don't believe my issue is suited as a bug report and should be in discussions instead. Thanks!

Screenshot_1

The add-on does not work when importing mdx documentation into stories

Describe the bug
The add-on does not work when importing mdx documentation into stories

Example

// index.stories.jsx

import React from 'react';

import { Text } from '../index';

import Doc from './docs.mdx';

export default {
  title: 'General/Text',
  component: Text,
  parameters: {
    docs: {
      page: Doc,
    },
  },
};

export const Tag = () => (
      <Text tag="h1">Heading 1</Text>
);

When I run my tests without parameters.docs everything is fine and works as it should. But it's not great.

The same situation was described here.

Documentation for using with Cypress

I was able to use this successfully with Jest, but I have been unable to find a way to get this working with Cypress component testing.
There's an article describing this working, https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/
The examples given don't describe how to fully set this up, as it looks like there is setup in cypress/plugins that needs to be done for this to work properly, but it requires react-scripts/cra. Can we get documentation of how to use this with Storybook alone?

Does this work with stories defined in MDX?

Hi,

I hope it's ok for me to open an issue for this even though it's just a question, but I couldn't find any information about whether or not this library works with stories that are defined inside of MDX using the Meta and Story components. Might be worth adding a note and/or example about this use case to the readme.

Thanks!
Dion

Render functions in meta don't get composed into stories

Describe the bug

If you have a story file with a render function in the meta, this render function is used correctly when viewing the story in Storybook, but not when composed using @storybook/testing-react

To Reproduce
Steps to reproduce the behavior:

  • Create a new story file
  • In the meta for the story file, specify a render function
  • Compose stories for that file

Expected behavior

  • The render function in the meta should be used as the render function for the composed stories.

[@storybook/testing-react] composeStories typescript error when using parameters.layout

Describe the bug

My story is using

parameters: {
    layout: 'fullscreen',
},

But I get the typescript error below when I try to composeStories:

The types of 'parameters.layout' are incompatible between these types.
    Type 'string' is not assignable to type '"fullscreen" | "centered" | "padded" | "none" | undefined'

I can work around this by specifying:

parameters: {
    layout: 'fullscreen' as const,
},

But maybe it's better for the typescript type for composeStories to be a bit more generic and allow string (or perhaps specify readonly somewhere...)?

To Reproduce
I can create a reproduction if requested, but I think this is pretty straightforward.

v0.0.19 broke TypeScript stories types on combineStories

Hello all,

Describe the bug
Since v0.0.19, combineStories is returning any for all the stories :
image

The component exported here is :

const Template: Story = () => (
  <CreateProjectDrawer>
    <DrawerTrigger as={Button}>Create a project</DrawerTrigger>
  </CreateProjectDrawer>
);

const Default = Template.bind({});

To Reproduce

  1. Export any story
  2. Use combineStories and you can see the stories are no longer typed

Expected behavior
combineStories should return typed stories (v0.0.18 here) :
image

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

Additional context
v0.0.18 is the last version where it works, all later release are broken.
I'm using storybook 6.3.5.

"Storybook preview hooks can only be called inside decorators and story functions." error

Describe the bug

We've got a custom addon that we've written internally to allow us to document / manipulate our i18n translations inside storybooks & we're having trouble getting it to work with this.

So far I've:

  • Started to set up @storybookjs/testing-react (which, once we get it working will save a lot of time & be incredibly useful so thanks)
  • Noticed that the decorator provided by the addon wasn't being applied (which I guess makes sense? the .storybook/main.js with the addon array isn't referenced anywhere)
  • Thought I'd use the setGlobalConfig to manually add in the decorator from our addon
  • Got the Storybook preview hooks can only be called inside decorators and story functions. error

Our decorator looks something like:

import React, { useEffect, useState } from "react"
import { I18nContext } from "@our_stuff/i18nContext"
import I18n from "@our_stuff/i18n"
import { makeDecorator, useGlobals, useParameter } from "@storybook/addons"

export const withOurI18n = makeDecorator({
  name: "withOurI18n",
  parameterName: "i18n",
  wrapper: (storyFn, context, { parameters }) => {
    const paramI18nData = useParameter("i18n", {})
    const [{ "our-intl": intlData }] = useGlobals()
    const [i18n, setI18n] = useState(getNewI18nForData(intlData || paramI18nData))

    useEffect(() => {
      if (intlData) setI18n(I18n(intlData))
    }, [intlData])

    return <I18nContext.Provider value={i18n}>{storyFn()}</I18nContext.Provider>
  },
})

(it's rough & has bugs but it works when viewing one story in storybook)

Our jest.setup.js looks like:

import { setGlobalConfig } from "@storybook/testing-react"
import * as globalStorybookConfig from "../.storybook/preview"
import { withOurI18n } from "@our_stuff/storybook-addon-i18n/dist/decorators/withI18n"

setGlobalConfig({ ...globalStorybookConfig, decorators: [withOurI18n] })

At the moment our test looks pretty much exactly like the one in the getting started documentation

Let me know if there's anything else I can provide that'd be useful

Expected behavior
Would expect the decorators to function the same way they do when viewing the story in the storybook.

Story's argTypes defaultValue is not used

Describe the bug

A Story's argTypes entries can have a defaultValue, which can be used if a value isn't defined in args.

To Reproduce

Given a Story meta of:

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    children: { control: 'text', defaultValue: 'Default button text' },
  },
} as Meta;

const Template: Story<ButtonProps> = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  size: 'large',
  primary: true,
};

Expected behavior

The button when rendered in tests should result in a button with text of "Default button text", as it does in Storybook:

image

Incompatible with styled-components

Describe the bug
A clear and concise description of what the bug is.

Despite following the global config instructions, my ThemeProvider is not passing the theme prop to its consumers, thus causing several test failures.

To Reproduce
Steps to reproduce the behavior:

  1. Create a Storybook project using styled-components

    Example component:

       /**
        * @file Implementation - Input
        * @module ui/lib/atoms/Input/impl
        */
    
       const Input: SC<Props, 'input'> = s.input<Props>(p => {
         const sm = p.$size === 'sm'
       
         return css`
           appearance: none; // Fix appearance for date inputs in Safari
           background-clip: padding-box;
           background-color: ${p.theme.settings.inputs.$bg};
           border-color: ${p.theme.settings.inputs.$borderColor};
           border-radius: ${p.theme.settings.inputs.$borderRadius};
           border-style: solid;
           border-width: ${p.theme.settings.inputs.$borderWidth};
           box-shadow: ${p.theme.settings.inputs.$boxShadow};
           color: ${p.theme.settings.inputs.$color};
           font-family: ${p.theme.settings.inputs.$fontFamily};
           font-size: ${p.theme.settings.inputs.$fontSize};
           font-weight: ${p.theme.settings.inputs.$fontWeight};
           height: ${p.theme.space[sm ? 32 : 48]};
           line-height: ${p.theme.settings.inputs.$lineHeight};
           padding: ${p.theme.settings.inputs.$py} ${p.theme.settings.inputs.$px};
           transition: ${p.theme.settings.inputs.$transition};
           width: ${p.theme.size.fluid};
       
           // Placeholders
           &::placeholder {
             font-family: ${p.theme.inputs.placeholder.fontFamily};
             font-size: ${p.theme.fontSizes.sm};
           }
       
           // Customize the focus state to imitate native WebKit styles
           &:focus {
             background-color: ${p.theme.settings.inputs['$bg:focus']};
             border-color: ${p.theme.settings.inputs['$borderColor:focus']};
             box-shadow: ${p.theme.settings.inputs['$borderColor:focus']};
             color: ${p.theme.settings.inputs['$color:focus']};
             font-weight: ${p.theme.settings.inputs['$fontWeight:focus']};
             outline: ${p.theme.space[0]};
           }
         `
       })
  2. Add a ThemeProvider to your Storybook preview configuration

       import { ThemeProvider } from 'styled-components'
       import THEME from '../src/styles/theme'
    
       /**
        * @file Storybook Preview Configuration
        * @module storybook/preview
        */
    
       export const decorators: DecoratorFn[] = [
         /**
          * Wraps the current story in a `ThemeProvider`.
          *
          * @param {Story} StoryFn - Current story
          * @return {StoryFnReactReturnType} Story wrapped in `ThemeProvider`
          */
         (StoryFn: Story): StoryFnReactReturnType => (
           <ThemeProvider theme={THEME}>
             <StoryFn />
           </ThemeProvider>
         )
       ]
  3. Follow global config instructions

  4. Add a test file

    import { render } from '@testing-library/react'
    import Input from '../Input'
    
    /**
     * @file Unit Tests - Input
     * @module ui/lib/atoms/Input/tests/unit
     * @see https://www.newline.co/fullstack-react/30-days-of-react/day-22
     */
    
    describe('unit:ui/lib/atoms/Input', () => {
      describe('html', () => {
        it('should render <input> element', () => {
          // Act
          const { container } = render(<Input />)
    
          // Expect
          expect(container.firstChild?.nodeName.toLowerCase()).toBe('input')
        })
      })
    })
  5. Run Jest

  6. See error

    Summary of all failing tests
     FAIL  packages/ui/src/lib/atoms/Input/__tests__/Input.spec.tsx (18.316 s)
      ● unit:ui/lib/atoms/Input › html › should render <input> element
    
        TypeError: Cannot read property 'inputs' of undefined
    
          40 |     &::placeholder {
          41 |       font-family: ${p.theme.inputs.placeholder.fontFamily};
        > 42 |       font-size: ${p.theme.fontSizes.sm};
             |                                          ^
          43 |     }
          44 |
          45 |     // Customize the focus state to imitate native WebKit styles
    
          at id (packages/ui/src/lib/atoms/Input/Input.tsx:42:42)
          at me (node_modules/styled-components/src/models/Keyframes.js:20:51)
          at e.id [as generateAndInjectStyles] (node_modules/styled-components/src/utils/isStaticRules.js:6:54)
          at id (node_modules/styled-components/src/models/Keyframes.js:20:51)
          at id (node_modules/styled-components/src/models/Keyframes.js:20:51)
          at I (node_modules/styled-components/src/models/Keyframes.js:20:51)
          at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)
          at updateForwardRef (node_modules/react-dom/cjs/react-dom.development.js:17044:20)
          at beginWork (node_modules/react-dom/cjs/react-dom.development.js:19098:16)
          at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:3945:14)
          at HTMLUnknownElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
          at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:338:25)
          at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
          at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
          at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
          at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
          at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:3994:16)
          at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4056:31)
          at beginWork$1 (node_modules/react-dom/cjs/react-dom.development.js:23964:7)
          at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:22779:12)
          at workLoopSync (node_modules/react-dom/cjs/react-dom.development.js:22707:5)
          at renderRootSync (node_modules/react-dom/cjs/react-dom.development.js:22670:7)
          at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:22293:18)
          at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:21881:7)
          at updateContainer (node_modules/react-dom/cjs/react-dom.development.js:25482:3)
          at node_modules/react-dom/cjs/react-dom.development.js:26021:7
          at unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:22431:12)
          at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:26020:5)
          at Object.render (node_modules/react-dom/cjs/react-dom.development.js:26103:10)
          at node_modules/@testing-library/react/dist/pure.js:101:25
          at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:22380:12)
          at act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:1042:14)
          at Object.render (node_modules/@testing-library/react/dist/pure.js:97:26)
          at Object.<anonymous> (packages/ui/src/lib/atoms/Input/__tests__/Input.spec.tsx:20:43)

Expected behavior
A clear and concise description of what you expected to happen.

The theme prop should be passed down to all context consumers.

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

N/A

Additional context
Add any other context about the problem here.

  • I can't share the project repo because it's for work
  • The project is setup as monorepo using Yarn Workspaces
  • To run my tests, I'm using the command syntax yarn workspace <workspace-name> <command>
  • Prior to attempting to integrate @storybook/testing-react, all of my tests passed
    • To render components with the ThemeProvider, I used a utility function:

        import type { RenderOptions, RenderResult } from '@testing-library/react'
        import { render as renderComponent } from '@testing-library/react'
        import type { ThemeProviderProps } from '@ui/providers/ThemeProvider'
        import { ThemeProvider } from 'styled-components'
        import THEME from '@ui/styles/theme'
        import type { ReactElement } from 'react'
        
        /**
         * @file Test Utilities - @ui
         * @module tests/utils/ui
         */
        
        /**
         * Renders a test component wrapped in the `ThemeProvider`.
         *
         * @param {ReactElement} [ui] - Component to render
         * @param {ThemeProviderProps} [props] - `ThemeProvider` properties
         * @param {RenderOptions} [options] - Render options
         * @return {RenderResult} Render result
         */
        export const render = (
          ui: ReactElement = <>{null}</>,
          props: ThemeProviderProps = { theme: THEME },
          options: RenderOptions = {}
        ): ReturnType<typeof renderComponent> => {
          return renderComponent(
            <ThemeProvider {...{ ...props, children: ui }} />,
            options
          )
        }

composeStories should support Meta typed with generic

The problem

I would like to apply a generic type to the Meta type for my default story exports. Doing so enables type-checking on the values defined on the args attribute in the default export. However, by applying this type, the composeStories function I am using for my tests fails. It appears as though, after digging through some type files, that composeStories does not take into account generics for Meta.

demo.stories.tsx

import { Meta, Story } from '@storybook/react';

interface DemoProps {
  foo: string;
  baz: boolean;
}

export default {
  title: 'MyComponent',
  component: MyComponent,
  args: {
    foo: 'baa',
    baz: false
  },
  argTypes: {
    foo: { control: { type: 'text' } },
    baz: { control: { type: 'boolean' } }
  }
} as Meta<DemoProps>;

export const StoryDemo: Story<DemoProps> = (args: DemoProps) => {
  return <MyComponent foo={args.foo} baz={args.baz} />;
};

demo.test.ts

import { composeStories } from '@storybook/testing-react';

import * as stories from './demo.stories';

const { StoryDemo } = composeStories(stories);

The errors I receive in my test file:

Property 'StoryDemo' does not exist on type 'StoriesWithPartialProps<{ default: Meta<Args>; __esModule?: boolean | undefined; }>'.

Type 'StoryContext<ReactFramework, Args>' is not assignable to type 'StoryContext<ReactFramework, DemoProps>'. Type 'StoryContext<ReactFramework, Args>' is not assignable to type '{ component?: ComponentType<any> | undefined; subcomponents?: Record<string, ComponentType<any>> | undefined; parameters: Parameters; initialArgs: DemoProps; argTypes: StrictArgTypes<...>; }'. Types of property 'initialArgs' are incompatible. Type 'Args' is not assignable to type 'DemoProps'.

Potential solution

I noticed that the composeStory function accepts a generic that is applied to the story itself, but not on the meta parameter. When I modified the @storybook/texting-react type declaration from node_modules for this function to the following, I was able to get it to work in my test.

index.d.ts

function composeStory<GenericArgs>(story: Story<GenericArgs>, meta: Meta<GenericArgs>, globalConfig?: GlobalConfig): Story<Partial<GenericArgs>>;

demo.test.ts

import { composeStory } from '@storybook/testing-react';

import meta, { DemoProps, StoryDemo } from './demo.stories';

const { StoryDemoTest } = composeStory<DemoProps>(StoryDemo, meta);

I would prefer if a similar solution could be applied to the composeStories function. Ideally, these compose functions could just accept any type set on Meta and apply it within its own implementation.

`StoryContext.title` Is Always An Empty String

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.

Module not found: Can't resolve '@storybook/react/types-6-0'

Describe the bug
After upgrading to Next 12, when running Cypress component tests I get the following error. This only happens during Cypress component test. Cypress component test is using import { composeStories } from "@storybook/testing-react"; to import and test the story. Has anyone run into this issue before?

Downgrading to Next 11 solves the issue.

Module not found: Can't resolve '@storybook/react/types-6-0'

Cross post from here storybookjs/storybook#16672, sorry just realized after this could be a better place to ask for help.

composeStories errors on imports from story files when transpiled with jest

Hey guys!

composeStory works fine but it appears the __esModule key generated by the jest babel transpilation is throwing an error in composeStories when we export * as stories.

I receive the error

Cannot compose story due to invalid format. @storybook/testing-react expected a function but received boolean instead.

I was able to track it down to

https://github.com/storybookjs/testing-react/blob/main/src/index.ts#L141

console.log
    stories {
      __esModule: true,
      Basic: [Function: bound Template],
      PartnerLink: [Function: bound Template] { args: { partner: true } },
      ExternalLink: [Function: bound Template] { args: { external: true } }
    }

Expose types

Is your feature request related to a problem? Please describe.
I have utils/helpers that work with the results of the composeStory/composeStories functions. Currently, @storybook/testing-react doesn't Expose the types these functions work with. More specifically, I need to use StoryFile and StoriesWithPartialProps.

Describe the solution you'd like
It would be great if types were exported and available like this: import type { StoryFile, StoriesWithPartialProps } from '@storybook/testing-react'

Describe alternatives you've considered
I'm currently importing these types from the dist folder, like this: import type { StoryFile, StoriesWithPartialProps } from '@storybook/testing-react/dist/types'. This works, but it's not a good approach.

Tests failing: Storybook preview hooks can only be called inside decorators and story functions

Describe the bug
I'm not sure if this is a bug, but following this example https://github.com/storybookjs/testing-react#composestory

my test fails with "Storybook preview hooks can only be called inside decorators and story functions.".

It's probably something stupid but I can't figure it out.

I have configured the global decorators and my test looks like this:

import React from 'react';
import { render, screen } from '@testing-library/react';
import { composeStory } from '@storybook/testing-react';
import Meta, { Default as DefaultStory } from './Label.stories';

// Returns a component that already contain all decorators from story level, meta level and global level.
const Label = composeStory(DefaultStory, Meta);

test('RANDOM', async () => {
  render(<Label />);

  expect(screen).toMatchSnapshot();
});

And my label stories:

import React from 'react';
import { Meta } from '@storybook/react';
import { Label } from './Label';
import { LabelProps } from './Label.types';

// Meta + template
export default {
  title: 'Components/Typography/Label',
  component: Label,
} as Meta;

const Template: any = (args: LabelProps) => <Label {...args} />;

// Stories
export const Default = Template.bind({});
Default.args = {
  children: 'Hello world!',
};

Adding notes to a story breaks composeStories

Describe the bug
If you have a story file which contains notes added directly onto any stories within in, even if you use a story without those notes, composeStories fails.

To Reproduce
Steps to reproduce the behavior:

  1. Create a story with notes added to that individual story
    export default {
    title: 'Test',
    component: Test,
    parameters: {
    notes: 'Lorem Ipsum'
    }
    };
    export const Example = () => (
); export const Example2 = () => ( ); Example2.story = { parameters: { notes: 'Notes for example 2' } }
  1. Create a cypress spec that imports a story from that file
    import * as stories from '../../stories/Test.stories';

const { Example } = composeStories(stories);

describe('Test', () => {
describe('Usage', () => {
beforeEach(() => {
mount();
});
it('renders', () => {
expect();
});
});
});

Expected behavior
Tests should work

Current Behavior
The following error originated from your test code, not from Cypress.

StoryFn.story object-style annotation is not supported. @storybook/testing-react expects hoisted CSF stories.

Running `tsc --noEmit` throws an error

Describe the bug

I installed this add-on to our Storybook and it works like a charm. Thank you all for the awesome effort.

I have tried using Storybooks version 6.3.3 and 6.3.7

In our project repo when running tsc --noEmit in our codebase, after installing "@storybook/testing-react": "^0.0.21" I am getting the following error:

node_modules/@storybook/testing-react/dist/types.d.ts:1:65 - error TS2724: '"@storybook/addons"' has no exported member named 'BaseStoryFn'. Did you mean 'BaseStory'?

1 import { ArgTypes, Parameters, BaseDecorators, BaseAnnotations, BaseStoryFn as OriginalBaseStoryFn } from '@storybook/addons';

Found 1 error.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Expected behavior
No errors

Screenshots
Screenshot 2021-08-10 at 19 16 01

Additional context
Am I missing something in the config?
The docs do not mention adding '@storybook/testing-react', to the main.js addons section. When I try this it has no effect.

When I inspect the file /node_modules/@storybook/testing-react/dist/types.d.ts on VS code, it finds that this is the case.
Screenshot 2021-08-10 at 19 20 33

When I inspect the file node_modules/@storybook/addons/dist/ts3.9/types.d.ts this is the case.
Screenshot 2021-08-10 at 19 21 57

Library does not work when using certain addons

Describe the bug
When running composeStories in stories that have certain addons, such as storybook-addons-design, the tests break with the following error:

Error: Uncaught [Error: Accessing non-existent addons channel, see https://storybook.js.org/basics/faq/#why-is-there-no-addons-channel]

Expected behavior
Addons channel should not present error in the tests

Tests that use "play" pass even if interactions fail

I am using @storybook/[email protected] and if I execute the play function from a test it will always pass, even if the interactions do not actually work in Storybook.

For example, take the following story that clicks on a "Home" button which I have deliberately broken:

HomeBreadcrumb.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  // Wait for the card to load
  await canvas.findByLabelText(/vehicle details/i);
  // Click the Home button THIS WILL FAIL
  userEvent.click(canvas.getByRole("link", { name: /home2/i }));
  // Wait for the home page
  await canvas.findByRole("heading", { name: /home/i, level: 1 });
};

In Storybook the debug interaction console shows an error:

image

I then have a test like this:

import { composeStories } from "@storybook/testing-react";
import * as stories from "./Details.stories";

const { HomeBreadcrumb } = composeStories(stories);

test("home breadcrumb", async () => {
  const { container } = render(<HomeBreadcrumb />);
  // @ts-ignore
  await HomeBreadcrumb.play({ canvasElement: container });
});

But the test passes. Am I missing something here?

Support CSF 3.0

I'm checking out CSF 3.0 (hidden behind a feature flag).

The stories can now be an object

export default {
  title: "Components/Button",
  component: Button
} as Meta;

export const Default = {
  args: {
    loading: false,
    block: false,
    disabled: false,
    children: "Default button",
  },
};

I get this error, when trying to use CSF 3.0 stories with composeStories.

Cannot compose story due to invalid format. @storybook/testing-react expected a function but received object instead.
    >  8 | const { Default } = composeStories(stories);

I tried browsing Storybooks code-base to find a utility function that converts CSF 3.0 objects to functions, but wasn't able to find it.

Fails to recognize non-story exports and exclude them from composeStories.

Describe the bug
I receive the following error when exporting non-story constants from my stories via the includeStories syntax.

Cannot compose story due to invalid format. @storybook/testing-react expected a function but received object instead.

The offending line:

const { Default, WithHeading, } = composeStories(stories);

The relevant story snippet:

export default {
  title: 'Components/CashDisplay',
  component: CashDisplay,
  includeStories: /^[A-Z]/,
};

export const data = {
  default: {
    amount: 5.25,
  }
}

To Reproduce
Steps to reproduce the behavior:

  1. Create a story
  2. Add a parameter that allows you to export a non-story object
  3. Use composeStory on that story.

Expected behavior
The testing framework should respect storybook's methods for turning some exports into stories and not others, and composeStories should properly recognize which exports are and aren't stories.

Additional context
Stack trace:

      at composeStory (node_modules/@storybook/testing-react/src/index.ts:63:11)
      at reduce (node_modules/@storybook/testing-react/src/index.ts:173:25)
          at Array.reduce (<anonymous>)
      at composeStories (node_modules/@storybook/testing-react/src/index.ts:171:51)
      at Object.<anonymous> (src/test/components/REDACTED.test.js:6:35)

Storybook 7.0 alpha.44 removed defaultDecorateStory, breaks testing-react

I attempted to upgrade storybook to the latest alphas, and got this error:

'defaultDecorateStory' is not exported by node_modules/@storybook/client-api/dist/index.mjs, imported by node_modules/@storybook/testing-react/dist/testing-react.esm.js
file: /Users/ianvs/code/defined/webclient/node_modules/@storybook/testing-react/dist/testing-react.esm.js:1:28
1: import { combineParameters, defaultDecorateStory } from '@storybook/client-api';
                               ^
2: import addons, { mockChannel, HooksContext, applyHooks } from '@storybook/addons';

The only thing I'm using from this library is:

import { composeStory } from '@storybook/testing-react';

Types unknown, not read by Jest

Describe the bug
StoryFn Types are not detected by Jest unless manually forced, making it a verbose process that has to be done for each and every story.

Framework: React
Project: TypeScript project built with Emotion.js (Using @emotion/styled)

Setup:

In Link.story.tsx,

import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';

import { Link } from '.';

export default {
  title: 'Components/Link',
  component: Link
} as ComponentMeta<typeof Link>;

const Template: ComponentStory<typeof Link> = (args) => <Link {...args} />;

export const DefaultLink = Template.bind({});
DefaultLink.args = {
  url: 'https://www.github.com/jayantasamaddar/ursa',
  children: 'Visit the Ursa Github page'
};

In Link.test.tsx,

import React from 'react';
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import '@testing-library/jest-dom';
import * as stories from './Link.story'; // import all stories from the stories file
import { lightTheme } from '../../styles';

const { DefaultLink } =  composeStories(stories);

describe('components/Link', () => {
  it('Renders Default Link', () => {
    render(<DefaultLink>Link</DefaultLink>);
    const linkEl = screen.getByTestId('link');
    const linkText = linkEl.querySelector('.Ursa-LinkText');
    expect(linkEl).not.toBeNull();
    expect(linkEl).toHaveAttribute('href', DefaultLink.args.url);
    expect(linkText).toHaveTextContent(linkText.textContent);
    expect(linkEl).toHaveStyle({
      color: lightTheme.color['--ursa-link-primary'],
      'text-decoration': 'underline'
    });
  });
});

Getting this error on the const { DefaultLink } = composeStories(stories); line above:

Error:

const DefaultLink: unknown
JSX element type 'DefaultLink' does not have any construct or call signatures.ts(2604)

What I did next is, changed the filename Link.test.tsx to Link.test.jsx to remove the type errors and view the types that show up:
The types showed up as: const DefaultLink: StoryFn<Partial<LinkProps>>. Used this as a basis to do a couple of tests for different scenarios.


Scenario 1: Use StoryFn and LinkProps (my own defined Type interface for the Link component)

So to remove the error, I had to manually do this on the story file, as shown below.

Note: in the Original file, I was getting away not using the LinkProps and simply using and letting TypeScript infer the types. Here I had to do a manual import of the LinkProps.

In Link.story.tsx

import React from 'react';
import { ComponentMeta, ComponentStory, StoryFn } from '@storybook/react';

import { Link, LinkProps } from '.';

export default {
  title: 'Components/Link',
  component: Link
} as ComponentMeta<typeof Link>;

const Template: ComponentStory<typeof Link> = (args) => <Link {...args} />;

export const DefaultLink: StoryFn<LinkProps> = Template.bind({});
DefaultLink.args = {
  url: 'https://www.github.com/jayantasamaddar/ursa',
  children: 'Visit the Ursa Github page'
};

This has seemed to work as expected as it mimics the behaviour that is expected by default. But the additional hassle of importing this to every story file now remains.


Scenario 2: Use ComponentStoryFn<typeof Link>

The idea is to not use LinkProps or a Type specifically but letting TypeScript infer types.

In Link.story.tsx

import React from 'react';
import {
  ComponentMeta,
  ComponentStory,
  ComponentStoryFn
} from '@storybook/react';

import { Link } from '.';

export default {
  title: 'Components/Link',
  component: Link
} as ComponentMeta<typeof Link>;

const Template: ComponentStory<typeof Link> = (args) => <Link {...args} />;

export const DefaultLink: ComponentStoryFn<typeof Link> = Template.bind({});
DefaultLink.args = {
  url: 'https://www.github.com/jayantasamaddar/ursa',
  children: 'Visit the Ursa Github page'
};

This too works. However the catch is, this makes the args optional requiring optional chaining to check for validity.

In Link.test.tsx

import React from 'react';
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import '@testing-library/jest-dom';
import * as stories from './Link.story'; // import all stories from the stories file
import { lightTheme } from '../../styles';

const { DefaultLink } = composeStories(stories);
describe('components/Link', () => {
  it('Renders Default Link', () => {
    render(<DefaultLink>Link</DefaultLink>);
    const linkEl = screen.getByTestId('link');
    const linkText = linkEl.querySelector('.Ursa-LinkText');
    expect(linkEl).not.toBeNull();
    expect(linkEl).toHaveAttribute('href', DefaultLink.args?.url);
    expect(linkText).toHaveTextContent(linkText.textContent);
    expect(linkEl).toHaveStyle({
      color: lightTheme.color['--ursa-link-primary'],
      'text-decoration': 'underline'
    });
  });
});

To Reproduce

Here is a link to a test repo that can be cloned and you can run storybook and jest.
https://github.com/jayantasamaddar/icons-jest-test

  • yarn storybook
  • yarn test

Expected behavior

  • Correct type inferring as per Scenario 1 but working by inferring types like Scenario 2.
  • Doing so without the usage of any StoryFn or ComponentStoryFn use.

Component exports fail type checking if not explicitly typed

Describe the bug
Using v1.2.2, I started getting some type failures in my code:

src/components/atoms/buttons/hamburger/Hamburger.test.tsx:4:9 - error TS2339: Property 'Closed' does not exist on type 'Omit<StoriesWithPartialProps<typeof import("/Users/ianvs/code/project/src/components/atoms/buttons/hamburger/Hamburger.stories")>, keyof StoryFile>'.

4 const { Closed, Controls } = composeStories(Stories);
          ~~~~~~

To Reproduce
Here's a reproduction of what I'm seeing: https://stackblitz.com/edit/node-grzpdq?file=src/app.test.tsx

If I have a story file like this:

import * as React from 'react';
import type { ComponentStory, Meta } from '@storybook/react';
import { App } from './app';

const meta: Meta = {
  title: 'App',
  component: App,
};
export default meta;

export const Args: ComponentStory<typeof App> = (args) => {
  return <App {...args} />;
};

export const NoArgs = () => {
  return <App />;
};

Then when I destructure from composeStories, I get an error on NoArgs, but not Args.

Expected behavior
I should continue to be able to use type inference on exported story components.

Path to support Mocha (for Meteor)?

Is your feature request related to a problem? Please describe.
I am stuck with Meteor.js, where Jest is currently a no go because it would require a too complicated setup, with a lot of mocks.
It is slightly easier to use Storybook though, since it concerns only the frontend part, so I am investigating if supporting it is worth it or not.

Do you thing it would doable to use this plugin in a Mocha testing environment? What could be the main blocker?

Support React@18

Describe the bug
I'm currently using React 18 in my project and I'm having issues when I try to install this package because of the react version specified in

"react": "^16.8.0 || ^17.0.0"

Is there anything I can do to help? I suppose I can open a PR to update the peerDeps, but I'm not familiar with the project so I don't know if that fix is going to be "that simple"

To Reproduce
Steps to reproduce the behavior:

I can create a repo to reproduce this later today if that's helpful.

Expected behavior
I can install the library

Screenshots

Additional context
Add any other context about the problem here.

Question: Can we use assertions inside interactions and avoid writing tests in jest files ?

Hello,
I have a question about using testing-react and passing stories with jest assertions to jest.

In the examples, in the readme, the stories are used as they already include the necessary decorators so it saves a considerable amount of time and avoids a lot of doublication.

I was wondering if there is a problem (or if it is considered an antipattern) having assertions in the stories as well.

The setup I'm experimenting with is leveraging Interactions play functions with assertions.

Then when I import the stories into jest I run any play functions there are and as far as I can see everything seems to be working as it should.

Tests pass and fail when they should and coverage is also generated:

Psedocode examples:
Story:

//...
export const Default: ComponentStory<typeof InputGroup> = args => (
    <InputGroup {...args} />
);
//...
Default.play = async ({ args, canvasElement }) => {
  const canvas = within(canvasElement);

  await userEvent.type(canvas.getByRole('textbox'), 'This is a search query', {
    delay: 50
  });

  await expect(args.onValueChange).toHaveBeenLastCalledWith(
    'This is a search query'
  );
};

Test file:

//...
import * as stories from './InputGroup.stories';

const testCases = Object.values(composeStories(stories)).map(Story => [
  Story.storyName,
  Story
]) as [string, StoryFn<unknown>][];

// Batch testing
test.each(testCases)('Renders %s story', async (_storyName, Story) => {
  // render story
  const { container } = render(<Story />);

  // run play function if exists
  if (Story.play) {
    await Story.play({
      canvasElement: container
    } as StoryContext<ReactFramework>);
  }
});

The above when run with jest will result in:

image

and will fail if I change the assertion:

image

It will also generate a valid coverage report.

Using setGlobalConfig remove window global overrides from test setup

Describe the bug

Given the following .storybook/preview.ts file:

/* eslint-disable no-use-before-define, react/jsx-filename-extension */
import { Parameters, DecoratorFn } from '@storybook/react';
import { create, ThemeVars } from '@storybook/theming';
import {
  initialize as mswInitialize,
  mswDecorator,
} from 'msw-storybook-addon';
import serverHandlers from '../src/server/handlers';
import { DocsContainer, StoryDecorator } from './components';

const companyTheme: Partial<ThemeVars> = {
  brandTitle: 'Company Console',
  brandImage: 'https://design.company.dev/img/logo/text.svg',
};

export const parameters: Parameters = {
  actions: {
    argTypesRegex: '^on[A-Z].*',
  },
  docs: {
    container: DocsContainer,
  },
  darkMode: {
    dark: create({
      base: 'dark',
      ...companyTheme,
    }),
    light: create({
      base: 'light',
      ...companyTheme,
    }),
  },
  msw: {
    handlers: serverHandlers,
  },
};

export const globalTypes = {
  locale: {
    name: 'Locale',
    description: 'Internationalization locale',
    defaultValue: 'en',
    toolbar: {
      icon: 'globe',
      items: [
        {
          value: 'en',
          right: '🇺🇸',
          title: 'English',
        },
        {
          value: 'fr',
          right: '🇫🇷',
          title: 'Français',
        },
      ],
    },
  },
};

// @see https://github.com/mswjs/msw-storybook-addon/issues/59#issuecomment-974683387
if (!global.test) {
  mswInitialize();
}

export const decorators: DecoratorFn[] = [
  // @ts-expect-error expected config fetched from setup documentation.
  mswDecorator,
  StoryDecorator,
];

And the following src/setupTests.ts file:

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import {
  createMatchMedia,
  server,
} from './test-utils';

window.matchMedia = createMatchMedia();

// @see https://react.i18next.com/misc/testing
jest.mock('react-i18next', () => {
  // @see https://github.com/facebook/jest/issues/2567
  // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
  const { i18n } = require('./test-utils');

  return {
    useTranslation: () => ({
      t: (str: string) => str,
      i18n,
    }),
    // Needed to make storyshots working with the locale global option.
    // @see https://github.com/i18next/i18next/issues/1426#issuecomment-828656983
    initReactI18next: {
      type: '3rdParty',
      init: jest.fn(),
    },
  };
});

beforeAll(() => {
  // Enable mocking.
  server.listen();
});

afterEach(() => {
  server.resetHandlers();
});

afterAll(() => {
  // Clean up once the tests are done.
  server.close();
});

If I add the setGlobalConfig call on it:

diff --git a/src/setupTests.ts b/src/setupTests.ts
index 4d462b6..341c29b 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -3,11 +3,15 @@
 // expect(element).toHaveTextContent(/react/i)
 // learn more: https://github.com/testing-library/jest-dom
 import '@testing-library/jest-dom';
+import { setGlobalConfig } from '@storybook/testing-react';
+import * as globalStorybookConfig from '../.storybook/preview'
 import {
   createMatchMedia,
   server,
 } from './test-utils';
 
+// setGlobalConfig(globalStorybookConfig);
+
 window.matchMedia = createMatchMedia();
 
 // @see https://react.i18next.com/misc/testing

I got the following error on any current test:

  ● Test suite failed to run

    TypeError: window.matchMedia is not a function

      4 |   DocsContainerProps,
      5 | } from '@storybook/addon-docs';
    > 6 | import { useDarkMode } from 'storybook-dark-mode';
        | ^
      7 | import { themes } from '@storybook/theming';
      8 |
      9 | // @see https://github.com/hipstersmoothie/storybook-dark-mode/issues/127#issuecomment-802018811

So I assume the window.matchMedia function, I defined is somewhat override by calling setGlobalConfig.

Looks similar to #34, however, I didn't defined any window.matchMedia function on my preview.ts file.

Decorators are not combined by composeStory

Describe the bug
If your story uses both Component Decorators and Story Decorators only the Story Decorators will work in the test.

To Reproduce
Steps to reproduce the behavior:

  1. Write a story file with two decorators
  2. Apply the first decorator to the default export
  3. apply the second decorator directly to the story
  4. Import the story into a test
  5. use composeStory
  6. expect some behavior from each decorator

Expected behavior
Both decorators should be applied.

Typing of required fields and default args with composeStories error

Great library all, really nice to see stories and unit tests coming together.

Describe the bug
composeStories with default required args cause typing errors, stating that they are missing the required arguments, e.g. children. However, when doing so via composeStory (as per the example), no errors are present.

To Reproduce
Steps to reproduce the behavior:

  1. Run the example provided in testing-react repo
  2. Move the Secondary composed story to the exploded object for composeStories() alongisde Primary. E.g. const { Primary, Secondary } = composeStories(stories)
  3. Note that <Secondary /> without children provided, errors. Stating it requires children
  4. Note that <Secondary /> did not error when generated with the composeStory() function

Expected behavior
I would expect composeStories() to function as composeStory() does but for multiple stories. Default args should be available and automatically picked up, as it is with composeStory().

`name` is changed when composing stories

Describe the bug
According to storybookjs/storybook#18464 (review), name should be used for csf3 stories. However, when I run composeStory from testing-react, the name changes to something like composedStory2. I'd really like to have the name remain unchanged when composing stories.

To Reproduce
https://github.com/IanVS/composed-story-name-reproduction

npm i
npm run storybook

Open the Primary Named story, which uses the Helper.jsx component to render a composed story as well as its name.

Expected behavior
The name of the resulting story should be the csf3 name.

Screenshots
image

Support for Story.storyName

Storybook allows to specify storyName but when using composeStories this information is not available anymore. I can access it if I import the story directly though.

Example:

// In stories file:
CurrentPlan.storyName = 'Current plan';
...
// In Jest test:
import * as stories from 'components/Header.stories';
import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';

test.each(Object.values(composeStories(stories)).map((Story) => [Story.storyName!, Story]))(
  'renders Header for %s story',
  (_storyName, Story) => {
    const tree = render(<Story />);
    expect(tree.baseElement).toMatchSnapshot();
  }
);

React native act bug

This is my test:

import { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react-native';

// ...
	test('Renders correct return url', async () => {
		const { container } = render(<WithReturnUrl />);
		expect(container).toBeDefined();
	});

I am having the next bug console log:

    Warning: An update to App inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at App (/Users/jonatthu/Development/Wayblock.Application/apps/seed/src/storybook/AppCoreStorybook.tsx:24:21)
        at RecoilRoot_INTERNAL (/Users/jonatthu/Development/Wayblock.Application/node_modules/recoil/cjs/recoil.js:3888:3)
        at RecoilRoot (/Users/jonatthu/Development/Wayblock.Application/node_modules/recoil/cjs/recoil.js:4044:5)
        at View
        at Component (/Users/jonatthu/Development/Wayblock.Application/node_modules/react-native/jest/mockComponent.js:28:18)
        at AppCoreStorybook (/Users/jonatthu/Development/Wayblock.Application/apps/seed/src/storybook/AppCoreStorybook.tsx:58:23)
        at composedStory

Any hints of why this is not working?
Is it react native supported?

global decorators receive globals object rather than its value

Describe the bug
Global decorators have different behaviour on unit testing whereas storybook uses the value as context.globals.locale but testing-react passes an global object instead of its value.

Expected behavior
I would expect that the decorator values on unit testing inherited global values the same as the way storybook does.

const LocaleProvider = ({ lang, children }) => {
	console.log(lang);
        return children;
};

// storybook output
'en'

// testing-react output
{
      name: 'Locale',
      description: 'Internationalization locale',
      defaultValue: 'en',
      toolbar: { icon: 'globe', items: [ [Object], [Object] ] }
}
// .storybook/preview.js
import React from "react";
import LocaleProvider from "/src/components/LocaleProvider";

export const decorators = [
	(Story, context) => (
                {/* react-testing passes entire global object here instead of value*/}
		<LocaleProvider lang={context.globals.locale}>
			<Story {...context} />
		</LocaleProvider>
	),
];

export const globalTypes = {
	locale: {
		name: "Locale",
		description: "Internationalization locale",
		defaultValue: "en",
		toolbar: {
			icon: "globe",
			items: [
				{ value: "en", right: "🇺🇸", title: "English" },
			],
		},
	},
};
// src/components/NavService.test.jsx
import React from "react";
import { render, act } from "@testing-library/react";
import { composeStories } from "@storybook/testing-react";
import * as stories from "/src/components/NavServices.stories";

const { Services } = composeStories(stories);

it("renders component", async () => {
	const rendered = render(<Services />);
	await act(() => new Promise((resolve) => setTimeout(resolve)));
	//rendered.debug();
	expect(rendered).toBeTruthy();
});

Possible solution

The workaround I could find is to manually tweak the contents of the locale global on the setupFile.js file

// setupFile.js <-- this will run before the tests in jest.
import { setGlobalConfig } from "@storybook/testing-react";
import * as globalStorybookConfig from "./.storybook/preview"; // path of your preview.js file

+ // manually tweak the value of locale global
+ globalStorybookConfig.globalTypes.locale = globalStorybookConfig.globalTypes.locale.defaultValue;

setGlobalConfig(globalStorybookConfig);

Although I can already see the need of customizing these values per test, so some sort of interface to allow this would be welcome such as:

const Story = ComposeStory(story, meta);
Story.globals.locale = 'fr'

it('renders component in french', ...);

Unintended side effect of loading preview.js in Jest

Describe the bug
Importing preview.js into setupTest.js will leak Storybook specific mocking into Jest.

To Reproduce

// in preview.js
window.Math.random = () => 0.5

// in setupJest.js
window.Math.random = () => 0.42 // this one will get overriden by the mock from preview.js
import { setGlobalConfig } from "@storybook/testing-react";
import * as globalStorybookConfig from "../.storybook/preview"; // path of your preview.js file
setGlobalConfig(globalStorybookConfig);

Expected behavior

The import of preview is meant at getting decorators and other exported variables, yet it will have the side effect of triggering all top-level code from this file.

First, it should be explicitely recommended to load this file on top of setupTest.js, in order to not erase any existing mocks
Also, user should know that loading preview.js will also trigger all side effects from this file.

This is mostly a documentation issue, rather than a bug.

Run test failed TypeError: Super expression must either be null or a function

Describe the bug
Get an error when run the jest test.

> jest "src/components/MultiChoice/MultiChoice.test.tsx"

 FAIL  src/components/MultiChoice/MultiChoice.test.tsx
  ● Test suite failed to run

    TypeError: Super expression must either be null or a function

To Reproduce
Steps to reproduce the behavior:
jest.config.ts

import type { Config } from '@jest/types';

const config: Config.InitialOptions = {
    moduleNameMapper: {
        '\\.(css|scss)$': '<rootDir>/tests/unit/mocks/styleMock.js',
    },
    moduleDirectories: [
        '.',
        'src',
        'node_modules',
    ],
    preset: 'ts-jest',
    reporters: [
        'default',
        ['jest-html-reporters', {
            publicPath: '__reports/report-unit',
            filename: 'index.html',
        }],
    ],
    testEnvironment: 'jsdom',
    setupFiles: ['<rootDir>/tests/setup.ts'],
};

export default config;

setup.ts

import { setGlobalConfig } from '@storybook/testing-react';

import * as globalStorybookConfig from '../.config/storybook/preview';

setGlobalConfig(globalStorybookConfig);

preview.ts

import { withRouter, withTheme } from '../../lib/storybook';

export const decorators = [
    withRouter,
    withTheme,
];

export const parameters = {
    actions: { argTypesRegex: '^on[A-Z].*' },
    layout: 'fullscreen',
};

export const args = { globalDarkTheme: false };

multiChoice.test.tsx

import React from 'react';

import { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react';

import * as stories from './MultiChoice.stories';

const { Full } = composeStories(stories);

describe('MultiChoice', () => {
    test('Render all buttons without cut', () => {
        render(<Full />);
    });
});

Multichoice.stories.tsx

import React from 'react';

import { ComponentMeta, Story } from '@storybook/react';
import { IMultiChoiceCutStyle, IMultiChoiceProps } from 'src/components/MultiChoice/MultiChoice.types';

import { MultiChoice } from '.';

export default {
    title: 'Components/MultiChoice',
    component: MultiChoice,
    argTypes: {
        onSelect: { action: 'select' },
    },
} as ComponentMeta<typeof MultiChoice>;

const Template: Story<IMultiChoiceProps> = args => (
    <div style={{ marginLeft: '16px', marginRight: '16px' }}>
        <MultiChoice {...args} />
    </div>
);

export const Full = Template.bind({});

const items = [
    { id: 2, text: 'Frontend', selected: false },
    { id: 3, text: 'Backend', selected: false },
    { id: 4, text: 'Mobile', selected: false },
];

Full.args = {
    items,
};

2022-11-17_14-53-22

Additional context

  • jest 22.6.3
  • @storybook/react 6.5.9
  • @storybook/testing-react 1.3.0
  • @testing-library/react 13.4.0

Documentation suggests using es6 in jest's setupFiles.js

Describe the bug
Reading the documentation for global decorators here: https://storybook.js.org/addons/@storybook/react-testing

It says to import storybook/preview.js

Import is an es6 feature as is the component story format which is the recommended way to implement preview.js

However, Jest doesn't transpile the setupFiles.js file when starting up: jestjs/jest#9041

This causes errors when trying to run tests:

Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /Users/dan.green/Code/react-build-store/jest-setup-files.js:5
    import { setGlobalConfig } from '@storybook/react';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)

To Reproduce
Steps to reproduce the behavior:

  1. Install Jest 26
  2. Follow instructions for global decorators here: https://storybook.js.org/addons/@storybook/react-testing
  3. Run jest

Expected behavior
Tests should run and composeStory should work.

argsEnhancers are not applied

Is your feature request related to a problem? Please describe.

The interactions addon replaces action arguments with jest mocks that can be inspected in play functions. It would be great to be able to run play functions in jest to test them during CI. This currently does not work because the arguments don't get enhanced.

Describe the solution you'd like
argsEnhancers from global config or other presets should be applied. Then it would be possible to use ...

https://github.com/storybookjs/testing-react#interactions-with-play-function

... in combination with ...

https://storybook.js.org/docs/react/essentials/interactions#writing-interactions

Or specifically the await waitFor(() => expect(args.onSubmit).toHaveBeenCalled()); part.

I think this would have to happen during argument combination:
https://github.com/storybookjs/testing-react/blob/main/src/index.ts#L121-L124

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.