diff --git a/code/addons/interactions/src/preview.test.ts b/code/addons/interactions/src/preview.test.ts new file mode 100644 index 000000000000..5cfec9d19d03 --- /dev/null +++ b/code/addons/interactions/src/preview.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from 'vitest'; +import { fn, isMockFunction } from '@storybook/test'; +import { action } from '@storybook/addon-actions'; + +import { traverseArgs } from './preview'; + +describe('traverseArgs', () => { + const args = { + deep: { + deeper: { + fnKey: fn(), + actionKey: action('name'), + }, + }, + arg2: Object.freeze({ frozen: true }), + }; + + expect(args.deep.deeper.fnKey.getMockName()).toEqual('spy'); + + const traversed = traverseArgs(args) as typeof args; + + test('The same structure is maintained', () => + expect(traversed).toEqual({ + deep: { + deeper: { + fnKey: args.deep.deeper.fnKey, + actionKey: args.deep.deeper.actionKey, + }, + }, + // We don't mutate frozen objects, but we do insert them back in the tree + arg2: args.arg2, + })); + + test('The mock name is mutated to be the arg key', () => + expect(traversed.deep.deeper.fnKey.getMockName()).toEqual('fnKey')); + + const actionFn = traversed.deep.deeper.actionKey; + + test('Actions are wrapped in a spy', () => expect(isMockFunction(actionFn)).toBeTruthy()); + test('The spy of the action is also matching the arg key ', () => + expect(isMockFunction(actionFn) && actionFn.getMockName()).toEqual('actionKey')); +}); diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index e0cc0aeae87a..6e70166629aa 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -16,7 +16,7 @@ export const { step: runStep } = instrument( { intercept: true } ); -const traverseArgs = (value: unknown, depth = 0, key?: string): any => { +export const traverseArgs = (value: unknown, depth = 0, key?: string): unknown => { // Make sure to not get in infinite loops with self referencing args if (depth > 5) return value; if (value == null) return value; @@ -45,9 +45,11 @@ const traverseArgs = (value: unknown, depth = 0, key?: string): any => { if (typeof value === 'object' && value.constructor === Object) { depth++; - // We have to mutate the original object for this to survive HMR. for (const [k, v] of Object.entries(value)) { - (value as Record)[k] = traverseArgs(v, depth, k); + if (Object.getOwnPropertyDescriptor(value, k).writable) { + // We have to mutate the original object for this to survive HMR. + (value as Record)[k] = traverseArgs(v, depth, k); + } } return value; }