Skip to content

fix(storybook-angular): add missing applyDecorators to render annotaions#2086

Merged
brandonroberts merged 1 commit into
analogjs:betafrom
Bayathy:fix-sb-angular-rendering
Feb 24, 2026
Merged

fix(storybook-angular): add missing applyDecorators to render annotaions#2086
brandonroberts merged 1 commit into
analogjs:betafrom
Bayathy:fix-sb-angular-rendering

Conversation

@Bayathy

@Bayathy Bayathy commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

Closes #2074

What is the new behavior?

@storybook/angular's componentWrapperDecorator() depends on storyFn().template internally.

This storyFn().template is resolved by prepareMain(), which is called inside Storybook's decorateStory(). This function is exported as applyDecorators within Storybook.

However, before this fix, decorateStory() was not being called at runtime. Instead, Storybook's generic renderer
defaultDecorateStory was being used. Since defaultDecorateStory does not call prepareMain(), storyFn().template remained unresolved.

This happens because prepareStory(), which
is called during portable story rendering in the Vitest environment, contains the following:

  // Currently it is only possible to set these globally
  const { applyDecorators = defaultDecorateStory, runStep } = projectAnnotations;

When applyDecorators is not defined in projectAnnotations, it falls back to defaultDecorateStory.

With this fix, applyDecorators is now included in renderAnnotations and passed through to prepareStory(). This causes Angular's decorateStory() to be used, which calls prepareMain() and properly resolves storyFn().template.

As a result, storyFn().template inside componentWrapperDecorator() is no longer undefined.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

This is my first PR to an OSS project. English is not my first language, so I wrote this description in Japanese first and then had it translated. I apologize if anything is unclear or hard to read.

@netlify

netlify Bot commented Feb 24, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit 5140dda
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/699dec7584545a00085d2756
😎 Deploy Preview https://deploy-preview-2086--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Feb 24, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit 5140dda
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/699dec7596be8a000868857c
😎 Deploy Preview https://deploy-preview-2086--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Feb 24, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit 5140dda
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/699dec75c0e763000849e0fb
😎 Deploy Preview https://deploy-preview-2086--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Bayathy

Bayathy commented Feb 24, 2026

Copy link
Copy Markdown
Contributor Author

The reproduction environment is here.

https://github.com/Bayathy/analog-storybook

When running npm run test-storybook in this repository, the following result is obtained:

 FAIL   storybook (chromium)  src/stories/page.stories.ts > Logged In
TestingLibraryElementError:
Click to debug the error directly in Storybook: http://localhost:6006/?path=/story/example-page--logged-in&addonPanel=storybook/interactions/panel

Unable to find an accessible element with the role "button" and name `/Log in/i`

There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole

Ignored nodes: comments, script, style
<div
  data-sb-story-uid="example-page--logged-in-0"
  id="example-page--logged-in"
>
  <example-page--logged-in
    example-page--logged-in-0=""
    ng-version="21.1.5"
  >
    <div
      style="padding: 1rem;"
    >
      undefined
    </div>
  </example-page--logged-in>
</div>
 ❯ getByRole src/stories/page.stories.ts:24:31
     22|   play: async ({ canvasElement }) => {
     23|     const canvas = within(canvasElement);
     24|     const loginButton = canvas.getByRole('button', { name: /Log in/i });
       |                               ^
     25|     await expect(loginButton).toBeInTheDocument();
     26|     await userEvent.click(loginButton);
 ❯ node_modules/.cache/storybook/10.2.11/95209e453a51f1cc97c9c8cfcc07cc086d2a83ebdbfe04ef274ba9013d3e81d4/sb-vitest/deps/@storybook_addon-vitest_internal_test-utils.js?v=8217aa13:126:184

The story rendered inside componentWrapperDecorator is becoming undefined.

@brandonroberts brandonroberts merged commit 9a14163 into analogjs:beta Feb 24, 2026
18 checks passed
@brandonroberts

Copy link
Copy Markdown
Member

Thanks @Bayathy!

@prclm

prclm commented Feb 25, 2026

Copy link
Copy Markdown

@Bayathy @brandonroberts I installed 2.3.0-beta.17 and it breaks my storybook interaction test in vitest where i use output-binding. (The test works inside storybook ui and with 2.3.0-beta.16).

It looks to me that the render in vitest does not add the component bindings correctly.

import { Meta, StoryObj } from '@storybook/angular';
import { expect, fn } from 'storybook/test';

import { DemoComponent } from './demo.component';

const meta = {
  component: DemoComponent,
  args: { btnClicked: fn() },
} satisfies Meta<DemoComponent>;

export default meta;

type Story = StoryObj<DemoComponent>;

export const Default: Story = {
  play: async ({ canvas, userEvent, args }) => {
    await userEvent.click(canvas.getByTestId('demo-btn'));
    // the mock fn() does not get called, it is not bound correctly
    void expect(args.btnClicked).toBeCalled();
  },
};

@prclm

prclm commented Feb 25, 2026

Copy link
Copy Markdown

here is what i get when i log the story in a decorator:

componentWrapperDecorator((story) => {
  console.log({ story });
  return `
    <div id="wrapper">
      ${story}
    </div>`;
});

2.3.0-beta.17

stdout | src/lib/demo.stories.ts > Default
{
  "story": "<app-demo    />",
}

2.3.0-beta.16

stdout | src/lib/demo.stories.ts > Default
{
  "story": "<app-demo (btnClicked)="btnClicked($event)"  />",
}

@prclm

prclm commented Feb 25, 2026

Copy link
Copy Markdown

at the moment i work around the decorator problem with this helper to test, if the story runs inside vitest. Then i don't apply my decorators at all...

/**
 * Helper method to test if in vitest environment.
 * @returns If is vitest environment.
 */
export function isVitestBrowser(): boolean {
  return window.__vitest_browser__ ?? false;
}

But when this patch will land in the final release, i would not know how to get my tests fixed.
For me it is more imporant to have component bindings of my components work in my tests, then having the decorators rendered.

@brandonroberts I would prefer to revert this change, if it can not be fixed...

@brandonroberts

Copy link
Copy Markdown
Member

Yes, we will revert this and revisit other options. We at least have a reproduction now

@Bayathy

Bayathy commented Feb 26, 2026

Copy link
Copy Markdown
Contributor Author

@prclm
Please add the following code to the story file that was previously shared as a reference.

This issue might be related:
storybookjs/storybook#20309

import type { Meta, StoryObj } from '@storybook/angular';
import { expect, fn } from 'storybook/test';

import { DemoComponent } from './demo.component';

const meta: Meta<DemoComponent> = {
  title: 'Example/Demo',
  component: DemoComponent,
+ argTypes: { btnClicked: { action: 'btnClicked' }, },
  args: { btnClicked: fn() },
};

export default meta;
type Story = StoryObj<DemoComponent>;

export const Default: Story = {
  play: async ({ canvas, userEvent, args }) => {
    await userEvent.click(canvas.getByTestId('demo-btn'));
    void expect(args.btnClicked).toBeCalled();
  },
};

With this modification, the story output in my environment looked like this:

<app-demo (btnClicked)="btnClicked($event)"></app-demo>

@prclm

prclm commented Mar 24, 2026

Copy link
Copy Markdown

@prclm Please add the following code to the story file that was previously shared as a reference.

This issue might be related: storybookjs/storybook#20309

import type { Meta, StoryObj } from '@storybook/angular';
import { expect, fn } from 'storybook/test';

import { DemoComponent } from './demo.component';

const meta: Meta<DemoComponent> = {
  title: 'Example/Demo',
  component: DemoComponent,
+ argTypes: { btnClicked: { action: 'btnClicked' }, },
  args: { btnClicked: fn() },
};

export default meta;
type Story = StoryObj<DemoComponent>;

export const Default: Story = {
  play: async ({ canvas, userEvent, args }) => {
    await userEvent.click(canvas.getByTestId('demo-btn'));
    void expect(args.btnClicked).toBeCalled();
  },
};

With this modification, the story output in my environment looked like this:

<app-demo (btnClicked)="btnClicked($event)"></app-demo>

Thanks @Bayathy, this indeed fixes the tests. Do you think it is possible to inject a console.log somehow to give the developer a hint what is going on?

@brandonroberts is it possible to bring this MR changes back in the next version and maybe make a "troubleshooting" note somewhere if people run in this bug, that they need to add the actions as "argTypes"?

@brandonroberts

Copy link
Copy Markdown
Member

@prclm Sure, we can add it to the bottom of the guide

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[storybook] componentWrapperDecorator broken in vitest

3 participants