storybook: withActions: CustomEvent detail missing

Is your feature request related to a problem? Please describe. I had originally filed this as a bug report, but belatedly decided that it’s technically a feature request.

Basically, though, when I am building a WebComponent which generates a CustomEvent, I want to be sure that the CustomEvent is being generated properly.

Describe the solution you’d like withActions (from @storybook/addon-actions) should include in the log the detail property of an event it receives if the event is a CustomEvent (or there should be some other documented way for stories to listen for a CustomEvent and see its detail property in the log).

Describe alternatives you’ve considered (using html from lit element and render from lit html), I render: <button type="button" @click="${this.publishtest}">publish</button>

And, I add a publish method on that class:

publishtest() {
  this.dispatchEvent(new CustomEvent('pubexample', {detail: {a: 1, b: 2}, bubbles: true}));
}

And, in my stories I include: .addDecorator(withActions('pubexample'))

I click the button, and something representing my example event shows up in the console log, but the detail property appears to be missing (or at least is not accessible from the console.log in chrome).

Are you able to assist bring the feature to reality? I don’t know - I’ll try implementing this tomorrow and submit a pull request if I can make this work.

Additional context Possibly relevant is that the logged message has a data property which has an args property which is an array of one element which is seems to be my CustomEvent but the only visible property of that item is {isTrusted: false}. Meanwhile, if I save that CustomEvent globally, and display it in the log, I see something completely different (with about 16 properties, but Object.keys() on that object only gets me isTrusted and _constructor-name_ – so a part of the issue here is simply that chrome doesn’t let me see inherited properties in the context of the logged message, perhaps because the CustomEvent is deeply nested in the message).

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 25 (10 by maintainers)

Most upvoted comments

@shilman an update for telesync is necessary https://github.com/storybookjs/telejson/blob/master/src/index.ts#L209 in this line we need


const constructorName = value.constructor.name;
Object.assign(value, {
  '_constructor-name_': constructorName,
  ...(constructorName.toLowerCase().includes('event') ? {
    eventData: JSON.parse(JSON.stringify(value, Object.keys(value.constructor.prototype))),
    details: value.detail,
  } : {}),
});

then we get the result as my snapshots above shown.

cheers

Gadzooks!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.3.0-alpha.23 containing PR #14879 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease

Closing this issue. Please re-open if you think there’s still more to do.

We want to address this in 6.3. If you want to contribute to Storybook, we’ll prioritize PR review for any fixes here. And if you’d like any help getting started, please jump into the #support channel on our Discord: https://discord.gg/storybook

After doing a few tries, I discovered that almost all the “properties” of the CustomEvent are actually class getters, the only property is isTrusted.

It means, that if you try to duplicate or serialize a CustomEvent, you will only get the property isTrusted since class getters are not considered as properties.

Examples:

  • Object.assign({}, new CustomEvent('test', {detail: 123})) // => { isTrusted: false }
  • JSON.stringify(new CustomEvent('test', {detail: 123})) // => {"isTrusted":false}

I assume that react-inspector is doing a serialization or a copy of the original event somewhere before to render it. Resulting in the loss of the other “properties”.

I managed to workaround that issue by adding a custom decorator to the addons-actions like this:

// .storybook/helpers/actions.js
import { decorate } from '@storybook/addon-actions';

const eventProperties = [
  'bubbles',
  'cancelBubble',
  'cancelable',
  'composed',
  'currentTarget',
  'defaultPrevented',
  'detail',
  'eventPhase',
  'isTrusted',
  'path',
  'returnValue',
  'srcElement',
  'target',
  'timeStamp',
  'type',
];

const cloneEventObj = (eventObj, overrideObj = {}) => {
  class EventCloneFactory {
    constructor (override){
      for(const prop of eventProperties){
        this[prop] = eventObj[prop];
      }

      for(const prop in override){
        this[prop] = override[prop];
      }
    }
   }
   EventCloneFactory.prototype.constructor = eventObj.constructor;
   return new EventCloneFactory(overrideObj);
}

const uniqueId = (eventName) => `${eventName}-${(new Date()).getTime()}`;

export const customEvent = decorate([args => {
  const originalEvent = args[0];
  const ev = cloneEventObj(originalEvent, {
    id: uniqueId(originalEvent.type),
  });
  return [ev];
}]);
// story/test.stories.js
import { customEvent } from '../.storybook/helpers/actions';

export default {
  title: 'Test',
  decorators: [
      customEvent.withActions('test'),
  ],
};

...

There’s probably a better way to do that, but at least it works 🤷‍♂️

The @click mechanism is slightly different from onClick – see, for example https://lit-element.polymer-project.org/guide/events

[And, on the positive side, despite a sordid history of CustomEvent problems with detail, in this context detail is not being lost – it’s just not visible as it’s logged here. (But I haven’t come up with a better alternative for logging, yet, either.)]