@storybook/angular 7.0.0-rc.9 → 7.0.1

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.
Files changed (29) hide show
  1. package/README.md +85 -0
  2. package/dist/client/angular-beta/AbstractRenderer.js +14 -12
  3. package/dist/client/angular-beta/StorybookModule.d.ts +3 -1
  4. package/dist/client/angular-beta/StorybookModule.js +10 -2
  5. package/dist/client/angular-beta/StorybookModule.test.js +17 -0
  6. package/dist/client/angular-beta/StorybookWrapperComponent.d.ts +10 -4
  7. package/dist/client/angular-beta/StorybookWrapperComponent.js +2 -5
  8. package/dist/client/angular-beta/utils/PropertyExtractor.d.ts +4 -4
  9. package/dist/client/angular-beta/utils/PropertyExtractor.js +46 -20
  10. package/dist/client/angular-beta/utils/PropertyExtractor.test.js +14 -14
  11. package/dist/client/decorateStory.test.js +0 -31
  12. package/dist/client/decorators.d.ts +9 -0
  13. package/dist/client/decorators.js +25 -1
  14. package/dist/client/decorators.test.js +44 -4
  15. package/dist/client/index.d.ts +1 -1
  16. package/dist/client/index.js +2 -1
  17. package/dist/client/types.d.ts +15 -6
  18. package/dist/server/framework-preset-angular-docs.js +1 -5
  19. package/package.json +15 -15
  20. package/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts +2 -4
  21. package/template/stories/core/applicationConfig/with-browser-animations.stories.ts +40 -0
  22. package/template/stories/core/applicationConfig/with-noop-browser-animations.stories.ts +35 -0
  23. package/template/stories/others/ngx-translate/README.stories.mdx +22 -15
  24. package/dist/client/angular/app.component.d.ts +0 -38
  25. package/dist/client/angular/app.component.js +0 -141
  26. package/dist/client/angular/app.token.d.ts +0 -3
  27. package/dist/client/angular/app.token.js +0 -5
  28. package/template/stories/core/moduleMetadata/with-browser-animations.stories.ts +0 -30
  29. package/template/stories/core/moduleMetadata/with-noop-browser-animations.stories.ts +0 -27
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  - [Setup Compodoc](#setup-compodoc)
8
8
  - [Automatic setup](#automatic-setup)
9
9
  - [Manual setup](#manual-setup)
10
+ - [moduleMetadata decorator](#modulemetadata-decorator)
11
+ - [applicationConfig decorator](#applicationconfig-decorator)
10
12
  - [FAQ](#faq)
11
13
  - [How do I migrate to a Angular Storybook builder?](#how-do-i-migrate-to-a-angular-storybook-builder)
12
14
  - [Do you have only one Angular project in your workspace?](#do-you-have-only-one-angular-project-in-your-workspace)
@@ -165,6 +167,89 @@ const preview: Preview = {
165
167
  export default preview;
166
168
  ```
167
169
 
170
+ ## moduleMetadata decorator
171
+
172
+ If your component has dependencies on other Angular directives and modules, these can be supplied using the moduleMetadata decorator either for all stories or for individual stories.
173
+
174
+ ```js
175
+ import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
176
+ import { SomeComponent } from './some.component';
177
+
178
+ export default {
179
+ component: SomeComponent,
180
+ decorators: [
181
+ // Apply metadata to all stories
182
+ moduleMetadata({
183
+ // import necessary ngModules or standalone components
184
+ imports: [...],
185
+ // declare components that are used in the template
186
+ declarations: [...],
187
+ // List of providers that should be available to the root component and all its children.
188
+ providers: [...],
189
+ }),
190
+ ],
191
+ } as Meta;
192
+
193
+ const Template = (): StoryFn => (args) => ({
194
+ props: args,
195
+ });
196
+
197
+ export const Base = Template();
198
+
199
+ export const WithCustomProvider = Template();
200
+ WithCustomProvider.decorators = [
201
+ // Apply metadata to a specific story
202
+ moduleMetadata({
203
+ imports: [...],
204
+ declarations: [...],
205
+ providers: [...]
206
+ }),
207
+ ];
208
+ ```
209
+
210
+ ## applicationConfig decorator
211
+
212
+ If your component relies on application-wide providers, like the ones defined by BrowserAnimationsModule or any other modules which use the forRoot pattern to provide a ModuleWithProviders, you can use the applicationConfig decorator on the meta default export to provide them to the [bootstrapApplication function](https://angular.io/guide/standalone-components#configuring-dependency-injection), which we use to bootstrap the component in Storybook.
213
+
214
+ ```js
215
+
216
+ import { StoryObj, Meta, applicationConfig } from '@storybook/angular';
217
+ import { BrowserAnimationsModule, provideAnimations } from '@angular/platform-browser/animations';
218
+ import { importProvidersFrom } from '@angular/core';
219
+ import { ChipsModule } from './angular-src/chips.module';
220
+
221
+ const meta: Meta = {
222
+ component: ChipsGroupComponent,
223
+ decorators: [
224
+ // Apply application config to all stories
225
+ applicationConfig({
226
+ // List of providers and environment providers that should be available to the root component and all its children.
227
+ providers: [
228
+ ...
229
+ // Import application-wide providers from a module
230
+ importProvidersFrom(BrowserAnimationsModule)
231
+ // Or use provide-style functions if available instead, e.g.
232
+ provideAnimations()
233
+ ],
234
+ }),
235
+ ],
236
+ };
237
+
238
+ export default meta;
239
+
240
+ type Story = StoryObj<typeof ChipsGroupComponent>;
241
+
242
+ export const WithCustomApplicationProvider: Story = {
243
+ render: () => ({
244
+ // Apply application config to a specific story
245
+ applicationConfig: {
246
+ // The providers will be merged with the ones defined in the applicationConfig decorators providers array of the global meta object
247
+ providers: [...]
248
+ }
249
+ })
250
+ }
251
+ ```
252
+
168
253
  ## FAQ
169
254
 
170
255
  ### How do I migrate to a Angular Storybook builder?
@@ -90,18 +90,20 @@ class AbstractRenderer {
90
90
  this.storyProps$ = newStoryProps$;
91
91
  this.initAngularRootElement(targetDOMNode, targetSelector);
92
92
  const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor(storyFnAngular.moduleMetadata, component);
93
- const providers = [
94
- // Providers for BrowserAnimations & NoopAnimationsModule
95
- analyzedMetadata.singletons,
96
- (0, core_1.importProvidersFrom)(...analyzedMetadata.imports.filter((imported) => {
97
- const { isStandalone } = PropertyExtractor_1.PropertyExtractor.analyzeDecorators(imported);
98
- return !isStandalone;
99
- })),
100
- analyzedMetadata.providers,
101
- (0, StorybookProvider_1.storyPropsProvider)(newStoryProps$),
102
- ].filter(Boolean);
103
- const application = (0, StorybookModule_1.getApplication)({ storyFnAngular, component, targetSelector });
104
- const applicationRef = await (0, platform_browser_1.bootstrapApplication)(application, { providers });
93
+ const application = (0, StorybookModule_1.getApplication)({
94
+ storyFnAngular,
95
+ component,
96
+ targetSelector,
97
+ analyzedMetadata,
98
+ });
99
+ const applicationRef = await (0, platform_browser_1.bootstrapApplication)(application, {
100
+ ...storyFnAngular.applicationConfig,
101
+ providers: [
102
+ (0, StorybookProvider_1.storyPropsProvider)(newStoryProps$),
103
+ ...analyzedMetadata.applicationProviders,
104
+ ...(storyFnAngular.applicationConfig?.providers ?? []),
105
+ ],
106
+ });
105
107
  applicationRefs.add(applicationRef);
106
108
  await this.afterFullRender();
107
109
  }
@@ -1,6 +1,8 @@
1
1
  import { StoryFnAngularReturnType } from '../types';
2
- export declare const getApplication: ({ storyFnAngular, component, targetSelector, }: {
2
+ import { PropertyExtractor } from './utils/PropertyExtractor';
3
+ export declare const getApplication: ({ storyFnAngular, component, targetSelector, analyzedMetadata, }: {
3
4
  storyFnAngular: StoryFnAngularReturnType;
4
5
  component?: any;
5
6
  targetSelector: string;
7
+ analyzedMetadata: PropertyExtractor;
6
8
  }) => import("@angular/core").Type<any>;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getApplication = void 0;
4
4
  const StorybookWrapperComponent_1 = require("./StorybookWrapperComponent");
5
5
  const ComputesTemplateFromComponent_1 = require("./ComputesTemplateFromComponent");
6
- const getApplication = ({ storyFnAngular, component, targetSelector, }) => {
6
+ const getApplication = ({ storyFnAngular, component, targetSelector, analyzedMetadata, }) => {
7
7
  const { props, styles, moduleMetadata = {} } = storyFnAngular;
8
8
  let { template } = storyFnAngular;
9
9
  const hasTemplate = !hasNoTemplate(template);
@@ -13,7 +13,15 @@ const getApplication = ({ storyFnAngular, component, targetSelector, }) => {
13
13
  /**
14
14
  * Create a component that wraps generated template and gives it props
15
15
  */
16
- return (0, StorybookWrapperComponent_1.createStorybookWrapperComponent)(targetSelector, template, component, styles, moduleMetadata, props);
16
+ return (0, StorybookWrapperComponent_1.createStorybookWrapperComponent)({
17
+ moduleMetadata,
18
+ selector: targetSelector,
19
+ template,
20
+ storyComponent: component,
21
+ styles,
22
+ initialProps: props,
23
+ analyzedMetadata,
24
+ });
17
25
  };
18
26
  exports.getApplication = getApplication;
19
27
  function hasNoTemplate(template) {
@@ -14,6 +14,7 @@ const testing_1 = require("@angular/core/testing");
14
14
  const rxjs_1 = require("rxjs");
15
15
  const StorybookModule_1 = require("./StorybookModule");
16
16
  const StorybookProvider_1 = require("./StorybookProvider");
17
+ const PropertyExtractor_1 = require("./utils/PropertyExtractor");
17
18
  describe('StorybookModule', () => {
18
19
  describe('getStorybookModuleMetadata', () => {
19
20
  describe('with simple component', () => {
@@ -70,10 +71,12 @@ describe('StorybookModule', () => {
70
71
  localProperty: 'localProperty',
71
72
  localFunction: () => 'localFunction',
72
73
  };
74
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
73
75
  const application = (0, StorybookModule_1.getApplication)({
74
76
  storyFnAngular: { props },
75
77
  component: FooComponent,
76
78
  targetSelector: 'my-selector',
79
+ analyzedMetadata,
77
80
  });
78
81
  const { fixture } = await configureTestingModule({
79
82
  imports: [application],
@@ -96,10 +99,12 @@ describe('StorybookModule', () => {
96
99
  expectedOutputBindingValue = value;
97
100
  },
98
101
  };
102
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
99
103
  const application = (0, StorybookModule_1.getApplication)({
100
104
  storyFnAngular: { props },
101
105
  component: FooComponent,
102
106
  targetSelector: 'my-selector',
107
+ analyzedMetadata,
103
108
  });
104
109
  const { fixture } = await configureTestingModule({
105
110
  imports: [application],
@@ -117,10 +122,12 @@ describe('StorybookModule', () => {
117
122
  inputBindingPropertyName: '',
118
123
  };
119
124
  const storyProps$ = new rxjs_1.BehaviorSubject(initialProps);
125
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
120
126
  const application = (0, StorybookModule_1.getApplication)({
121
127
  storyFnAngular: { props: initialProps },
122
128
  component: FooComponent,
123
129
  targetSelector: 'my-selector',
130
+ analyzedMetadata,
124
131
  });
125
132
  const { fixture } = await configureTestingModule({
126
133
  imports: [application],
@@ -155,10 +162,12 @@ describe('StorybookModule', () => {
155
162
  },
156
163
  };
157
164
  const storyProps$ = new rxjs_1.BehaviorSubject(initialProps);
165
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
158
166
  const application = (0, StorybookModule_1.getApplication)({
159
167
  storyFnAngular: { props: initialProps },
160
168
  component: FooComponent,
161
169
  targetSelector: 'my-selector',
170
+ analyzedMetadata,
162
171
  });
163
172
  const { fixture } = await configureTestingModule({
164
173
  imports: [application],
@@ -188,6 +197,7 @@ describe('StorybookModule', () => {
188
197
  input: 'input',
189
198
  };
190
199
  const storyProps$ = new rxjs_1.BehaviorSubject(initialProps);
200
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
191
201
  const application = (0, StorybookModule_1.getApplication)({
192
202
  storyFnAngular: {
193
203
  props: initialProps,
@@ -195,6 +205,7 @@ describe('StorybookModule', () => {
195
205
  },
196
206
  component: FooComponent,
197
207
  targetSelector: 'my-selector',
208
+ analyzedMetadata,
198
209
  });
199
210
  const { fixture } = await configureTestingModule({
200
211
  imports: [application],
@@ -217,10 +228,12 @@ describe('StorybookModule', () => {
217
228
  setter: 'init',
218
229
  };
219
230
  const storyProps$ = new rxjs_1.BehaviorSubject(initialProps);
231
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
220
232
  const application = (0, StorybookModule_1.getApplication)({
221
233
  storyFnAngular: { props: initialProps },
222
234
  component: FooComponent,
223
235
  targetSelector: 'my-selector',
236
+ analyzedMetadata,
224
237
  });
225
238
  const { fixture } = await configureTestingModule({
226
239
  imports: [application],
@@ -246,6 +259,7 @@ describe('StorybookModule', () => {
246
259
  ], WithoutSelectorComponent);
247
260
  it('should display the component', async () => {
248
261
  const props = {};
262
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({ entryComponents: [WithoutSelectorComponent] }, WithoutSelectorComponent);
249
263
  const application = (0, StorybookModule_1.getApplication)({
250
264
  storyFnAngular: {
251
265
  props,
@@ -253,6 +267,7 @@ describe('StorybookModule', () => {
253
267
  },
254
268
  component: WithoutSelectorComponent,
255
269
  targetSelector: 'my-selector',
270
+ analyzedMetadata,
256
271
  });
257
272
  const { fixture } = await configureTestingModule({
258
273
  imports: [application],
@@ -271,10 +286,12 @@ describe('StorybookModule', () => {
271
286
  template: `Should not be displayed`,
272
287
  })
273
288
  ], FooComponent);
289
+ const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor({}, FooComponent);
274
290
  const application = (0, StorybookModule_1.getApplication)({
275
291
  storyFnAngular: { template: '' },
276
292
  component: FooComponent,
277
293
  targetSelector: 'my-selector',
294
+ analyzedMetadata,
278
295
  });
279
296
  const { fixture } = await configureTestingModule({
280
297
  imports: [application],
@@ -1,10 +1,16 @@
1
1
  import { Type } from '@angular/core';
2
2
  import { ICollection, NgModuleMetadata } from '../types';
3
+ import { PropertyExtractor } from './utils/PropertyExtractor';
3
4
  export declare const componentNgModules: Map<any, Type<any>>;
4
5
  /**
5
6
  * Wraps the story template into a component
6
- *
7
- * @param storyComponent
8
- * @param initialProps
9
7
  */
10
- export declare const createStorybookWrapperComponent: (selector: string, template: string, storyComponent: Type<unknown> | undefined, styles: string[], moduleMetadata: NgModuleMetadata, initialProps?: ICollection) => Type<any>;
8
+ export declare const createStorybookWrapperComponent: ({ selector, template, storyComponent, styles, moduleMetadata, initialProps, analyzedMetadata, }: {
9
+ selector: string;
10
+ template: string;
11
+ storyComponent: Type<unknown> | undefined;
12
+ styles: string[];
13
+ moduleMetadata: NgModuleMetadata;
14
+ initialProps?: ICollection;
15
+ analyzedMetadata?: PropertyExtractor;
16
+ }) => Type<any>;
@@ -32,15 +32,11 @@ const getNonInputsOutputsProps = (ngComponentInputsOutputs, props = {}) => {
32
32
  exports.componentNgModules = new Map();
33
33
  /**
34
34
  * Wraps the story template into a component
35
- *
36
- * @param storyComponent
37
- * @param initialProps
38
35
  */
39
- const createStorybookWrapperComponent = (selector, template, storyComponent, styles, moduleMetadata, initialProps) => {
36
+ const createStorybookWrapperComponent = ({ selector, template, storyComponent, styles, moduleMetadata, initialProps, analyzedMetadata, }) => {
40
37
  // In ivy, a '' selector is not allowed, therefore we need to just set it to anything if
41
38
  // storyComponent was not provided.
42
39
  const viewChildSelector = storyComponent ?? '__storybook-noop';
43
- const analyzedMetadata = new PropertyExtractor_1.PropertyExtractor(moduleMetadata, storyComponent);
44
40
  const { imports, declarations, providers } = analyzedMetadata;
45
41
  // Only create a new module if it doesn't already exist
46
42
  // This is to prevent the module from being recreated on every story change
@@ -60,6 +56,7 @@ const createStorybookWrapperComponent = (selector, template, storyComponent, sty
60
56
  exports.componentNgModules.set(storyComponent, StorybookComponentModule);
61
57
  ngModule = exports.componentNgModules.get(storyComponent);
62
58
  }
59
+ PropertyExtractor_1.PropertyExtractor.warnImportsModuleWithProviders(analyzedMetadata);
63
60
  let StorybookWrapperComponent = class StorybookWrapperComponent {
64
61
  constructor(storyProps$, changeDetectorRef) {
65
62
  this.storyProps$ = storyProps$;
@@ -1,4 +1,4 @@
1
- import { InjectionToken, NgModule, Provider, ɵReflectionCapabilities as ReflectionCapabilities } from '@angular/core';
1
+ import { importProvidersFrom, InjectionToken, NgModule, Provider, ɵReflectionCapabilities as ReflectionCapabilities } from '@angular/core';
2
2
  import { NgModuleMetadata } from '../../types';
3
3
  export declare const reflectionCapabilities: ReflectionCapabilities;
4
4
  export declare const REMOVED_MODULES: InjectionToken<unknown>;
@@ -9,21 +9,21 @@ export declare class PropertyExtractor implements NgModuleMetadata {
9
9
  declarations?: any[];
10
10
  imports?: any[];
11
11
  providers?: Provider[];
12
- singletons?: Provider[];
12
+ applicationProviders?: Array<Provider | ReturnType<typeof importProvidersFrom>>;
13
13
  constructor(metadata: NgModuleMetadata, component?: any);
14
+ static warnImportsModuleWithProviders(propertyExtractor: PropertyExtractor): void;
14
15
  private init;
15
16
  /**
16
17
  * Analyze NgModule Metadata
17
18
  *
18
19
  * - Removes Restricted Imports
19
20
  * - Extracts providers from ModuleWithProviders
20
- * - Flattens imports
21
21
  * - Returns a new NgModuleMetadata object
22
22
  *
23
23
  *
24
24
  */
25
25
  private analyzeMetadata;
26
- static analyzeRestricted: (ngModule: NgModule) => (boolean | Provider[])[];
26
+ static analyzeRestricted: (ngModule: NgModule) => [boolean] | [boolean, Provider];
27
27
  static analyzeDecorators: (component: any) => {
28
28
  isDeclarable: boolean;
29
29
  isStandalone: boolean;
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  var _a;
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  exports.PropertyExtractor = exports.uniqueArray = exports.REMOVED_MODULES = exports.reflectionCapabilities = void 0;
8
+ /* eslint-disable no-console */
5
9
  const common_1 = require("@angular/common");
6
10
  const core_1 = require("@angular/core");
7
11
  const platform_browser_1 = require("@angular/platform-browser");
8
12
  const animations_1 = require("@angular/platform-browser/animations");
13
+ const ts_dedent_1 = __importDefault(require("ts-dedent"));
9
14
  const NgModulesAnalyzer_1 = require("./NgModulesAnalyzer");
10
15
  exports.reflectionCapabilities = new core_1.ɵReflectionCapabilities();
11
16
  exports.REMOVED_MODULES = new core_1.InjectionToken('REMOVED_MODULES');
@@ -28,7 +33,6 @@ class PropertyExtractor {
28
33
  *
29
34
  * - Removes Restricted Imports
30
35
  * - Extracts providers from ModuleWithProviders
31
- * - Flattens imports
32
36
  * - Returns a new NgModuleMetadata object
33
37
  *
34
38
  *
@@ -36,27 +40,41 @@ class PropertyExtractor {
36
40
  this.analyzeMetadata = (metadata) => {
37
41
  const declarations = [...(metadata?.declarations || [])];
38
42
  const providers = [...(metadata?.providers || [])];
39
- const singletons = [...(metadata?.singletons || [])];
43
+ const applicationProviders = [];
40
44
  const imports = [...(metadata?.imports || [])].reduce((acc, imported) => {
41
45
  // remove ngModule and use only its providers if it is restricted
42
46
  // (e.g. BrowserModule, BrowserAnimationsModule, NoopAnimationsModule, ...etc)
43
47
  const [isRestricted, restrictedProviders] = PropertyExtractor.analyzeRestricted(imported);
44
48
  if (isRestricted) {
45
- singletons.unshift(restrictedProviders || []);
49
+ applicationProviders.unshift(restrictedProviders || []);
46
50
  return acc;
47
51
  }
48
52
  acc.push(imported);
49
53
  return acc;
50
54
  }, []);
51
- return { ...metadata, imports, providers, singletons, declarations };
55
+ return { ...metadata, imports, providers, applicationProviders, declarations };
52
56
  };
53
57
  this.init();
54
58
  }
59
+ // With the new way of mounting standalone components to the DOM via bootstrapApplication API,
60
+ // we should now pass ModuleWithProviders to the providers array of the bootstrapApplication function.
61
+ static warnImportsModuleWithProviders(propertyExtractor) {
62
+ const hasModuleWithProvidersImport = propertyExtractor.imports.some((importedModule) => 'ngModule' in importedModule);
63
+ if (hasModuleWithProvidersImport) {
64
+ console.warn((0, ts_dedent_1.default)(`
65
+ Storybook Warning:
66
+ moduleMetadata property 'imports' contains one or more ModuleWithProviders, likely the result of a 'Module.forRoot()'-style call.
67
+ In Storybook 7.0 we use Angular's new 'bootstrapApplication' API to mount the component to the DOM, which accepts a list of providers to set up application-wide providers.
68
+ Use the 'applicationConfig' decorator from '@storybook/angular' to pass your ModuleWithProviders to the 'providers' property in combination with the importProvidersFrom helper function from '@angular/core' to extract all the necessary providers.
69
+ Visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information
70
+ `));
71
+ }
72
+ }
55
73
  init() {
56
74
  const analyzed = this.analyzeMetadata(this.metadata);
57
75
  this.imports = (0, exports.uniqueArray)([common_1.CommonModule, analyzed.imports]);
58
76
  this.providers = (0, exports.uniqueArray)(analyzed.providers);
59
- this.singletons = (0, exports.uniqueArray)(analyzed.singletons);
77
+ this.applicationProviders = (0, exports.uniqueArray)(analyzed.applicationProviders);
60
78
  this.declarations = (0, exports.uniqueArray)(analyzed.declarations);
61
79
  if (this.component) {
62
80
  const { isDeclarable, isStandalone } = PropertyExtractor.analyzeDecorators(this.component);
@@ -73,28 +91,36 @@ class PropertyExtractor {
73
91
  exports.PropertyExtractor = PropertyExtractor;
74
92
  _a = PropertyExtractor;
75
93
  PropertyExtractor.analyzeRestricted = (ngModule) => {
76
- /**
77
- * BrowserModule is restricted,
78
- * because bootstrapApplication API, which mounts the component to the DOM,
79
- * automatically imports BrowserModule
80
- */
81
94
  if (ngModule === platform_browser_1.BrowserModule) {
95
+ console.warn((0, ts_dedent_1.default) `
96
+ Storybook Warning:
97
+ You have imported the "BrowserModule", which is not necessary anymore.
98
+ In Storybook v7.0 we are using Angular's new bootstrapApplication API to mount an Angular application to the DOM.
99
+ Note that the BrowserModule providers are automatically included when starting an application with bootstrapApplication()
100
+ Please remove the "BrowserModule" from the list of imports in your moduleMetadata definition to remove this warning.
101
+ `);
82
102
  return [true];
83
103
  }
84
- /**
85
- * BrowserAnimationsModule imports BrowserModule, which is restricted,
86
- * because bootstrapApplication API, which mounts the component to the DOM,
87
- * automatically imports BrowserModule
88
- */
89
104
  if (ngModule === animations_1.BrowserAnimationsModule) {
105
+ console.warn((0, ts_dedent_1.default) `
106
+ Storybook Warning:
107
+ You have added the "BrowserAnimationsModule" to the list of "imports" in your moduleMetadata definition of your Story.
108
+ In Storybook 7.0 we use Angular's new 'bootstrapApplication' API to mount the component to the DOM, which accepts a list of providers to set up application-wide providers.
109
+ Use the 'applicationConfig' decorator from '@storybook/angular' and add the "provideAnimations" function to the list of "providers".
110
+ If your Angular version does not support "provide-like" functions, use the helper function importProvidersFrom instead to set up animations. For this case, please add "importProvidersFrom(BrowserAnimationsModule)" to the list of providers of your applicationConfig definition.
111
+ Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information.
112
+ `);
90
113
  return [true, (0, animations_1.provideAnimations)()];
91
114
  }
92
- /**
93
- * NoopAnimationsModule imports BrowserModule, which is restricted,
94
- * because bootstrapApplication API, which mounts the component to the DOM,
95
- * automatically imports BrowserModule
96
- */
97
115
  if (ngModule === animations_1.NoopAnimationsModule) {
116
+ console.warn((0, ts_dedent_1.default) `
117
+ Storybook Warning:
118
+ You have added the "NoopAnimationsModule" to the list of "imports" in your moduleMetadata definition of your Story.
119
+ In Storybook v7.0 we are using Angular's new bootstrapApplication API to mount an Angular application to the DOM, which accepts a list of providers to set up application-wide providers.
120
+ Use the 'applicationConfig' decorator from '@storybook/angular' and add the "provideNoopAnimations" function to the list of "providers".
121
+ If your Angular version does not support "provide-like" functions, use the helper function importProvidersFrom instead to set up noop animations and to extract all necessary providers from NoopAnimationsModule. For this case, please add "importProvidersFrom(NoopAnimationsModule)" to the list of providers of your applicationConfig definition.
122
+ Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information.
123
+ `);
98
124
  return [true, (0, animations_1.provideNoopAnimations)()];
99
125
  }
100
126
  return [false];
@@ -40,9 +40,9 @@ const extractProviders = (metadata, component) => {
40
40
  const { providers } = new PropertyExtractor_1.PropertyExtractor(metadata, component);
41
41
  return providers;
42
42
  };
43
- const extractSingletons = (metadata, component) => {
44
- const { singletons } = new PropertyExtractor_1.PropertyExtractor(metadata, component);
45
- return singletons;
43
+ const extractApplicationProviders = (metadata, component) => {
44
+ const { applicationProviders } = new PropertyExtractor_1.PropertyExtractor(metadata, component);
45
+ return applicationProviders;
46
46
  };
47
47
  describe('PropertyExtractor', () => {
48
48
  describe('analyzeMetadata', () => {
@@ -50,46 +50,46 @@ describe('PropertyExtractor', () => {
50
50
  const metadata = {
51
51
  imports: [platform_browser_1.BrowserModule],
52
52
  };
53
- const { imports, providers, singletons } = analyzeMetadata(metadata);
53
+ const { imports, providers, applicationProviders } = analyzeMetadata(metadata);
54
54
  expect(imports.flat(Number.MAX_VALUE)).toEqual([common_1.CommonModule]);
55
55
  expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
56
- expect(singletons.flat(Number.MAX_VALUE)).toEqual([]);
56
+ expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]);
57
57
  });
58
58
  it('should remove BrowserAnimationsModule and use its providers instead', () => {
59
59
  const metadata = {
60
60
  imports: [animations_1.BrowserAnimationsModule],
61
61
  };
62
- const { imports, providers, singletons } = analyzeMetadata(metadata);
62
+ const { imports, providers, applicationProviders } = analyzeMetadata(metadata);
63
63
  expect(imports.flat(Number.MAX_VALUE)).toEqual([common_1.CommonModule]);
64
64
  expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
65
- expect(singletons.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideAnimations)());
65
+ expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideAnimations)());
66
66
  });
67
67
  it('should remove NoopAnimationsModule and use its providers instead', () => {
68
68
  const metadata = {
69
69
  imports: [animations_1.NoopAnimationsModule],
70
70
  };
71
- const { imports, providers, singletons } = analyzeMetadata(metadata);
71
+ const { imports, providers, applicationProviders } = analyzeMetadata(metadata);
72
72
  expect(imports.flat(Number.MAX_VALUE)).toEqual([common_1.CommonModule]);
73
73
  expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
74
- expect(singletons.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideNoopAnimations)());
74
+ expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideNoopAnimations)());
75
75
  });
76
76
  it('should remove Browser/Animations modules recursively', () => {
77
77
  const metadata = {
78
78
  imports: [animations_1.BrowserAnimationsModule, platform_browser_1.BrowserModule],
79
79
  };
80
- const { imports, providers, singletons } = analyzeMetadata(metadata);
80
+ const { imports, providers, applicationProviders } = analyzeMetadata(metadata);
81
81
  expect(imports.flat(Number.MAX_VALUE)).toEqual([common_1.CommonModule]);
82
82
  expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
83
- expect(singletons.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideAnimations)());
83
+ expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual((0, animations_1.provideAnimations)());
84
84
  });
85
85
  it('should not destructure Angular official module', () => {
86
86
  const metadata = {
87
87
  imports: [test_module_1.WithOfficialModule],
88
88
  };
89
- const { imports, providers, singletons } = analyzeMetadata(metadata);
89
+ const { imports, providers, applicationProviders } = analyzeMetadata(metadata);
90
90
  expect(imports.flat(Number.MAX_VALUE)).toEqual([common_1.CommonModule, test_module_1.WithOfficialModule]);
91
91
  expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
92
- expect(singletons.flat(Number.MAX_VALUE)).toEqual([]);
92
+ expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]);
93
93
  });
94
94
  });
95
95
  describe('extractImports', () => {
@@ -132,7 +132,7 @@ describe('PropertyExtractor', () => {
132
132
  expect(providers).toEqual([TestService]);
133
133
  });
134
134
  it('should return an array of singletons extracted', () => {
135
- const singeltons = extractSingletons({
135
+ const singeltons = extractApplicationProviders({
136
136
  imports: [animations_1.BrowserAnimationsModule],
137
137
  });
138
138
  expect(singeltons).toEqual((0, animations_1.provideAnimations)());
@@ -169,37 +169,6 @@ describe('decorateStory', () => {
169
169
  userDefinedTemplate: false,
170
170
  });
171
171
  });
172
- it('should include legacy story components in decorators', () => {
173
- const decorators = [
174
- (s) => {
175
- const story = s();
176
- return {
177
- ...story,
178
- template: `<parent>${story.template}</parent>`,
179
- };
180
- },
181
- (s) => {
182
- const story = s();
183
- return {
184
- ...story,
185
- template: `<grandparent>${story.template}</grandparent>`,
186
- };
187
- },
188
- (s) => {
189
- const story = s();
190
- return {
191
- ...story,
192
- template: `<great-grandparent>${story.template}</great-grandparent>`,
193
- };
194
- },
195
- ];
196
- const decorated = (0, decorateStory_1.default)(() => ({ component: FooComponent }), decorators);
197
- expect(decorated(makeContext({}))).toEqual({
198
- template: '<great-grandparent><grandparent><parent><foo></foo></parent></grandparent></great-grandparent>',
199
- component: FooComponent,
200
- userDefinedTemplate: false,
201
- });
202
- });
203
172
  it('should keep template with an empty value', () => {
204
173
  const decorators = [
205
174
  (0, decorators_1.componentWrapperDecorator)(ParentComponent),
@@ -1,5 +1,14 @@
1
1
  import { Type } from '@angular/core';
2
+ import { ApplicationConfig } from '@angular/platform-browser';
2
3
  import { DecoratorFunction, StoryContext } from '@storybook/types';
3
4
  import { ICollection, NgModuleMetadata, AngularRenderer } from './types';
4
5
  export declare const moduleMetadata: <TArgs = any>(metadata: Partial<NgModuleMetadata>) => DecoratorFunction<AngularRenderer, TArgs>;
6
+ /**
7
+ * Decorator to set the config options which are available during the application bootstrap operation
8
+ */
9
+ export declare function applicationConfig<TArgs = any>(
10
+ /**
11
+ * Set of config options available during the application bootstrap operation.
12
+ */
13
+ config: ApplicationConfig): DecoratorFunction<AngularRenderer, TArgs>;
5
14
  export declare const componentWrapperDecorator: <TArgs = any>(element: Type<unknown> | ((story: string) => string), props?: ICollection | ((storyContext: StoryContext<AngularRenderer, TArgs>) => ICollection)) => DecoratorFunction<AngularRenderer, TArgs>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.componentWrapperDecorator = exports.moduleMetadata = void 0;
3
+ exports.componentWrapperDecorator = exports.applicationConfig = exports.moduleMetadata = void 0;
4
4
  const ComputesTemplateFromComponent_1 = require("./angular-beta/ComputesTemplateFromComponent");
5
5
  const NgComponentAnalyzer_1 = require("./angular-beta/utils/NgComponentAnalyzer");
6
6
  // We use `any` here as the default type rather than `Args` because we need something that is
@@ -24,6 +24,30 @@ const moduleMetadata = (metadata) => (storyFn) => {
24
24
  };
25
25
  };
26
26
  exports.moduleMetadata = moduleMetadata;
27
+ /**
28
+ * Decorator to set the config options which are available during the application bootstrap operation
29
+ */
30
+ function applicationConfig(
31
+ /**
32
+ * Set of config options available during the application bootstrap operation.
33
+ */
34
+ config) {
35
+ return (storyFn) => {
36
+ const story = storyFn();
37
+ const storyConfig = story.applicationConfig;
38
+ return {
39
+ ...story,
40
+ applicationConfig: storyConfig || config
41
+ ? {
42
+ ...config,
43
+ ...storyConfig,
44
+ providers: [...(config?.providers || []), ...(storyConfig?.providers || [])],
45
+ }
46
+ : undefined,
47
+ };
48
+ };
49
+ }
50
+ exports.applicationConfig = applicationConfig;
27
51
  const componentWrapperDecorator = (element, props) => (storyFn, storyContext) => {
28
52
  const story = storyFn();
29
53
  const currentProps = typeof props === 'function' ? props(storyContext) : props;
@@ -39,18 +39,58 @@ let MockComponent = class MockComponent {
39
39
  MockComponent = __decorate([
40
40
  (0, core_1.Component)({})
41
41
  ], MockComponent);
42
+ describe('applicationConfig', () => {
43
+ const provider1 = () => { };
44
+ const provider2 = () => { };
45
+ it('should apply global config', () => {
46
+ expect((0, decorators_1.applicationConfig)({
47
+ providers: [provider1],
48
+ })(() => ({}), defaultContext)).toEqual({
49
+ applicationConfig: {
50
+ providers: [provider1],
51
+ },
52
+ });
53
+ });
54
+ it('should apply story config', () => {
55
+ expect((0, decorators_1.applicationConfig)({
56
+ providers: [],
57
+ })(() => ({
58
+ applicationConfig: {
59
+ providers: [provider2],
60
+ },
61
+ }), {
62
+ ...defaultContext,
63
+ })).toEqual({
64
+ applicationConfig: {
65
+ providers: [provider2],
66
+ },
67
+ });
68
+ });
69
+ it('should merge global and story config', () => {
70
+ expect((0, decorators_1.applicationConfig)({
71
+ providers: [provider1],
72
+ })(() => ({
73
+ applicationConfig: {
74
+ providers: [provider2],
75
+ },
76
+ }), {
77
+ ...defaultContext,
78
+ })).toEqual({
79
+ applicationConfig: {
80
+ providers: [provider1, provider2],
81
+ },
82
+ });
83
+ });
84
+ });
42
85
  describe('moduleMetadata', () => {
43
86
  it('should add metadata to a story without it', () => {
44
87
  const result = (0, decorators_1.moduleMetadata)({
45
88
  imports: [MockModule],
46
89
  providers: [MockService],
47
- })(() => ({
48
- component: MockComponent,
49
- }),
90
+ })(() => ({}),
50
91
  // deepscan-disable-next-line
51
92
  defaultContext);
52
93
  expect(result).toEqual({
53
- component: MockComponent,
54
94
  moduleMetadata: {
55
95
  declarations: [],
56
96
  entryComponents: [],
@@ -2,4 +2,4 @@ import './globals';
2
2
  export * from './public-api';
3
3
  export * from './public-types';
4
4
  export type { StoryFnAngularReturnType as IStory } from './types';
5
- export { moduleMetadata, componentWrapperDecorator } from './decorators';
5
+ export { moduleMetadata, componentWrapperDecorator, applicationConfig } from './decorators';
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.componentWrapperDecorator = exports.moduleMetadata = void 0;
18
+ exports.applicationConfig = exports.componentWrapperDecorator = exports.moduleMetadata = void 0;
19
19
  require("./globals");
20
20
  // eslint-disable-next-line import/export
21
21
  __exportStar(require("./public-api"), exports);
@@ -24,5 +24,6 @@ __exportStar(require("./public-types"), exports);
24
24
  var decorators_1 = require("./decorators");
25
25
  Object.defineProperty(exports, "moduleMetadata", { enumerable: true, get: function () { return decorators_1.moduleMetadata; } });
26
26
  Object.defineProperty(exports, "componentWrapperDecorator", { enumerable: true, get: function () { return decorators_1.componentWrapperDecorator; } });
27
+ Object.defineProperty(exports, "applicationConfig", { enumerable: true, get: function () { return decorators_1.applicationConfig; } });
27
28
  // optimization: stop HMR propagation in webpack
28
29
  module?.hot?.decline();
@@ -1,22 +1,31 @@
1
+ import { Provider } from '@angular/core';
2
+ import { ApplicationConfig } from '@angular/platform-browser';
1
3
  import { Parameters as DefaultParameters, StoryContext as DefaultStoryContext, WebRenderer } from '@storybook/types';
2
4
  export interface NgModuleMetadata {
5
+ /**
6
+ * List of components, directives, and pipes that belong to your component.
7
+ */
3
8
  declarations?: any[];
4
9
  entryComponents?: any[];
10
+ /**
11
+ * List of modules that should be available to the root Storybook Component and all its children.
12
+ * If you want to register application providers or if you want to use the forRoot() pattern, please use the `applicationConfig` decorator in combination with the importProvidersFrom helper function from @angular/core instead.
13
+ */
5
14
  imports?: any[];
6
15
  schemas?: any[];
7
- providers?: any[];
8
- singletons?: any[];
16
+ /**
17
+ * List of providers that should be available on the root component and all its children.
18
+ * Use the `applicationConfig` decorator to register environemt and application-wide providers.
19
+ */
20
+ providers?: Provider[];
9
21
  }
10
22
  export interface ICollection {
11
23
  [p: string]: any;
12
24
  }
13
25
  export interface StoryFnAngularReturnType {
14
- /** @deprecated `component` story input is deprecated, and will be removed in Storybook 7.0. */
15
- component?: any;
16
26
  props?: ICollection;
17
- /** @deprecated `propsMeta` story input is deprecated, and will be removed in Storybook 7.0. */
18
- propsMeta?: ICollection;
19
27
  moduleMetadata?: NgModuleMetadata;
28
+ applicationConfig?: ApplicationConfig;
20
29
  template?: string;
21
30
  styles?: string[];
22
31
  userDefinedTemplate?: boolean;
@@ -1,14 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.previewAnnotations = void 0;
7
- const path_1 = __importDefault(require("path"));
8
4
  const docs_tools_1 = require("@storybook/docs-tools");
9
5
  const previewAnnotations = (entry = [], options) => {
10
6
  if (!(0, docs_tools_1.hasDocsOrControls)(options))
11
7
  return entry;
12
- return [...entry, path_1.default.join(__dirname, '../../dist/client/docs/config')];
8
+ return [...entry, require.resolve('../client/docs/config')];
13
9
  };
14
10
  exports.previewAnnotations = previewAnnotations;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/angular",
3
- "version": "7.0.0-rc.9",
3
+ "version": "7.0.1",
4
4
  "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
5
5
  "keywords": [
6
6
  "storybook",
@@ -36,20 +36,20 @@
36
36
  "prep": "../../../scripts/prepare/tsc.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@storybook/builder-webpack5": "7.0.0-rc.9",
40
- "@storybook/cli": "7.0.0-rc.9",
41
- "@storybook/client-logger": "7.0.0-rc.9",
42
- "@storybook/core-client": "7.0.0-rc.9",
43
- "@storybook/core-common": "7.0.0-rc.9",
44
- "@storybook/core-events": "7.0.0-rc.9",
45
- "@storybook/core-server": "7.0.0-rc.9",
46
- "@storybook/core-webpack": "7.0.0-rc.9",
47
- "@storybook/docs-tools": "7.0.0-rc.9",
39
+ "@storybook/builder-webpack5": "7.0.1",
40
+ "@storybook/cli": "7.0.1",
41
+ "@storybook/client-logger": "7.0.1",
42
+ "@storybook/core-client": "7.0.1",
43
+ "@storybook/core-common": "7.0.1",
44
+ "@storybook/core-events": "7.0.1",
45
+ "@storybook/core-server": "7.0.1",
46
+ "@storybook/core-webpack": "7.0.1",
47
+ "@storybook/docs-tools": "7.0.1",
48
48
  "@storybook/global": "^5.0.0",
49
- "@storybook/manager-api": "7.0.0-rc.9",
50
- "@storybook/node-logger": "7.0.0-rc.9",
51
- "@storybook/preview-api": "7.0.0-rc.9",
52
- "@storybook/types": "7.0.0-rc.9",
49
+ "@storybook/manager-api": "7.0.1",
50
+ "@storybook/node-logger": "7.0.1",
51
+ "@storybook/preview-api": "7.0.1",
52
+ "@storybook/types": "7.0.1",
53
53
  "@types/node": "^16.0.0",
54
54
  "@types/react": "^16.14.34",
55
55
  "@types/react-dom": "^16.9.14",
@@ -123,5 +123,5 @@
123
123
  "bundler": {
124
124
  "tsConfig": "tsconfig.build.json"
125
125
  },
126
- "gitHead": "618743e115ad0f172537a4b7b6cdfb0beaef52b5"
126
+ "gitHead": "2fe0c39167a40d6856f5cbc2ab927b3b65fc384b"
127
127
  }
@@ -1,5 +1,5 @@
1
1
  import { OnDestroy, OnInit, Component } from '@angular/core';
2
- import { Meta, StoryFn } from '@storybook/angular';
2
+ import { Meta, StoryObj } from '@storybook/angular';
3
3
 
4
4
  @Component({
5
5
  selector: 'on-destroy',
@@ -37,6 +37,4 @@ export default {
37
37
  },
38
38
  } as Meta;
39
39
 
40
- export const SimpleComponent: StoryFn = () => ({
41
- component: OnDestroyComponent,
42
- });
40
+ export const SimpleComponent: StoryObj = {};
@@ -0,0 +1,40 @@
1
+ import { Meta, StoryObj, applicationConfig } from '@storybook/angular';
2
+ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
3
+ import { within, userEvent } from '@storybook/testing-library';
4
+ import { expect } from '@storybook/jest';
5
+ import { importProvidersFrom } from '@angular/core';
6
+ import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component';
7
+
8
+ const meta: Meta = {
9
+ component: OpenCloseComponent,
10
+ decorators: [
11
+ applicationConfig({
12
+ providers: [importProvidersFrom(BrowserAnimationsModule)],
13
+ }),
14
+ ],
15
+ parameters: {
16
+ chromatic: { delay: 100 },
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+
22
+ type Story = StoryObj<typeof OpenCloseComponent>;
23
+
24
+ export const WithBrowserAnimations: Story = {
25
+ render: () => ({
26
+ template: `<app-open-close></app-open-close>`,
27
+ moduleMetadata: {
28
+ declarations: [OpenCloseComponent],
29
+ },
30
+ }),
31
+ play: async ({ canvasElement }) => {
32
+ const canvas = within(canvasElement);
33
+ const opened = canvas.getByText('The box is now Open!');
34
+ expect(opened).toBeDefined();
35
+ const submitButton = canvas.getByRole('button');
36
+ await userEvent.click(submitButton);
37
+ const closed = canvas.getByText('The box is now Closed!');
38
+ expect(closed).toBeDefined();
39
+ },
40
+ };
@@ -0,0 +1,35 @@
1
+ import { Meta, StoryObj } from '@storybook/angular';
2
+ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
3
+ import { within, userEvent } from '@storybook/testing-library';
4
+ import { expect } from '@storybook/jest';
5
+ import { importProvidersFrom } from '@angular/core';
6
+ import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component';
7
+
8
+ const meta: Meta = {
9
+ component: OpenCloseComponent,
10
+ };
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof OpenCloseComponent>;
15
+
16
+ export const WithNoopBrowserAnimations: Story = {
17
+ render: () => ({
18
+ template: `<app-open-close></app-open-close>`,
19
+ applicationConfig: {
20
+ providers: [importProvidersFrom(NoopAnimationsModule)],
21
+ },
22
+ moduleMetadata: {
23
+ declarations: [OpenCloseComponent],
24
+ },
25
+ }),
26
+ play: async ({ canvasElement }) => {
27
+ const canvas = within(canvasElement);
28
+ const opened = canvas.getByText('The box is now Open!');
29
+ expect(opened).toBeDefined();
30
+ const submitButton = canvas.getByRole('button');
31
+ await userEvent.click(submitButton);
32
+ const closed = canvas.getByText('The box is now Closed!');
33
+ expect(closed).toBeDefined();
34
+ },
35
+ };
@@ -15,7 +15,7 @@ Here is a simple example with a storybook decorator that you can place in the `p
15
15
  import { HttpClient, HttpClientModule } from '@angular/common/http';
16
16
  import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
17
17
  import { TranslateHttpLoader } from '@ngx-translate/http-loader';
18
- import { moduleMetadata } from '@storybook/angular';
18
+ import { moduleMetadata, applicationConfig } from '@storybook/angular';
19
19
 
20
20
  function createTranslateLoader(http: HttpClient) {
21
21
  return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
@@ -24,23 +24,30 @@ function createTranslateLoader(http: HttpClient) {
24
24
  const TranslateModuleDecorator = (storyFunc, context) => {
25
25
  const { locale } = context.globals;
26
26
 
27
- return moduleMetadata({
28
- imports: [
29
- HttpClientModule,
30
- TranslateModule.forRoot({
31
- defaultLanguage: locale,
32
- loader: {
33
- provide: TranslateLoader,
34
- useFactory: createTranslateLoader,
35
- deps: [HttpClient],
36
- },
37
- }),
38
- ],
27
+ return applicationConfig({
28
+ providers: [
29
+ importProvidersFrom(
30
+ HttpClientModule,
31
+ TranslateModule.forRoot({
32
+ defaultLanguage: locale,
33
+ loader: {
34
+ provide: TranslateLoader,
35
+ useFactory: createTranslateLoader,
36
+ deps: [HttpClient],
37
+ },
38
+ })
39
+ )
40
+ ]
39
41
  })(storyFunc, context);
40
42
  };
41
43
 
42
44
  // for `preview.ts`
43
- export const decorators = [TranslateModuleDecorator];
45
+ export const decorators = [
46
+ moduleMetadata({
47
+ imports: [TranslateModule],
48
+ }),
49
+ TranslateModuleDecorator,
50
+ ];
44
51
  ```
45
52
 
46
53
  If the `TranslateModule.forRoot` is made by another module you can try to set this provider `DEFAULT_LANGUAGE`
@@ -51,7 +58,7 @@ import { DEFAULT_LANGUAGE } from '@ngx-translate/core';
51
58
  const TranslateModuleDecorator = (storyFunc, context) => {
52
59
  const { locale } = context.globals;
53
60
 
54
- return moduleMetadata({
61
+ return applicationConfig({
55
62
  providers: [{ provide: DEFAULT_LANGUAGE, useValue: locale }],
56
63
  })(storyFunc, context);
57
64
  };
@@ -1,38 +0,0 @@
1
- import { ViewContainerRef, ChangeDetectorRef, OnInit, ComponentFactoryResolver, OnDestroy } from '@angular/core';
2
- import { Observable, Subscription } from 'rxjs';
3
- import { StoryFnAngularReturnType } from '../types';
4
- export declare class AppComponent implements OnInit, OnDestroy {
5
- private cfr;
6
- private changeDetectorRef;
7
- private data;
8
- target: ViewContainerRef;
9
- readonly previousValues: {
10
- [key: string]: any;
11
- };
12
- subscription: Subscription;
13
- propSubscriptions: Map<any, {
14
- prop: any;
15
- sub: Subscription;
16
- }>;
17
- constructor(cfr: ComponentFactoryResolver, changeDetectorRef: ChangeDetectorRef, data: Observable<StoryFnAngularReturnType>);
18
- ngOnInit(): void;
19
- ngOnDestroy(): void;
20
- /**
21
- * Set inputs and outputs
22
- */
23
- private setProps;
24
- /**
25
- * Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components
26
- * Issue: [https://github.com/angular/angular/issues/8903]
27
- */
28
- private callNgOnChangesHook;
29
- /**
30
- * If component implements ControlValueAccessor interface try to set ngModel
31
- */
32
- private setNgModel;
33
- /**
34
- * Store ref to subscription for cleanup in 'ngOnDestroy' and check if
35
- * observable needs to be resubscribed to, before creating a new subscription.
36
- */
37
- private setPropSubscription;
38
- }
@@ -1,141 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.AppComponent = void 0;
16
- // We could use NgComponentOutlet here but there's currently no easy way
17
- // to provide @Inputs and subscribe to @Outputs, see
18
- // https://github.com/angular/angular/issues/15360
19
- // For the time being, the ViewContainerRef approach works pretty well.
20
- const core_1 = require("@angular/core");
21
- const rxjs_1 = require("rxjs");
22
- const operators_1 = require("rxjs/operators");
23
- const app_token_1 = require("./app.token");
24
- let AppComponent = class AppComponent {
25
- constructor(cfr, changeDetectorRef, data) {
26
- this.cfr = cfr;
27
- this.changeDetectorRef = changeDetectorRef;
28
- this.data = data;
29
- this.previousValues = {};
30
- this.propSubscriptions = new Map();
31
- }
32
- ngOnInit() {
33
- this.data.pipe((0, operators_1.first)()).subscribe((data) => {
34
- this.target.clear();
35
- const compFactory = this.cfr.resolveComponentFactory(data.component);
36
- const componentRef = this.target.createComponent(compFactory);
37
- const { instance } = componentRef;
38
- // For some reason, manual change detection ref is only working when getting the ref from the injector (rather than componentRef.changeDetectorRef)
39
- const childChangeDetectorRef = componentRef.injector.get(core_1.ChangeDetectorRef);
40
- this.subscription = this.data.subscribe((newData) => {
41
- this.setProps(instance, newData);
42
- childChangeDetectorRef.markForCheck();
43
- // Must detect changes on the current component in order to update any changes in child component's @HostBinding properties (angular/angular#22560)
44
- this.changeDetectorRef.detectChanges();
45
- });
46
- });
47
- }
48
- ngOnDestroy() {
49
- this.target.clear();
50
- if (this.subscription) {
51
- this.subscription.unsubscribe();
52
- }
53
- this.propSubscriptions.forEach((v) => {
54
- if (!v.sub.closed) {
55
- v.sub.unsubscribe();
56
- }
57
- });
58
- this.propSubscriptions.clear();
59
- }
60
- /**
61
- * Set inputs and outputs
62
- */
63
- setProps(instance, { props = {} }) {
64
- const changes = {};
65
- const hasNgOnChangesHook = !!instance.ngOnChanges;
66
- Object.keys(props).forEach((key) => {
67
- const value = props[key];
68
- const instanceProperty = instance[key];
69
- if (!(instanceProperty instanceof core_1.EventEmitter) && value !== undefined && value !== null) {
70
- // eslint-disable-next-line no-param-reassign
71
- instance[key] = value;
72
- if (hasNgOnChangesHook) {
73
- const previousValue = this.previousValues[key];
74
- if (previousValue !== value) {
75
- changes[key] = new core_1.SimpleChange(previousValue, value, !Object.prototype.hasOwnProperty.call(this.previousValues, key));
76
- this.previousValues[key] = value;
77
- }
78
- }
79
- }
80
- else if (typeof value === 'function' && key !== 'ngModelChange') {
81
- this.setPropSubscription(key, instanceProperty, value);
82
- }
83
- });
84
- this.callNgOnChangesHook(instance, changes);
85
- this.setNgModel(instance, props);
86
- }
87
- /**
88
- * Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components
89
- * Issue: [https://github.com/angular/angular/issues/8903]
90
- */
91
- callNgOnChangesHook(instance, changes) {
92
- if (Object.keys(changes).length) {
93
- instance.ngOnChanges(changes);
94
- }
95
- }
96
- /**
97
- * If component implements ControlValueAccessor interface try to set ngModel
98
- */
99
- setNgModel(instance, props) {
100
- if (props.ngModel) {
101
- instance.writeValue(props.ngModel);
102
- }
103
- if (typeof props.ngModelChange === 'function') {
104
- instance.registerOnChange(props.ngModelChange);
105
- }
106
- }
107
- /**
108
- * Store ref to subscription for cleanup in 'ngOnDestroy' and check if
109
- * observable needs to be resubscribed to, before creating a new subscription.
110
- */
111
- setPropSubscription(key, instanceProperty, value) {
112
- if (this.propSubscriptions.has(key)) {
113
- const v = this.propSubscriptions.get(key);
114
- if (v.prop === value) {
115
- // Prop hasn't changed, so the existing subscription can stay.
116
- return;
117
- }
118
- // Now that the value has changed, unsubscribe from the previous value's subscription.
119
- if (!v.sub.closed) {
120
- v.sub.unsubscribe();
121
- }
122
- }
123
- const sub = instanceProperty.subscribe(value);
124
- this.propSubscriptions.set(key, { prop: value, sub });
125
- }
126
- };
127
- __decorate([
128
- (0, core_1.ViewChild)('target', { read: core_1.ViewContainerRef, static: true }),
129
- __metadata("design:type", core_1.ViewContainerRef)
130
- ], AppComponent.prototype, "target", void 0);
131
- AppComponent = __decorate([
132
- (0, core_1.Component)({
133
- selector: 'storybook-dynamic-app-root',
134
- template: '<ng-template #target></ng-template>',
135
- }),
136
- __param(2, (0, core_1.Inject)(app_token_1.STORY)),
137
- __metadata("design:paramtypes", [core_1.ComponentFactoryResolver,
138
- core_1.ChangeDetectorRef,
139
- rxjs_1.Observable])
140
- ], AppComponent);
141
- exports.AppComponent = AppComponent;
@@ -1,3 +0,0 @@
1
- import { InjectionToken } from '@angular/core';
2
- import { StoryFnAngularReturnType } from '../types';
3
- export declare const STORY: InjectionToken<StoryFnAngularReturnType>;
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.STORY = void 0;
4
- const core_1 = require("@angular/core");
5
- exports.STORY = new core_1.InjectionToken('story');
@@ -1,30 +0,0 @@
1
- import { Meta, StoryFn } from '@storybook/angular';
2
- import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
3
- import { within, userEvent } from '@storybook/testing-library';
4
- import { expect } from '@storybook/jest';
5
- import { OpenCloseComponent } from './angular-src/open-close-component/open-close.component';
6
-
7
- export default {
8
- component: OpenCloseComponent,
9
- parameters: {
10
- chromatic: { delay: 100 },
11
- },
12
- } as Meta;
13
-
14
- export const WithBrowserAnimations: StoryFn = () => ({
15
- template: `<app-open-close></app-open-close>`,
16
- moduleMetadata: {
17
- declarations: [OpenCloseComponent],
18
- imports: [BrowserAnimationsModule],
19
- },
20
- });
21
-
22
- WithBrowserAnimations.play = async ({ canvasElement }) => {
23
- const canvas = within(canvasElement);
24
- const opened = canvas.getByText('The box is now Open!');
25
- expect(opened).toBeDefined();
26
- const submitButton = canvas.getByRole('button');
27
- await userEvent.click(submitButton);
28
- const closed = canvas.getByText('The box is now Closed!');
29
- expect(closed).toBeDefined();
30
- };
@@ -1,27 +0,0 @@
1
- import { Meta, StoryFn } from '@storybook/angular';
2
- import { NoopAnimationsModule } from '@angular/platform-browser/animations';
3
- import { within, userEvent } from '@storybook/testing-library';
4
- import { expect } from '@storybook/jest';
5
- import { OpenCloseComponent } from './angular-src/open-close-component/open-close.component';
6
-
7
- export default {
8
- component: OpenCloseComponent,
9
- } as Meta;
10
-
11
- export const WithNoopBrowserAnimations: StoryFn = () => ({
12
- template: `<app-open-close></app-open-close>`,
13
- moduleMetadata: {
14
- declarations: [OpenCloseComponent],
15
- imports: [NoopAnimationsModule],
16
- },
17
- });
18
-
19
- WithNoopBrowserAnimations.play = async ({ canvasElement }) => {
20
- const canvas = within(canvasElement);
21
- const opened = canvas.getByText('The box is now Open!');
22
- expect(opened).toBeDefined();
23
- const submitButton = canvas.getByRole('button');
24
- await userEvent.click(submitButton);
25
- const closed = canvas.getByText('The box is now Closed!');
26
- expect(closed).toBeDefined();
27
- };