From fcb5c346f23c1d11ba98795676ded12ac15128d4 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 20 Feb 2024 16:01:45 +0100 Subject: [PATCH 1/2] Only mutate arg keys when writable --- code/addons/interactions/src/preview.test.ts | 34 ++++++++++++++++++++ code/addons/interactions/src/preview.ts | 8 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 code/addons/interactions/src/preview.test.ts diff --git a/code/addons/interactions/src/preview.test.ts b/code/addons/interactions/src/preview.test.ts new file mode 100644 index 000000000000..477ff638bea4 --- /dev/null +++ b/code/addons/interactions/src/preview.test.ts @@ -0,0 +1,34 @@ +import { expect, test } from 'vitest'; +import { fn, isMockFunction } from '@storybook/test'; +import { action } from '@storybook/addon-actions'; + +import { traverseArgs } from './preview'; + +test('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; + expect(traversed).toEqual({ + deep: { + deeper: { + fnKey: args.deep.deeper.fnKey, + actionKey: args.deep.deeper.actionKey, + }, + }, + arg2: args.arg2, + }); + + expect(traversed.deep.deeper.fnKey.getMockName()).toEqual('fnKey'); + const actionFn = traversed.deep.deeper.actionKey; + 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; } From 8ceb63664b883f8268b58438e08bceb2e1f2e00e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 22 Feb 2024 10:27:27 +0100 Subject: [PATCH 2/2] Add comments --- code/addons/interactions/src/preview.test.ts | 32 ++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/code/addons/interactions/src/preview.test.ts b/code/addons/interactions/src/preview.test.ts index 477ff638bea4..5cfec9d19d03 100644 --- a/code/addons/interactions/src/preview.test.ts +++ b/code/addons/interactions/src/preview.test.ts @@ -1,10 +1,10 @@ -import { expect, test } from 'vitest'; +import { describe, expect, test } from 'vitest'; import { fn, isMockFunction } from '@storybook/test'; import { action } from '@storybook/addon-actions'; import { traverseArgs } from './preview'; -test('traverseArgs', () => { +describe('traverseArgs', () => { const args = { deep: { deeper: { @@ -18,17 +18,25 @@ test('traverseArgs', () => { expect(args.deep.deeper.fnKey.getMockName()).toEqual('spy'); const traversed = traverseArgs(args) as typeof args; - expect(traversed).toEqual({ - deep: { - deeper: { - fnKey: args.deep.deeper.fnKey, - actionKey: args.deep.deeper.actionKey, + + test('The same structure is maintained', () => + expect(traversed).toEqual({ + deep: { + deeper: { + fnKey: args.deep.deeper.fnKey, + actionKey: args.deep.deeper.actionKey, + }, }, - }, - arg2: args.arg2, - }); + // 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')); - expect(traversed.deep.deeper.fnKey.getMockName()).toEqual('fnKey'); const actionFn = traversed.deep.deeper.actionKey; - expect(isMockFunction(actionFn) && actionFn.getMockName()).toEqual('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')); });