Make sure the render functions is a proper react components

This commit is contained in:
Kasper Peulen 2025-03-20 15:40:19 +01:00
parent 5b4401b2e1
commit 69dee4cde6
6 changed files with 49 additions and 10 deletions

View File

@ -246,7 +246,7 @@ export interface StoryContext<TRenderer extends Renderer = Renderer, TArgs = Arg
abortSignal: AbortSignal;
canvasElement: TRenderer['canvasElement'];
hooks: unknown;
originalStoryFn: StoryFn<TRenderer>;
originalStoryFn: ArgsStoryFn<TRenderer>;
viewMode: ViewMode;
step: StepFunction<TRenderer, TArgs>;
context: this;

View File

@ -0,0 +1,14 @@
import React from 'react';
import type { DecoratorFunction, LegacyStoryFn } from 'storybook/internal/types';
import { defaultDecorateStory } from 'storybook/preview-api';
import type { ReactRenderer } from './types';
export const applyDecorators = (
storyFn: LegacyStoryFn<ReactRenderer>,
decorators: DecoratorFunction<ReactRenderer>[]
): LegacyStoryFn<ReactRenderer> => {
return defaultDecorateStory((context) => React.createElement(storyFn, context), decorators);
};

View File

@ -1,7 +1,6 @@
import type { DecoratorFunction, LegacyStoryFn } from 'storybook/internal/types';
import { defaultDecorateStory } from 'storybook/preview-api';
import { applyDecorators as defaultDecorateStory } from '../applyDecorators';
import type { ReactRenderer } from '../types';
import { jsxDecorator } from './jsxDecorator';

View File

@ -264,10 +264,7 @@ export const jsxDecorator = (
...(context?.parameters.jsx || {}),
} as Required<JSXOptions>;
// Exclude decorators from source code snippet by default
const storyJsx = context?.parameters.docs?.source?.excludeDecorators
? (context.originalStoryFn as ArgsStoryFn<ReactRenderer>)(context.args, context)
: story;
const storyJsx = context.originalStoryFn(context.args, context);
const sourceJsx = mdxToJsx(storyJsx);

View File

@ -9,6 +9,7 @@ export const parameters = { renderer: 'react' };
export { render } from './render';
export { renderToCanvas } from './renderToCanvas';
export { mount } from './mount';
export { applyDecorators } from './applyDecorators';
export const decorators: Decorator[] = [
(story, context) => {

View File

@ -1,5 +1,7 @@
import type { FC } from 'react';
import React, { createContext, useContext } from 'react';
import React, { createContext, useContext, useState } from 'react';
import { useParameter } from 'storybook/internal/preview-api';
import type { Meta, StoryObj } from '@storybook/react';
@ -29,11 +31,11 @@ export const All: StoryObj<typeof Component> = {
],
};
// This story will error if `parameters.docs.source.excludeDecorators` is true:
// This story should not error
// See https://github.com/storybookjs/storybook/issues/21900
const TestContext = createContext<boolean>(false);
export const Context: StoryObj<typeof Component> = {
// parameters: { docs: { source: { excludeDecorators: true } } },
parameters: { docs: { source: { excludeDecorators: true } } },
decorators: [
(Story) => (
<TestContext.Provider value>
@ -50,3 +52,29 @@ export const Context: StoryObj<typeof Component> = {
return <p>Story</p>;
},
};
/**
* This story demonstrates is a regression test for this issue with React hooks in Storybook
* (https://github.com/storybookjs/storybook/issues/29189)
*
* Which happened when a decorator was using storybook hooks, and the render react hooks.
*/
export const AllowUseStateInRender: StoryObj = {
render: () => {
const [count, setCount] = useState(0);
const Button = (globalThis as any).Components.Button;
return <Button onClick={() => setCount(count + 1)} label={`Clicked ${count} times`} />;
},
decorators: [
(storyFn) => {
useParameter('docs', {});
return storyFn();
},
],
play: async ({ canvas, userEvent }) => {
const button = await canvas.findByText('Clicked 0 times');
await userEvent.click(button);
await canvas.findByText('Clicked 1 times');
},
tags: ['!vitest'],
};