diff --git a/lib/client-api/src/parameters.test.ts b/lib/client-api/src/parameters.test.ts index 689a92bd10ab..e0ed1bc03b70 100644 --- a/lib/client-api/src/parameters.test.ts +++ b/lib/client-api/src/parameters.test.ts @@ -26,4 +26,8 @@ describe('client-api.parameters', () => { }, }); }); + + it('ignores undefined additions', () => { + expect(combineParameters({ a: 1 }, { a: 2 }, { a: undefined })).toEqual({ a: 2 }); + }); }); diff --git a/lib/client-api/src/parameters.ts b/lib/client-api/src/parameters.ts index 62f475c809ce..8f3b0010dde1 100644 --- a/lib/client-api/src/parameters.ts +++ b/lib/client-api/src/parameters.ts @@ -1,12 +1,40 @@ // Utilities for handling parameters -import mergeWith from 'lodash/mergeWith'; - import { Parameters } from '@storybook/addons'; +import isPlainObject from 'lodash/isPlainObject'; -export const combineParameters = (...parameterSets: Parameters[]) => - mergeWith({}, ...parameterSets, (objValue: any, srcValue: any) => { - // Treat arrays as scalars: - if (Array.isArray(srcValue)) return srcValue; +/** + * Safely combine parameters recursively. Only copy objects when needed. + * Algorithm = always overwrite the existing value UNLESS both values + * are plain objects. In this case flag the key as "special" and handle + * it with a heuristic. + */ +export const combineParameters = (...parameterSets: Parameters[]) => { + const mergeKeys: Record = {}; + const combined = parameterSets.reduce((acc, p) => { + Object.entries(p).forEach(([key, value]) => { + const existing = acc[key]; + if (Array.isArray(value) || typeof existing === 'undefined') { + acc[key] = value; + } else if (isPlainObject(value) && isPlainObject(existing)) { + // do nothing, we'll handle this later + mergeKeys[key] = true; + } else if (typeof value !== 'undefined') { + acc[key] = value; + } + }); + return acc; + }, {} as Parameters); - return undefined; + Object.keys(mergeKeys).forEach((key) => { + const mergeValues = parameterSets + .map((p) => p[key]) + .filter((value) => typeof value !== 'undefined'); + if (mergeValues.every((value) => isPlainObject(value))) { + combined[key] = combineParameters(...mergeValues); + } else { + combined[key] = mergeValues[mergeValues.length - 1]; + } }); + + return combined; +}; diff --git a/lib/client-api/src/story_store.ts b/lib/client-api/src/story_store.ts index 7eb0464644d2..01b3df58f3d2 100644 --- a/lib/client-api/src/story_store.ts +++ b/lib/client-api/src/story_store.ts @@ -612,9 +612,7 @@ export default class StoryStore { return Array.from(new Set(this.raw().map((s) => s.kind))); } - getStoriesForKind(kind: string) { - return this.raw().filter((story) => story.kind === kind); - } + getStoriesForKind = (kind: string) => this.raw().filter((story) => story.kind === kind); getRawStory(kind: string, name: string) { return this.getStoriesForKind(kind).find((s) => s.name === name);