diff --git a/code/addons/actions/src/runtime/action.ts b/code/addons/actions/src/runtime/action.ts index f6779d1a1a64..a647a8eb0d1b 100644 --- a/code/addons/actions/src/runtime/action.ts +++ b/code/addons/actions/src/runtime/action.ts @@ -103,6 +103,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti channel.emit(EVENT_ID, actionDisplayToEmit); }; handler.isAction = true; + handler.implicit = options.implicit; return handler; } diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index d751cbf7bc58..ef10d79b3ca0 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -1,22 +1,41 @@ /* eslint-disable no-underscore-dangle */ -import type { - Args, - LoaderFunction, - PlayFunction, - PlayFunctionContext, - StepLabel, -} from '@storybook/types'; +import type { Args, ArgsEnhancer, Renderer } from '@storybook/types'; import { instrument } from '@storybook/instrumenter'; +import { fn } from '@storybook/test'; -export const { step: runStep } = instrument( - { - step: (label: StepLabel, play: PlayFunction, context: PlayFunctionContext) => - play(context), - }, - { intercept: true } -); +const addSpies = (value: unknown, depth = 0): any => { + // Make sure to not get in infinite loops with self referencing args + if (depth > 5) return value; -const instrumentSpies: LoaderFunction = ({ initialArgs }) => { + if (value == null) return value; + if ( + typeof value === 'function' && + 'isAction' in value && + value.isAction && + !('implicit' in value && value.implicit) && + !('_isMockFunction' in value && value._isMockFunction) + ) { + return fn(value as any); + } + + if (Array.isArray(value)) { + return value.map((item) => addSpies(item, depth++)); + } + + if (typeof value === 'object') { + // We have to mutate the original object for this to survive HMR. + + for (const [k, v] of Object.entries(value)) { + (value as Record)[k] = addSpies(v, depth++); + } + return value; + } + return value; +}; + +const wrapActionsInSpyFns: ArgsEnhancer = ({ initialArgs }) => addSpies(initialArgs); + +const instrumentSpies: ArgsEnhancer = ({ initialArgs }) => { const argTypesWithAction = Object.entries(initialArgs).filter( ([, value]) => typeof value === 'function' && @@ -29,13 +48,12 @@ const instrumentSpies: LoaderFunction = ({ initialArgs }) => { const instrumented = instrument({ [key]: () => value }, { retain: true })[key]; acc[key] = instrumented(); // this enhancer is being called multiple times - value._instrumented = true; return acc; }, {} as Args); }; -export const argsEnhancers = [instrumentSpies]; +export const argsEnhancers = [wrapActionsInSpyFns, instrumentSpies]; export const parameters = { throwPlayFunctionExceptions: false,