Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

argTypes mapping doesn't work with Vue props #14420

Open
catrope opened this issue Mar 31, 2021 · 19 comments
Open

argTypes mapping doesn't work with Vue props #14420

catrope opened this issue Mar 31, 2021 · 19 comments

Comments

@catrope
Copy link

catrope commented Mar 31, 2021

Describe the bug
When using the new mapping feature for an argType that corresponds to a prop of a Vue component, the mapping is applied to the value that is passed to args, but is not applied to the value of the prop that is passed to the component.

To Reproduce
Steps to reproduce the behavior:

  1. Create a story for a Vue component, with automatic prop detection (using the docs addon)
  2. For one of the props, add an entry to the story's argTypes that includes options and mapping keys
  3. Open the story in the browser, and change the value of this prop back and forth

Expected behavior
The mapping should be applied to the value of the prop, but it's not.

Code snippets

import MyButton from './Button.vue`;
import Vue from 'vue';
import { Args, StoryContext } from '@storybook/addons';

export default {
	title: 'Components/Button',
	component: MyButton,
	argTypes: {
		label: {
			options: [ 'short', 'medium', 'long' ],
			mapping: {
				short: 'Short',
				medium: 'Medium text',
				long: 'Very long text that is meant to demonstrate how the button wraps text'
			},
			defaultValue: 'short',
			control: 'inline-radio'
		}
	}
};

export const SimpleStory = ( _args: Args, { argTypes } : StoryContext ) : Vue.Component =>
	Vue.extend( {
		components: { MyButton },
		props: Object.keys( argTypes ),
		template: `<my-button v-bind="$props" />`
	} );

With this snippet, I expect the label of the button to change between Short, Medium text and Very long text that... (the mapped values) when the user clicks the radio buttons in the controls panel. Instead, the label of the button changes between short, medium and long (the unmapped values).

Interestingly, if you put console.log( _args.label ) somewhere (either in the arrow function above Vue.extend, or in a mounted lifecycle hook), it prints Short, indicating that the values in _args are mapped correctly. That mapping just isn't applied to the props that are passed to the Vue component.

(I'm using Vue 2 here, I haven't tested this with Vue 3.)

System

$ npx sb@next info

Environment Info:

  System:
    OS: Linux 5.4 Ubuntu 20.04.2 LTS (Focal Fossa)
    CPU: (8) x64 Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
  Binaries:
    Node: 14.16.0 - ~/.nvm/versions/node/v14.16.0/bin/node
    npm: 7.6.0 - ~/.nvm/versions/node/v14.16.0/bin/npm
  Browsers:
    Chrome: 89.0.4389.90
    Firefox: 87.0
  npmPackages:
    @storybook/addon-a11y: 6.2.1 => 6.2.1 
    @storybook/addon-actions: 6.2.1 => 6.2.1 
    @storybook/addon-backgrounds: 6.2.1 => 6.2.1 
    @storybook/addon-controls: 6.2.1 => 6.2.1 
    @storybook/addon-docs: 6.2.1 => 6.2.1 
    @storybook/addon-links: 6.2.1 => 6.2.1 
    @storybook/addon-storysource: 6.2.1 => 6.2.1 
    @storybook/addon-viewport: 6.2.1 => 6.2.1 
    @storybook/vue: 6.2.1 => 6.2.1 

┆Issue is synchronized with this Asana task by Unito

@lee-chase
Copy link

Select example show here fails in Vue as a result
https://storybook.js.org/docs/vue/essentials/controls#dealing-with-complex-values

@shilman shilman removed this from the 6.2 stabilization milestone Jun 8, 2021
@tillsanders
Copy link

I needed argTypes to inject complex data that could not be serialized to JSON, resulting in this console warning:

We've detected a cycle in arg 'X'. Args should be JSON-serializable.

So while I haven't been able to get argTypes with mapping to work, presumably for the reasons mentioned in this issue, I found a workaround for my specific use-case: You can inject complex variables using the render() method instead of the template string. Example:

const Template: Story<Props> = (args, { argTypes }) => ({
  components: {
    MyComponent,
  },
  props: Object.keys(argTypes),
  render: (h) => h('MyComponent', {
    props: {
      myComplexVariable,
      ...args,
    },
  }),
});

@shilman
Copy link
Member

shilman commented Nov 18, 2021

@tillsanders have you tried the mapping feature in 6.3? i'll update the error accordingly

https://storybook.js.org/docs/react/writing-stories/args#mapping-to-complex-arg-values

@tillsanders
Copy link

@shilman I'm using v6.3.12.

This is what I tried, but instead of the group variable, I only received the string 'Group':

export default {
  title: 'Components/Editor',
  component: Editor,
    argTypes: {
      group: {
        options: ['Group'],
        mapping: {
          'Group': group,
        },
      },
    },
  ),
};

@shilman
Copy link
Member

shilman commented Nov 19, 2021

@ghengeveld does this look right to you? ☝️

@VasiliDarozhkin
Copy link

VasiliDarozhkin commented Nov 19, 2021

The same happens to me on 6.3.12

const propValues = {prop1, prop2} // prop1 and prop2 are objects

export default {
  title: 'MyTitle',
  argTypes: {
    propToPass: {
      name: 'Prop Name',
      options: Object.keys(propValues),
      mapping: propValues,
      control: {
        type: 'select',
        labels: {
          prop1: 'Label 1',
          prop2: 'Label 2',
        }
      },
    },
 }

then the Storybook throws in console
Invalid prop: type check failed for prop "propToPass". Expected Object, got String with value "prop1".

Labels work fine though

@xander-marjoram
Copy link

I can confirm I'm seeing the same behaviour.

customNavLinks: {
    description: 'Custom navigation links',
    options: ['OptionA', 'OptionB', 'OptionC'],
    mapping: {
        OptionA: ['Array A'],
        OptionB: ['Array B'],
        OptionC: ['Array C']
    },
    control: {
        type: 'select',
        labels: {
            OptionA: 'Label A',
            OptionB: 'Label B',
            OptionC: 'Label C'
        }
    }
},

The control labels are correctly appearing as "Label A", "Label B" and "Label C", but when I select one of them I see the following error:

[Vue warn]: Invalid prop: type check failed for prop "customNavLinks". Expected Array, got String with value "OptionA".

@stale
Copy link

stale bot commented Jan 9, 2022

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@stale stale bot added the inactive label Jan 9, 2022
@xander-marjoram
Copy link

This issue is still active.

@stale stale bot removed the inactive label Jan 10, 2022
@msetegn
Copy link

msetegn commented Feb 13, 2022

Can confirm that this is still an issue

@whereisalex
Copy link

Facing the same issue

@AndreiSoroka
Copy link

+1

@vandor
Copy link

vandor commented Jun 8, 2022

I've also just run into this with my Vue2 storybook.

@Sidnioulz
Copy link
Contributor

Sidnioulz commented Oct 14, 2022

I've just checked in code/lib/store/src/csf/prepareStory.ts, and I can see that the mapping is correctly computed. In Vue 2, we have renderTimePassArgsFirst true, so we do receive the includedContext.args correctly.

From there, in my story function, I can see it is the mapped values that are passed to args. The trick is to actually bind the component to those args instead of $props, and then it works.

MWE:

export const Default = (args, context) => {
  return {
    components: {
        MyComponent,
    },
    data: () => ({ args }),
    props: Object.keys(argTypes),
    template: `<MyComponent v-bind="args" />`,
  }
}

Can someone else confirm this bug is fixed?

EDIT: note that control values are never updated in canvas mode on Vue, that's a separate bug. But in the docs tab, you can see controls map both literal and complex values if you pass them that way instead of via $props (which somehow is not updated).

@shilman shilman removed the P1 label Oct 18, 2022
@Sidnioulz
Copy link
Contributor

I said lots of silly things so let's go at it again.

Mappings work in the docs renderer. That is because it always rerenders the whole component on control change. They do not work in the canvas renderer.

I have tested on

  • StoryBook 6.4 / Vue 2.6 / Webpack 4 / Vue-Class-Component latest
  • StoryBook 7.0-alpha37 / Vue 2.7 / Vite / Vue-Class-Component latest

In SB7, both canvas and docs share their rendering code and both exhibit the bug. The value is not mapped.

Until this can be fixed, please find an updated MWE workaround:

// Assuming you have MyComponent, and args

const Template = (storyArgs, { argTypes } => {
  return {
    computed: {
      props() {
        const finalProps = { ...this.$props }

        // Reapply mappings to circumvent Storybook Vue 2 bug.
        Object.entries(this.$props)
          .filter(([key, value]) =>
            Object.hasOwn(argTypes[key]?.mapping || {}, value),
          )
          .forEach(([key, value]) => {
            finalProps[key] = argTypes[key].mapping[value]
          })

        return finalProps
      },
    },
    props: Object.keys(argTypes),
    template: `<MyComponent v-bind="props" />`,
  }
}

Template.args = args

@shilman
Copy link
Member

shilman commented Oct 24, 2022

@prashantpalikhe If you have a min, this is highly related to the last set of fixes you made to the Vue2 renderer. In fact, I'm a bit surprised they didn't solve this issue.

@gleb-chetvertak
Copy link

gleb-chetvertak commented Mar 12, 2023

With this.$props Storybook Docs work, but Canvas doesn't (btw, before the workaround it was vice-versa).
Using storyArgs instead of this.$props seems to work well in both tabs.

// Alert.stories.js

import AppAlert from './Alert.vue'

export default {
  title: 'Alert',
  component: AppAlert,
  argTypes: {
    title: {
      control: 'text',
    },
    description: {
      control: {
        type: 'inline-radio',
      },
      options: ['none', 'inline', 'array', 'arraySingleOption'],
      mapping: {
        none: '',
        inline: 'Description as string',
        array: [
          'Array of options - the first item',
          'Array of options - the second item',
          'Array of options - the third item',
        ],
        arraySingleOption: ['Array of options - the only option'],
      },
    },
    variant: {
      options: ['info', 'error'],
      control: 'inline-radio',
    },
  },
  args: {
    title: '',
    description: 'inline',
    variant: 'info',
  },
}

const Template = (storyArgs, { argTypes }) => ({
  components: { AppAlert },
  props: Object.keys(argTypes),
  computed: {
    args() {
      const finalProps = { ...storyArgs }

      // Reapply mappings to circumvent Storybook bug.
      // https://github.com/storybookjs/storybook/issues/14420
      Object.entries(storyArgs)
        .filter(([key, value]) => Object.hasOwn(argTypes[key]?.mapping || {}, value))
        .forEach(([key, value]) => {
          finalProps[key] = argTypes[key].mapping[value]
        })

      return finalProps
    },
  },
  template: `<AppAlert v-bind="args" />`,
})

export const Default = Template.bind({})

export const Array = Template.bind({})
Array.args = {
  description: 'array',
}

@Sidnioulz
Copy link
Contributor

On Storybook 7 with Vue 3, argtype mappings work much better: in both canvas and doc modes, clicking a control applies the mapped argtype value. I'm not able to test with Vue 2 any more though.

But not everything works. In both modes, the default value for a mapped argtype is not mapped to the corresponding control, so the control looks like no value is selected. I'm making a separate ticket for this.

@mamdasan
Copy link

I said lots of silly things so let's go at it again.

Mappings work in the docs renderer. That is because it always rerenders the whole component on control change. They do not work in the canvas renderer.

I have tested on

  • StoryBook 6.4 / Vue 2.6 / Webpack 4 / Vue-Class-Component latest
  • StoryBook 7.0-alpha37 / Vue 2.7 / Vite / Vue-Class-Component latest

In SB7, both canvas and docs share their rendering code and both exhibit the bug. The value is not mapped.

Until this can be fixed, please find an updated MWE workaround:

// Assuming you have MyComponent, and args

const Template = (storyArgs, { argTypes } => {
  return {
    computed: {
      props() {
        const finalProps = { ...this.$props }

        // Reapply mappings to circumvent Storybook Vue 2 bug.
        Object.entries(this.$props)
          .filter(([key, value]) =>
            Object.hasOwn(argTypes[key]?.mapping || {}, value),
          )
          .forEach(([key, value]) => {
            finalProps[key] = argTypes[key].mapping[value]
          })

        return finalProps
      },
    },
    props: Object.keys(argTypes),
    template: `<MyComponent v-bind="props" />`,
  }
}

Template.args = args

this solution solved my problem,
i think the problem is in canvas, the mapping value is'nt passed but instead the string it self is passed.
and this solution solves that problem
anyway, thanks for the work around, i hope the storybook team solves this problem, as a lot of big projects are still working on vue 2, and this feature is really usefull for testing complex components

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests