@storybook/angular 8.0.0-alpha.2 → 8.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,13 @@ type StoryRenderInfo = {
4
4
  storyFnAngular: StoryFnAngularReturnType;
5
5
  moduleMetadataSnapshot: string;
6
6
  };
7
+ /**
8
+ * Attribute name for the story UID that may be written to the targetDOMNode.
9
+ *
10
+ * If a target DOM node has a story UID attribute, it will be used as part of
11
+ * the selector for the Angular component.
12
+ */
13
+ export declare const STORY_UID_ATTRIBUTE = "data-sb-story-uid";
7
14
  export declare abstract class AbstractRenderer {
8
15
  /**
9
16
  * Wait and destroy the platform
@@ -47,6 +54,9 @@ export declare abstract class AbstractRenderer {
47
54
  * @memberof AbstractRenderer
48
55
  */
49
56
  protected generateTargetSelectorFromStoryId(id: string): string;
57
+ /**
58
+ * Adds DOM element that angular will use as bootstrap component.
59
+ */
50
60
  protected initAngularRootElement(targetDOMNode: HTMLElement, targetSelector: string): void;
51
61
  private fullRendererRequired;
52
62
  }
@@ -24,7 +24,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  var _a;
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.AbstractRenderer = void 0;
27
+ exports.AbstractRenderer = exports.STORY_UID_ATTRIBUTE = void 0;
28
28
  const core_1 = require("@angular/core");
29
29
  const platform_browser_1 = require("@angular/platform-browser");
30
30
  const rxjs_1 = require("rxjs");
@@ -34,6 +34,13 @@ const StorybookProvider_1 = require("./StorybookProvider");
34
34
  const StorybookWrapperComponent_1 = require("./StorybookWrapperComponent");
35
35
  const PropertyExtractor_1 = require("./utils/PropertyExtractor");
36
36
  const applicationRefs = new Map();
37
+ /**
38
+ * Attribute name for the story UID that may be written to the targetDOMNode.
39
+ *
40
+ * If a target DOM node has a story UID attribute, it will be used as part of
41
+ * the selector for the Angular component.
42
+ */
43
+ exports.STORY_UID_ATTRIBUTE = 'data-sb-story-uid';
37
44
  class AbstractRenderer {
38
45
  /**
39
46
  * Wait and destroy the platform
@@ -90,10 +97,16 @@ class AbstractRenderer {
90
97
  this.storyProps$ = newStoryProps$;
91
98
  this.initAngularRootElement(targetDOMNode, targetSelector);
92
99
  const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor(storyFnAngular.moduleMetadata, component);
100
+ const storyUid = targetDOMNode.getAttribute(exports.STORY_UID_ATTRIBUTE);
101
+ const componentSelector = storyUid !== null ? `${targetSelector}[${storyUid}]` : targetSelector;
102
+ if (storyUid !== null) {
103
+ const element = targetDOMNode.querySelector(targetSelector);
104
+ element.toggleAttribute(storyUid, true);
105
+ }
93
106
  const application = (0, StorybookModule_1.getApplication)({
94
107
  storyFnAngular,
95
108
  component,
96
- targetSelector,
109
+ targetSelector: componentSelector,
97
110
  analyzedMetadata,
98
111
  });
99
112
  const applicationRef = await (0, platform_browser_1.bootstrapApplication)(application, {
@@ -124,8 +137,10 @@ class AbstractRenderer {
124
137
  const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(id);
125
138
  return storyIdIsInvalidHtmlTagName ? `sb-${id.replace(invalidHtmlTag, '')}-component` : id;
126
139
  }
140
+ /**
141
+ * Adds DOM element that angular will use as bootstrap component.
142
+ */
127
143
  initAngularRootElement(targetDOMNode, targetSelector) {
128
- // Adds DOM element that angular will use as bootstrap component
129
144
  // eslint-disable-next-line no-param-reassign
130
145
  targetDOMNode.innerHTML = '';
131
146
  targetDOMNode.appendChild(document.createElement(targetSelector));
@@ -10,4 +10,5 @@ export declare class DocsRenderer extends AbstractRenderer {
10
10
  }): Promise<void>;
11
11
  beforeFullRender(domNode?: HTMLElement): Promise<void>;
12
12
  afterFullRender(): Promise<void>;
13
+ protected initAngularRootElement(targetDOMNode: HTMLElement, targetSelector: string): void;
13
14
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DocsRenderer = void 0;
4
4
  const preview_api_1 = require("@storybook/preview-api");
5
5
  const core_events_1 = require("@storybook/core-events");
6
+ const StoryUID_1 = require("./utils/StoryUID");
6
7
  const AbstractRenderer_1 = require("./AbstractRenderer");
7
8
  class DocsRenderer extends AbstractRenderer_1.AbstractRenderer {
8
9
  async render(options) {
@@ -36,5 +37,9 @@ class DocsRenderer extends AbstractRenderer_1.AbstractRenderer {
36
37
  async afterFullRender() {
37
38
  await AbstractRenderer_1.AbstractRenderer.resetCompiledComponents();
38
39
  }
40
+ initAngularRootElement(targetDOMNode, targetSelector) {
41
+ super.initAngularRootElement(targetDOMNode, targetSelector);
42
+ targetDOMNode.setAttribute(AbstractRenderer_1.STORY_UID_ATTRIBUTE, (0, StoryUID_1.getNextStoryUID)(targetDOMNode.id));
43
+ }
39
44
  }
40
45
  exports.DocsRenderer = DocsRenderer;
@@ -17,10 +17,12 @@ describe('RendererFactory', () => {
17
17
  let rendererFactory;
18
18
  let rootTargetDOMNode;
19
19
  let rootDocstargetDOMNode;
20
+ let storyInDocstargetDOMNode;
20
21
  beforeEach(async () => {
21
22
  rendererFactory = new RendererFactory_1.RendererFactory();
22
23
  document.body.innerHTML =
23
- '<div id="storybook-root"></div><div id="root-docs"><div id="story-in-docs"></div></div>';
24
+ '<div id="storybook-root"></div><div id="root-docs"><div id="story-in-docs"></div></div>' +
25
+ '<div id="storybook-docs"></div>';
24
26
  rootTargetDOMNode = global.document.getElementById('storybook-root');
25
27
  rootDocstargetDOMNode = global.document.getElementById('root-docs');
26
28
  platform_browser_dynamic_1.platformBrowserDynamic.mockImplementation(testing_1.platformBrowserDynamicTesting);
@@ -160,5 +162,39 @@ describe('RendererFactory', () => {
160
162
  const render = await rendererFactory.getRendererInstance(rootDocstargetDOMNode);
161
163
  expect(render).toBeInstanceOf(DocsRenderer_1.DocsRenderer);
162
164
  });
165
+ describe('when multiple story for the same component', () => {
166
+ it('should render both stories', async () => {
167
+ let FooComponent = class FooComponent {
168
+ };
169
+ FooComponent = __decorate([
170
+ (0, core_1.Component)({ selector: 'foo', template: '🦊' })
171
+ ], FooComponent);
172
+ const render = await rendererFactory.getRendererInstance(global.document.getElementById('storybook-docs'));
173
+ const targetDOMNode1 = global.document.createElement('div');
174
+ targetDOMNode1.id = 'story-1';
175
+ global.document.getElementById('storybook-docs').appendChild(targetDOMNode1);
176
+ await render?.render({
177
+ storyFnAngular: {
178
+ props: {},
179
+ },
180
+ forced: false,
181
+ component: FooComponent,
182
+ targetDOMNode: targetDOMNode1,
183
+ });
184
+ const targetDOMNode2 = global.document.createElement('div');
185
+ targetDOMNode2.id = 'story-1';
186
+ global.document.getElementById('storybook-docs').appendChild(targetDOMNode2);
187
+ await render?.render({
188
+ storyFnAngular: {
189
+ props: {},
190
+ },
191
+ forced: false,
192
+ component: FooComponent,
193
+ targetDOMNode: targetDOMNode2,
194
+ });
195
+ expect(global.document.querySelectorAll('#story-1 > story-1')[0].innerHTML).toBe('<foo>🦊</foo><!--container-->');
196
+ expect(global.document.querySelectorAll('#story-1 > story-1')[1].innerHTML).toBe('<foo>🦊</foo><!--container-->');
197
+ });
198
+ });
163
199
  });
164
200
  });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Increments the count for a storyId and returns the next UID.
3
+ *
4
+ * When a story is bootstrapped, the storyId is used as the element tag. That
5
+ * becomes an issue when a story is rendered multiple times in the same docs
6
+ * page. This function returns a UID that is appended to the storyId to make
7
+ * it unique.
8
+ *
9
+ * @param storyId id of a story
10
+ * @returns uid of a story
11
+ */
12
+ export declare const getNextStoryUID: (storyId: string) => string;
13
+ /**
14
+ * Clears the storyId counts.
15
+ *
16
+ * Can be useful for testing, where you need predictable increments, without
17
+ * reloading the global state.
18
+ *
19
+ * If onlyStoryId is provided, only that storyId is cleared.
20
+ *
21
+ * @param onlyStoryId id of a story
22
+ */
23
+ export declare const clearStoryUIDs: (onlyStoryId?: string) => void;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearStoryUIDs = exports.getNextStoryUID = void 0;
4
+ /**
5
+ * Count of stories for each storyId.
6
+ */
7
+ const storyCounts = new Map();
8
+ /**
9
+ * Increments the count for a storyId and returns the next UID.
10
+ *
11
+ * When a story is bootstrapped, the storyId is used as the element tag. That
12
+ * becomes an issue when a story is rendered multiple times in the same docs
13
+ * page. This function returns a UID that is appended to the storyId to make
14
+ * it unique.
15
+ *
16
+ * @param storyId id of a story
17
+ * @returns uid of a story
18
+ */
19
+ const getNextStoryUID = (storyId) => {
20
+ if (!storyCounts.has(storyId)) {
21
+ storyCounts.set(storyId, -1);
22
+ }
23
+ const count = storyCounts.get(storyId) + 1;
24
+ storyCounts.set(storyId, count);
25
+ return `${storyId}-${count}`;
26
+ };
27
+ exports.getNextStoryUID = getNextStoryUID;
28
+ /**
29
+ * Clears the storyId counts.
30
+ *
31
+ * Can be useful for testing, where you need predictable increments, without
32
+ * reloading the global state.
33
+ *
34
+ * If onlyStoryId is provided, only that storyId is cleared.
35
+ *
36
+ * @param onlyStoryId id of a story
37
+ */
38
+ const clearStoryUIDs = (onlyStoryId) => {
39
+ if (onlyStoryId !== undefined && onlyStoryId !== null) {
40
+ storyCounts.delete(onlyStoryId);
41
+ }
42
+ else {
43
+ storyCounts.clear();
44
+ }
45
+ };
46
+ exports.clearStoryUIDs = clearStoryUIDs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/angular",
3
- "version": "8.0.0-alpha.2",
3
+ "version": "8.0.0-alpha.4",
4
4
  "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
5
5
  "keywords": [
6
6
  "storybook",
@@ -37,19 +37,19 @@
37
37
  "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/tsc.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@storybook/builder-webpack5": "8.0.0-alpha.2",
41
- "@storybook/cli": "8.0.0-alpha.2",
42
- "@storybook/client-logger": "8.0.0-alpha.2",
43
- "@storybook/core-common": "8.0.0-alpha.2",
44
- "@storybook/core-events": "8.0.0-alpha.2",
45
- "@storybook/core-server": "8.0.0-alpha.2",
46
- "@storybook/core-webpack": "8.0.0-alpha.2",
47
- "@storybook/docs-tools": "8.0.0-alpha.2",
40
+ "@storybook/builder-webpack5": "8.0.0-alpha.4",
41
+ "@storybook/cli": "8.0.0-alpha.4",
42
+ "@storybook/client-logger": "8.0.0-alpha.4",
43
+ "@storybook/core-common": "8.0.0-alpha.4",
44
+ "@storybook/core-events": "8.0.0-alpha.4",
45
+ "@storybook/core-server": "8.0.0-alpha.4",
46
+ "@storybook/core-webpack": "8.0.0-alpha.4",
47
+ "@storybook/docs-tools": "8.0.0-alpha.4",
48
48
  "@storybook/global": "^5.0.0",
49
- "@storybook/node-logger": "8.0.0-alpha.2",
50
- "@storybook/preview-api": "8.0.0-alpha.2",
51
- "@storybook/telemetry": "8.0.0-alpha.2",
52
- "@storybook/types": "8.0.0-alpha.2",
49
+ "@storybook/node-logger": "8.0.0-alpha.4",
50
+ "@storybook/preview-api": "8.0.0-alpha.4",
51
+ "@storybook/telemetry": "8.0.0-alpha.4",
52
+ "@storybook/types": "8.0.0-alpha.4",
53
53
  "@types/node": "^18.0.0",
54
54
  "@types/react": "^18.0.37",
55
55
  "@types/react-dom": "^18.0.11",
@@ -111,7 +111,7 @@
111
111
  }
112
112
  },
113
113
  "engines": {
114
- "node": ">=16.0.0"
114
+ "node": ">=18.0.0"
115
115
  },
116
116
  "publishConfig": {
117
117
  "access": "public"
@@ -3,6 +3,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
3
3
 
4
4
  @Component({
5
5
  selector: 'storybook-button',
6
+ standalone: true,
6
7
  imports: [CommonModule],
7
8
  template: ` <button
8
9
  type="button"
@@ -14,7 +15,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
14
15
  </button>`,
15
16
  styleUrls: ['./button.css'],
16
17
  })
17
- export default class ButtonComponent {
18
+ export class ButtonComponent {
18
19
  /**
19
20
  * Is this the principal call to action on the page?
20
21
  */
@@ -1,12 +1,13 @@
1
1
  import type { Meta, StoryObj } from '@storybook/angular';
2
- import Button from './button.component';
2
+
3
+ import { ButtonComponent } from './button.component';
3
4
 
4
5
  // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
5
- const meta: Meta<Button> = {
6
+ const meta: Meta<ButtonComponent> = {
6
7
  title: 'Example/Button',
7
- component: Button,
8
+ component: ButtonComponent,
8
9
  tags: ['autodocs'],
9
- render: (args: Button) => ({
10
+ render: (args: ButtonComponent) => ({
10
11
  props: {
11
12
  backgroundColor: null,
12
13
  ...args,
@@ -20,7 +21,7 @@ const meta: Meta<Button> = {
20
21
  };
21
22
 
22
23
  export default meta;
23
- type Story = StoryObj<Button>;
24
+ type Story = StoryObj<ButtonComponent>;
24
25
 
25
26
  // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
26
27
  export const Primary: Story = {
@@ -1,8 +1,13 @@
1
1
  import { Component, Input, Output, EventEmitter } from '@angular/core';
2
- import type { User } from './User';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ import { ButtonComponent } from './button.component';
5
+ import type { User } from './user';
3
6
 
4
7
  @Component({
5
8
  selector: 'storybook-header',
9
+ standalone: true,
10
+ imports: [CommonModule, ButtonComponent],
6
11
  template: `<header>
7
12
  <div class="storybook-header">
8
13
  <div>
@@ -47,9 +52,8 @@ import type { User } from './User';
47
52
  ></storybook-button>
48
53
  <storybook-button
49
54
  *ngIf="!user"
50
- primary
51
55
  size="small"
52
- primary="true"
56
+ [primary]="true"
53
57
  class="margin-left"
54
58
  (onClick)="onCreateAccount.emit($event)"
55
59
  label="Sign up"
@@ -60,7 +64,7 @@ import type { User } from './User';
60
64
  </header>`,
61
65
  styleUrls: ['./header.css'],
62
66
  })
63
- export default class HeaderComponent {
67
+ export class HeaderComponent {
64
68
  @Input()
65
69
  user: User | null = null;
66
70
 
@@ -1,22 +1,12 @@
1
- import { moduleMetadata } from '@storybook/angular';
2
1
  import type { Meta, StoryObj } from '@storybook/angular';
3
- import { CommonModule } from '@angular/common';
4
2
 
5
- import Button from './button.component';
6
- import Header from './header.component';
3
+ import { HeaderComponent } from './header.component';
7
4
 
8
- const meta: Meta<Header> = {
5
+ const meta: Meta<HeaderComponent> = {
9
6
  title: 'Example/Header',
10
- component: Header,
7
+ component: HeaderComponent,
11
8
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
12
9
  tags: ['autodocs'],
13
- render: (args) => ({ props: args }),
14
- decorators: [
15
- moduleMetadata({
16
- declarations: [Button],
17
- imports: [CommonModule],
18
- }),
19
- ],
20
10
  parameters: {
21
11
  // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
22
12
  layout: 'fullscreen',
@@ -24,7 +14,7 @@ const meta: Meta<Header> = {
24
14
  };
25
15
 
26
16
  export default meta;
27
- type Story = StoryObj<Header>;
17
+ type Story = StoryObj<HeaderComponent>;
28
18
 
29
19
  export const LoggedIn: Story = {
30
20
  args: {
@@ -1,8 +1,13 @@
1
1
  import { Component } from '@angular/core';
2
- import type { User } from './User';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ import { HeaderComponent } from './header.component';
5
+ import type { User } from './user';
3
6
 
4
7
  @Component({
5
8
  selector: 'storybook-page',
9
+ standalone: true,
10
+ imports: [CommonModule, HeaderComponent],
6
11
  template: `<article>
7
12
  <storybook-header
8
13
  [user]="user"
@@ -60,7 +65,7 @@ import type { User } from './User';
60
65
  </article>`,
61
66
  styleUrls: ['./page.css'],
62
67
  })
63
- export default class PageComponent {
68
+ export class PageComponent {
64
69
  user: User | null = null;
65
70
 
66
71
  doLogout() {
@@ -1,41 +1,24 @@
1
1
  import type { Meta, StoryObj } from '@storybook/angular';
2
- import { moduleMetadata } from '@storybook/angular';
3
- import { CommonModule } from '@angular/common';
4
2
  import { within, userEvent, expect } from '@storybook/test';
5
3
 
6
- import Button from './button.component';
7
- import Header from './header.component';
8
- import Page from './page.component';
4
+ import { PageComponent } from './page.component';
9
5
 
10
- const meta: Meta<Page> = {
6
+ const meta: Meta<PageComponent> = {
11
7
  title: 'Example/Page',
12
- component: Page,
8
+ component: PageComponent,
13
9
  parameters: {
14
10
  // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
15
11
  layout: 'fullscreen',
16
12
  },
17
- decorators: [
18
- moduleMetadata({
19
- declarations: [Button, Header],
20
- imports: [CommonModule],
21
- }),
22
- ],
23
13
  };
24
14
 
25
15
  export default meta;
26
- type Story = StoryObj<Page>;
16
+ type Story = StoryObj<PageComponent>;
27
17
 
28
- export const LoggedOut: Story = {
29
- render: (args: Page) => ({
30
- props: args,
31
- }),
32
- };
18
+ export const LoggedOut: Story = {};
33
19
 
34
20
  // More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing
35
21
  export const LoggedIn: Story = {
36
- render: (args: Page) => ({
37
- props: args,
38
- }),
39
22
  play: async ({ canvasElement }) => {
40
23
  const canvas = within(canvasElement);
41
24
  const loginButton = canvas.getByRole('button', { name: /Log in/i });
@@ -0,0 +1,3 @@
1
+ export interface User {
2
+ name: string;
3
+ }
@@ -1,2 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
2
- export interface User {}