Skip to content

Commit

Permalink
Merge pull request #26118 from storybookjs/kasper/traverse-args-bug
Browse files Browse the repository at this point in the history
Addon-interactions: Only mutate arg keys when writable
  • Loading branch information
kasperpeulen authored Feb 22, 2024
2 parents d103115 + 8ceb636 commit dd10004
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 3 deletions.
42 changes: 42 additions & 0 deletions code/addons/interactions/src/preview.test.ts
Original file line number Diff line number Diff line change
@@ -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'));
});
8 changes: 5 additions & 3 deletions code/addons/interactions/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, unknown>)[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<string, unknown>)[k] = traverseArgs(v, depth, k);
}
}
return value;
}
Expand Down

0 comments on commit dd10004

Please sign in to comment.