diff --git a/code/addons/docs/src/blocks/controls/options/Options.tsx b/code/addons/docs/src/blocks/controls/options/Options.tsx index cb0eb2ce79dc..0c8ec9c10015 100644 --- a/code/addons/docs/src/blocks/controls/options/Options.tsx +++ b/code/addons/docs/src/blocks/controls/options/Options.tsx @@ -21,7 +21,12 @@ import { SelectControl } from './Select'; const normalizeOptions = (options: Options, labels?: Record) => { if (Array.isArray(options)) { return options.reduce((acc, item) => { - acc[labels?.[item] || String(item)] = item; + const label = labels?.[item]; + // Ensure the label is a string to avoid using non-string values (e.g., Array prototype + // methods) as object keys. This can happen when an option's name matches a built-in array + // method (e.g. 'reverse') and `labels` is inadvertently an array instead of a Record. + // See: https://github.com/storybookjs/storybook/issues/30142 + acc[typeof label === 'string' && label !== '' ? label : String(item)] = item; return acc; }, {}); } diff --git a/code/addons/docs/src/blocks/controls/options/RadioOptions.stories.tsx b/code/addons/docs/src/blocks/controls/options/RadioOptions.stories.tsx index e0a5bb706a4e..c492511bc2ab 100644 --- a/code/addons/docs/src/blocks/controls/options/RadioOptions.stories.tsx +++ b/code/addons/docs/src/blocks/controls/options/RadioOptions.stories.tsx @@ -115,3 +115,28 @@ export const ArrayReadonly: Story = { }, }, }; + +// Regression test for https://github.com/storybookjs/storybook/issues/30142 +// Options whose names match Array prototype methods (e.g. 'reverse') must be +// displayed as plain text, not as the stringified native function. +const optionsWithArrayMethodNames = ['normal', 'reverse', 'filter', 'map']; +export const ArrayWithBuiltinNames: Story = { + name: 'Array with option names matching built-in Array methods', + args: { + value: optionsWithArrayMethodNames[0], + argType: { options: optionsWithArrayMethodNames }, + }, +}; + +// Reproduces the exact bug from issue #30142: when `labels` is inadvertently an array +// (instead of a Record), `labels?.['reverse']` resolves to `Array.prototype.reverse` — a +// truthy native function — causing the label to render as "function reverse() { [native code] }". +export const ArrayWithBuiltinNamesAndArrayLabels: Story = { + name: 'Array with built-in names and labels accidentally passed as array (bug #30142)', + args: { + value: optionsWithArrayMethodNames[0], + argType: { options: optionsWithArrayMethodNames }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + labels: optionsWithArrayMethodNames as any, + }, +};