storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.95k stars 9.22k forks source link

[Documentation]: Angular - The documentation for handing event emitters is non existant. #28658

Open mmorris8 opened 1 month ago

mmorris8 commented 1 month ago

Describe the problem

I've looked for 10 hours. There is no documentation on how to verify that a component is emitting an event. Since this feature is intimate to how Angular works, particularly as part of two-way binding, it's absence is entirely unacceptable.

Given this component:

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'cv-button',
  standalone: true,
  template: `
    <button (click)="onClick()">Click Me</button>
  `,
  imports: [],
})
export class ButtonComponent {
  private count = 0
  @Output() clickCount = new EventEmitter<number>

  onClick() {
    this.count++
    this.clickCount.emit(this.count)
  }
}

I can test the click with this story.

import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { ButtonComponent } from './button.component';
import { userEvent, within } from '@storybook/test';

const meta:Meta<ButtonComponent> = {
  title: 'Button Test',
  component: ButtonComponent,
  tags: ['autodocs'],

  decorators: [
    moduleMetadata({
      imports: []
    })
  ]
}

export default meta

type Story = StoryObj<ButtonComponent>

export const Primary: Story = {
  args: {},
  play: async ({canvasElement, args}) => {
    const element = within(canvasElement)
    userEvent.click(element.getByText('Click Me'))
  }
}

But there's no page on this site or anywhere on the net I've been able to find that clearly states how to check if the clickCount was incremented. I know it has to be possible - Storybook shows it is going up in the Actions inspector. But that inspector is entirely useless for an automated test.

Someone please help. This has been incredibly frustrating and has me on the verge of giving up using Storybook altogether with Angular, as being unable to test if components are actually emitting the values they are supposed to be emitting renders the whole exercise pointless.

Additional context

No response

greptile-apps[bot] commented 1 month ago

Disclaimer This information might be inaccurate, due to it being generated automatically To verify that a component is emitting an event in Storybook for Angular, you can use the args parameter in the play function to listen for the event. Here's how you can modify your story to check if clickCount was incremented:

import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { ButtonComponent } from './button.component';
import { userEvent, within } from '@storybook/testing-library';

const meta: Meta<ButtonComponent> = {
  title: 'Button Test',
  component: ButtonComponent,
  tags: ['autodocs'],
  decorators: [
    moduleMetadata({
      imports: []
    })
  ]
};

export default meta;

type Story = StoryObj<ButtonComponent>;

export const Primary: Story = {
  args: {},
  play: async ({ canvasElement, args }) => {
    const element = within(canvasElement);
    const clickCountSpy = jest.fn();
    args.clickCount = { emit: clickCountSpy };
    userEvent.click(element.getByText('Click Me'));
    expect(clickCountSpy).toHaveBeenCalledWith(1);
  }
};

This code uses Jest to create a spy function that listens for the clickCount event and verifies that it was called with the expected value.

References

/code/addons/docs/angular /code/frameworks/angular/src/client/docs/testfixtures/doc-button/argtypes.snapshot /code/frameworks/angular/template/stories/others/issues/12009-unknown-component.stories.ts /code/lib/cli/src/automigrate/fixes/angular-builders.ts /test-storybooks/ember-cli/ember-output/storybook-docgen /code/frameworks/ember/src/client/preview/docs /code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/reference-type-events /code/frameworks/angular/src/builders/build-storybook/index.spec.ts /.github/DISCUSSION_TEMPLATE/ideas.yml /code/frameworks/angular/template/stories/others/issues /code/core/src/manager-api/tests/stories.test.ts /code/frameworks/angular/src/builders/start-storybook/index.spec.ts /docs/_snippets/storybook-preview-with-angular-polyfills.md /code/lib/cli/src/automigrate/fixes/angular-builders.test.ts /docs/_snippets/storybook-interactions-step-function.md /docs/_snippets/custom-build-script-production.md /code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts /.github/DISCUSSION_TEMPLATE/help.yml /docs/_snippets/button-story-hypothetical-example.md /docs/_snippets/checkbox-story-grouped.md /code/frameworks/angular/template/stories/basics/component-with-ng-content /code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.test.ts /code/frameworks/angular/src/client/docs/testfixtures/doc-button/properties.snapshot /code/addons/docs/template/stories/docspage/override.stories.ts /docs/_snippets/my-component-story-mandatory-export.md

#### About Greptile This response provides a starting point for your research, not a precise solution. Help us improve! Please leave a 👍 if this is helpful and 👎 if it is irrelevant. [Ask Greptile](https://app.greptile.com/chat/github/storybookjs/storybook/next) · [Edit Issue Bot Settings](https://app.greptile.com/apps/github)
mmorris8 commented 1 month ago

Ok, that wasn't entirely correct, but it pointed me in the correct direction to solve it.

The Component

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'cv-button',
  standalone: true,
  template: `
    <button (click)="onClick()">Click Me</button>
  `,
  imports: [],
})
export class ButtonComponent {
  private count = 0
  @Output() clickCount = new EventEmitter<number>

  onClick() {
    this.count++
    this.clickCount.emit(this.count)
  }
}

The Story

import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { ButtonComponent } from './button.component';
import { userEvent, within, expect, fn } from '@storybook/test';

const meta:Meta<ButtonComponent> = {
  title: 'Button Test',
  component: ButtonComponent,
  tags: ['autodocs'],
  args: {
    clickCount: fn()
  },
  decorators: [
    moduleMetadata({
      imports: []
    })
  ]
}

export default meta

type Story = StoryObj<ButtonComponent>

export const Primary: Story = {
  args: {},
  play: async ({canvasElement, args}) => {
    const element = within(canvasElement)   
    await userEvent.click(element.getByText('Click Me'))
    expect(args.clickCount).toHaveBeenCalledWith(1)
    await userEvent.click(element.getByText('Click Me'))
    expect(args.clickCount).toHaveBeenCalledWith(2)
  }
}

This example needs to be incorporated into the main documentation somewhere.

michael-lloyd-morris commented 1 month ago

I've worked on this further (this is my personal github account, the thread was started from my work account). The above details on how to write stories that test angular event emitters, and have went beyond this to finally figure out how to build Reactive Form components and test them fully in Storybook. This weekend I would like to take that knowledge and put it up into a tutorial, probably using a github to demo the code.

Integrating Reactive Forms with Storybook tests has been a sore point, and I'd like to do what I can to make it less painful.

michael-lloyd-morris commented 1 month ago

Got the first three chapters in place - https://github.com/michael-lloyd-morris/storybook-angular-reactive-forms/tree/main

michael-lloyd-morris commented 1 month ago

Ok, now I have the first three chapters :\ I goofed yesterday and was counting branches - the main branch being the table of contents.