diff --git a/docs/generated/packages/angular/generators/component-story.json b/docs/generated/packages/angular/generators/component-story.json
index 5b82d4749c8710..9332a523820300 100644
--- a/docs/generated/packages/angular/generators/component-story.json
+++ b/docs/generated/packages/angular/generators/component-story.json
@@ -32,6 +32,13 @@
"examples": ["awesome.component"],
"x-priority": "important"
},
+ "interactionTests": {
+ "type": "boolean",
+ "description": "Set up Storybook interaction tests.",
+ "x-prompt": "Do you want to set up Storybook interaction tests?",
+ "x-priority": "important",
+ "default": true
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/docs/generated/packages/angular/generators/stories.json b/docs/generated/packages/angular/generators/stories.json
index bd89a9bbee66ea..b6d3bf6c8cff5d 100644
--- a/docs/generated/packages/angular/generators/stories.json
+++ b/docs/generated/packages/angular/generators/stories.json
@@ -18,15 +18,22 @@
"x-dropdown": "projects",
"x-priority": "important"
},
+ "interactionTests": {
+ "type": "boolean",
+ "description": "Set up Storybook interaction tests.",
+ "x-prompt": "Do you want to set up Storybook interaction tests?",
+ "x-priority": "important",
+ "default": true
+ },
"generateCypressSpecs": {
"type": "boolean",
"description": "Specifies whether to automatically generate `*.spec.ts` files in the Cypress e2e app generated by the `cypress-configure` generator.",
- "x-prompt": "Do you want to generate Cypress specs as well?",
- "x-priority": "important"
+ "x-deprecated": "Please use Storybook interaction tests instead."
},
"cypressProject": {
"type": "string",
- "description": "The Cypress project to generate the stories under. This is inferred from `name` by default."
+ "description": "The Cypress project to generate the stories under. This is inferred from `name` by default.",
+ "x-deprecated": "Please use Storybook interaction tests instead."
},
"skipFormat": {
"description": "Skip formatting files.",
diff --git a/e2e/storybook-angular/src/storybook-angular.test.ts b/e2e/storybook-angular/src/storybook-angular.test.ts
index 01c008d9d55bb8..49b68a1366ee45 100644
--- a/e2e/storybook-angular/src/storybook-angular.test.ts
+++ b/e2e/storybook-angular/src/storybook-angular.test.ts
@@ -5,11 +5,8 @@ import {
newProject,
runCLI,
runCommandUntil,
- runCypressTests,
- tmpProjPath,
uniq,
} from '@nx/e2e/utils';
-import { writeFileSync } from 'fs';
describe('Storybook executors for Angular', () => {
const angularStorybookLib = uniq('test-ui-ng-lib');
@@ -17,7 +14,7 @@ describe('Storybook executors for Angular', () => {
newProject();
runCLI(`g @nx/angular:library ${angularStorybookLib} --no-interactive`);
runCLI(
- `generate @nx/angular:storybook-configuration ${angularStorybookLib} --configureCypress --generateStories --generateCypressSpecs --no-interactive`
+ `generate @nx/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
);
});
@@ -43,80 +40,4 @@ describe('Storybook executors for Angular', () => {
checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`);
}, 200_000);
});
-
- // However much I increase the timeout, this takes forever?
- xdescribe('run cypress tests using storybook', () => {
- it('should execute e2e tests using Cypress running against Storybook', async () => {
- if (runCypressTests()) {
- addTestButtonToUILib(angularStorybookLib);
- writeFileSync(
- tmpProjPath(
- `apps/${angularStorybookLib}-e2e/src/e2e/test-button/test-button.component.cy.ts`
- ),
- `
- describe('${angularStorybookLib}, () => {
-
- it('should render the correct text', () => {
- cy.visit(
- '/iframe.html?id=testbuttoncomponent--primary&args=text:Click+me;color:#ddffdd;disabled:false;'
- )
- cy.get('button').should('contain', 'Click me');
- cy.get('button').should('not.be.disabled');
- });
-
- it('should adjust the controls', () => {
- cy.visit(
- '/iframe.html?id=testbuttoncomponent--primary&args=text:Click+me;color:#ddffdd;disabled:true;'
- )
- cy.get('button').should('be.disabled');
- });
- });
- `
- );
-
- const e2eResults = runCLI(`e2e ${angularStorybookLib}-e2e --no-watch`);
- expect(e2eResults).toContain('All specs passed!');
- expect(await killPorts()).toBeTruthy();
- }
- }, 1000_000);
- });
});
-
-function addTestButtonToUILib(libName: string): void {
- runCLI(
- `g @nx/angular:component test-button --project=${libName} --no-interactive`
- );
-
- writeFileSync(
- tmpProjPath(`libs/${libName}/src/lib/test-button/test-button.component.ts`),
- `
- import { Component, Input } from '@angular/core';
-
- @Component({
- selector: 'proj-test-button',
- templateUrl: './test-button.component.html',
- styleUrls: ['./test-button.component.css'],
- })
- export class TestButtonComponent {
- @Input() text = 'Click me';
- @Input() color = '#ddffdd';
- @Input() disabled = false;
- }
- `
- );
-
- writeFileSync(
- tmpProjPath(
- `libs/${libName}/src/lib/test-button/test-button.component.html`
- ),
- `
-
- `
- );
-}
diff --git a/packages/angular/src/generators/component-story/__snapshots__/component-story.spec.ts.snap b/packages/angular/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
index 2d774f7aa55eea..db470fa3e7d10b 100644
--- a/packages/angular/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
+++ b/packages/angular/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
@@ -1,18 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`componentStory generator should generate the right props 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { TestButtonComponent } from './test-button.component';
-export default {
- title: 'TestButtonComponent',
+const meta: Meta = {
component: TestButtonComponent,
-} as Meta;
+ title: 'TestButtonComponent',
+};
+export default meta;
+type Story = StoryObj;
-export const Primary = {
- render: (args: TestButtonComponent) => ({
- props: args,
- }),
+export const Primary: Story = {
args: {
buttonType: 'button',
style: 'default',
diff --git a/packages/angular/src/generators/component-story/component-story.ts b/packages/angular/src/generators/component-story/component-story.ts
index f087b78a37948a..f24b12c452c07d 100644
--- a/packages/angular/src/generators/component-story/component-story.ts
+++ b/packages/angular/src/generators/component-story/component-story.ts
@@ -29,6 +29,8 @@ export async function componentStoryGenerator(
generateFiles(tree, templatesDir, destinationDir, {
componentFileName: componentFileName,
componentName: componentName,
+ componentNameSimple: componentFileName.replace('.component', ''),
+ interactionTests: options.interactionTests,
props: props.filter((p) => typeof p.defaultValue !== 'undefined'),
tmpl: '',
});
diff --git a/packages/angular/src/generators/component-story/files/__componentFileName__.stories.ts__tmpl__ b/packages/angular/src/generators/component-story/files/__componentFileName__.stories.ts__tmpl__
index 338a94bde0e8c4..66981e5d6be1c9 100644
--- a/packages/angular/src/generators/component-story/files/__componentFileName__.stories.ts__tmpl__
+++ b/packages/angular/src/generators/component-story/files/__componentFileName__.stories.ts__tmpl__
@@ -1,16 +1,31 @@
-import { Meta } from '@storybook/angular';
+import type { Meta, StoryObj } from '@storybook/angular';
import { <%=componentName%> } from './<%=componentFileName%>';
+<% if ( interactionTests ) { %>
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+<% } %>
-export default {
- title: '<%=componentName%>',
- component: <%=componentName%>
-} as Meta<<%=componentName%>>;
+const meta: Meta<<%= componentName %>> = {
+ component: <%= componentName %>,
+ title: '<%= componentName %>',
+};
+export default meta;
+type Story = StoryObj<<%=componentName%>>;
-export const Primary = {
- render: (args: <%=componentName%>) => ({
- props: args,
- }),
+export const Primary: Story = {
args: {<% for (let prop of props) { %>
<%= prop.name %>: <%- prop.defaultValue %>,<% } %>
},
-};
\ No newline at end of file
+};
+
+<% if ( interactionTests ) { %>
+export const Heading: Story = {
+ args: {<% for (let prop of props) { %>
+ <%= prop.name %>: <%- prop.defaultValue %>,<% } %>
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/<%=componentNameSimple%> works!/gi)).toBeTruthy();
+ },
+};
+<% } %>
\ No newline at end of file
diff --git a/packages/angular/src/generators/component-story/schema.d.ts b/packages/angular/src/generators/component-story/schema.d.ts
index 05768fae96293e..31b965960878fc 100644
--- a/packages/angular/src/generators/component-story/schema.d.ts
+++ b/packages/angular/src/generators/component-story/schema.d.ts
@@ -1,5 +1,6 @@
export interface ComponentStoryGeneratorOptions {
projectPath: string;
+ interactionTests?: boolean;
componentName: string;
componentPath: string;
componentFileName: string;
diff --git a/packages/angular/src/generators/component-story/schema.json b/packages/angular/src/generators/component-story/schema.json
index e2591db714d359..a74cbeb20e4b96 100644
--- a/packages/angular/src/generators/component-story/schema.json
+++ b/packages/angular/src/generators/component-story/schema.json
@@ -29,6 +29,13 @@
"examples": ["awesome.component"],
"x-priority": "important"
},
+ "interactionTests": {
+ "type": "boolean",
+ "description": "Set up Storybook interaction tests.",
+ "x-prompt": "Do you want to set up Storybook interaction tests?",
+ "x-priority": "important",
+ "default": true
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/packages/angular/src/generators/stories/__snapshots__/stories-app.spec.ts.snap b/packages/angular/src/generators/stories/__snapshots__/stories-app.spec.ts.snap
index 9a80b69d508766..19657b24dd13be 100644
--- a/packages/angular/src/generators/stories/__snapshots__/stories-app.spec.ts.snap
+++ b/packages/angular/src/generators/stories/__snapshots__/stories-app.spec.ts.snap
@@ -1,37 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`angularStories generator: applications should generate stories file for inline scam component 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { MyScamComponent } from './my-scam.component';
-export default {
- title: 'MyScamComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: MyScamComponent,
-} as Meta;
+ title: 'MyScamComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
+
+export const Heading: Story = {
+ args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/my-scam works!/gi)).toBeTruthy();
+ },
+};
+"
+`;
+
+exports[`angularStories generator: applications should generate stories file with interaction tests 1`] = `
+"import type { Meta, StoryObj } from '@storybook/angular';
+import { AppComponent } from './app.component';
-export const Primary = {
- render: (args: MyScamComponent) => ({
- props: args,
- }),
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
+ component: AppComponent,
+ title: 'AppComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
args: {},
};
+
+export const Heading: Story = {
+ args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/app works!/gi)).toBeTruthy();
+ },
+};
"
`;
exports[`angularStories generator: applications should ignore a path that has a nested component, but still generate nested component stories 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { ComponentBComponent } from './component-b.component';
-export default {
- title: 'ComponentBComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: ComponentBComponent,
-} as Meta;
+ title: 'ComponentBComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
-export const Primary = {
- render: (args: ComponentBComponent) => ({
- props: args,
- }),
+export const Heading: Story = {
args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/component-b works!/gi)).toBeTruthy();
+ },
};
"
`;
diff --git a/packages/angular/src/generators/stories/__snapshots__/stories-lib.spec.ts.snap b/packages/angular/src/generators/stories/__snapshots__/stories-lib.spec.ts.snap
index 396bf86c63c60f..8d236612dd3f76 100644
--- a/packages/angular/src/generators/stories/__snapshots__/stories-lib.spec.ts.snap
+++ b/packages/angular/src/generators/stories/__snapshots__/stories-lib.spec.ts.snap
@@ -1,68 +1,76 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`angularStories generator: libraries Stories for non-empty Angular library should generate cypress spec files 1`] = `
-"describe('test-ui-lib', () => {
- beforeEach(() =>
- cy.visit(
- '/iframe.html?id=testbuttoncomponent--primary&args=buttonType:button;style:default;age;isOn:false;'
- )
- );
- it('should render the component', () => {
- cy.get('proj-test-button').should('exist');
- });
-});
-"
-`;
-
exports[`angularStories generator: libraries Stories for non-empty Angular library should generate stories file for standalone components 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { StandaloneComponent } from './standalone.component';
-export default {
- title: 'StandaloneComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: StandaloneComponent,
-} as Meta;
+ title: 'StandaloneComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
-export const Primary = {
- render: (args: StandaloneComponent) => ({
- props: args,
- }),
+export const Heading: Story = {
args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/standalone works!/gi)).toBeTruthy();
+ },
};
"
`;
exports[`angularStories generator: libraries Stories for non-empty Angular library should generate stories file for standalone components 2`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { SecondaryStandaloneComponent } from './secondary-standalone.component';
-export default {
- title: 'SecondaryStandaloneComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: SecondaryStandaloneComponent,
-} as Meta;
+ title: 'SecondaryStandaloneComponent',
+};
+export default meta;
+type Story = StoryObj;
-export const Primary = {
- render: (args: SecondaryStandaloneComponent) => ({
- props: args,
- }),
+export const Primary: Story = {
args: {},
};
+
+export const Heading: Story = {
+ args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/secondary-standalone works!/gi)).toBeTruthy();
+ },
+};
"
`;
exports[`angularStories generator: libraries Stories for non-empty Angular library should generate stories.ts files 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { TestButtonComponent } from './test-button.component';
-export default {
- title: 'TestButtonComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: TestButtonComponent,
-} as Meta;
+ title: 'TestButtonComponent',
+};
+export default meta;
+type Story = StoryObj;
-export const Primary = {
- render: (args: TestButtonComponent) => ({
- props: args,
- }),
+export const Primary: Story = {
args: {
buttonType: 'button',
style: 'default',
@@ -70,22 +78,37 @@ export const Primary = {
isOn: false,
},
};
+
+export const Heading: Story = {
+ args: {
+ buttonType: 'button',
+ style: 'default',
+ age: 0,
+ isOn: false,
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/test-button works!/gi)).toBeTruthy();
+ },
+};
"
`;
exports[`angularStories generator: libraries Stories for non-empty Angular library should ignore paths 1`] = `
-"import { Meta } from '@storybook/angular';
+"import type { Meta, StoryObj } from '@storybook/angular';
import { TestButtonComponent } from './test-button.component';
-export default {
- title: 'TestButtonComponent',
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
component: TestButtonComponent,
-} as Meta;
+ title: 'TestButtonComponent',
+};
+export default meta;
+type Story = StoryObj;
-export const Primary = {
- render: (args: TestButtonComponent) => ({
- props: args,
- }),
+export const Primary: Story = {
args: {
buttonType: 'button',
style: 'default',
@@ -93,5 +116,18 @@ export const Primary = {
isOn: false,
},
};
+
+export const Heading: Story = {
+ args: {
+ buttonType: 'button',
+ style: 'default',
+ age: 0,
+ isOn: false,
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/test-button works!/gi)).toBeTruthy();
+ },
+};
"
`;
diff --git a/packages/angular/src/generators/stories/schema.d.ts b/packages/angular/src/generators/stories/schema.d.ts
index 88364762103963..bdc41eae362288 100644
--- a/packages/angular/src/generators/stories/schema.d.ts
+++ b/packages/angular/src/generators/stories/schema.d.ts
@@ -1,5 +1,6 @@
export interface StoriesGeneratorOptions {
name: string;
+ interactionTests?: boolean;
cypressProject?: string;
generateCypressSpecs?: boolean;
skipFormat?: boolean;
diff --git a/packages/angular/src/generators/stories/schema.json b/packages/angular/src/generators/stories/schema.json
index b97cfe83f5f194..a7e638e196f558 100644
--- a/packages/angular/src/generators/stories/schema.json
+++ b/packages/angular/src/generators/stories/schema.json
@@ -18,15 +18,22 @@
"x-dropdown": "projects",
"x-priority": "important"
},
+ "interactionTests": {
+ "type": "boolean",
+ "description": "Set up Storybook interaction tests.",
+ "x-prompt": "Do you want to set up Storybook interaction tests?",
+ "x-priority": "important",
+ "default": true
+ },
"generateCypressSpecs": {
"type": "boolean",
"description": "Specifies whether to automatically generate `*.spec.ts` files in the Cypress e2e app generated by the `cypress-configure` generator.",
- "x-prompt": "Do you want to generate Cypress specs as well?",
- "x-priority": "important"
+ "x-deprecated": "Please use Storybook interaction tests instead."
},
"cypressProject": {
"type": "string",
- "description": "The Cypress project to generate the stories under. This is inferred from `name` by default."
+ "description": "The Cypress project to generate the stories under. This is inferred from `name` by default.",
+ "x-deprecated": "Please use Storybook interaction tests instead."
},
"skipFormat": {
"description": "Skip formatting files.",
diff --git a/packages/angular/src/generators/stories/stories-app.spec.ts b/packages/angular/src/generators/stories/stories-app.spec.ts
index 39ade7427e65b1..177f6fc5e96315 100644
--- a/packages/angular/src/generators/stories/stories-app.spec.ts
+++ b/packages/angular/src/generators/stories/stories-app.spec.ts
@@ -10,6 +10,8 @@ import { angularStoriesGenerator } from './stories';
// which is v9 while we are testing for the new v10 version
jest.mock('@nx/cypress/src/utils/cypress-version');
+// TODO(katerina): remove Cypress for Nx 18
+
describe('angularStories generator: applications', () => {
let tree: Tree;
const appName = 'test-app';
@@ -25,12 +27,12 @@ describe('angularStories generator: applications', () => {
});
});
- it('should generate stories file', async () => {
+ it('should generate stories file with interaction tests', async () => {
await angularStoriesGenerator(tree, { name: appName });
expect(
- tree.exists(`apps/${appName}/src/app/app.component.stories.ts`)
- ).toBeTruthy();
+ tree.read(`apps/${appName}/src/app/app.component.stories.ts`, 'utf-8')
+ ).toMatchSnapshot();
});
it('should generate stories file for scam component', async () => {
@@ -90,11 +92,10 @@ describe('angularStories generator: applications', () => {
});
expect(
- tree
- .read(
- `apps/${appName}/src/app/component-a/component-b/component-b.component.stories.ts`
- )
- .toString()
+ tree.read(
+ `apps/${appName}/src/app/component-a/component-b/component-b.component.stories.ts`,
+ 'utf-8'
+ )
).toMatchSnapshot();
expect(
tree.exists(
@@ -113,20 +114,10 @@ describe('angularStories generator: applications', () => {
await angularStoriesGenerator(tree, { name: appName });
expect(
- tree
- .read(`apps/${appName}/src/app/my-scam/my-scam.component.stories.ts`)
- .toString()
+ tree.read(
+ `apps/${appName}/src/app/my-scam/my-scam.component.stories.ts`,
+ 'utf-8'
+ )
).toMatchSnapshot();
});
-
- it('should generate cypress spec file', async () => {
- await angularStoriesGenerator(tree, {
- name: appName,
- generateCypressSpecs: true,
- });
-
- expect(
- tree.exists(`apps/${appName}-e2e/src/e2e/app.component.cy.ts`)
- ).toBeTruthy();
- });
});
diff --git a/packages/angular/src/generators/stories/stories-lib.spec.ts b/packages/angular/src/generators/stories/stories-lib.spec.ts
index ec1008480fbc2e..ea0195213302f7 100644
--- a/packages/angular/src/generators/stories/stories-lib.spec.ts
+++ b/packages/angular/src/generators/stories/stories-lib.spec.ts
@@ -2,7 +2,6 @@ import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import { Tree } from '@nx/devkit';
import { writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
-import { Linter } from '@nx/linter';
import { componentGenerator } from '../component/component';
import { librarySecondaryEntryPointGenerator } from '../library-secondary-entry-point/library-secondary-entry-point';
import { scamGenerator } from '../scam/scam';
@@ -15,6 +14,7 @@ import { angularStoriesGenerator } from './stories';
// need to mock cypress otherwise it'll use the nx installed version from package.json
// which is v9 while we are testing for the new v10 version
jest.mock('@nx/cypress/src/utils/cypress-version');
+// TODO(katerina): remove Cypress for Nx 18
describe('angularStories generator: libraries', () => {
const libName = 'test-ui-lib';
@@ -39,7 +39,6 @@ describe('angularStories generator: libraries', () => {
async () =>
await angularStoriesGenerator(tree, {
name: libName,
- generateCypressSpecs: false,
})
).not.toThrow();
});
@@ -47,13 +46,9 @@ describe('angularStories generator: libraries', () => {
describe('Stories for non-empty Angular library', () => {
let tree: Tree;
- let cypressProjectGenerator;
beforeEach(async () => {
tree = await createStorybookTestWorkspaceForLib(libName);
- cypressProjectGenerator = await (
- await import('@nx/storybook')
- ).cypressProjectGenerator;
});
it('should generate stories.ts files', async () => {
@@ -105,56 +100,11 @@ describe('angularStories generator: libraries', () => {
).toBeTruthy();
});
- it('should generate cypress spec files', async () => {
- await cypressProjectGenerator(tree, {
- linter: Linter.EsLint,
- name: libName,
- });
-
- await angularStoriesGenerator(tree, {
- name: libName,
- generateCypressSpecs: true,
- });
-
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/barrel-button/barrel-button.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/nested-button/nested-button.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/test-button/test-button.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/test-other/test-other.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.read(
- `apps/${libName}-e2e/src/e2e/test-button/test-button.component.cy.ts`,
- 'utf-8'
- )
- ).toMatchSnapshot();
- });
-
it('should run twice without errors', async () => {
- await cypressProjectGenerator(tree, {
- linter: Linter.EsLint,
- name: libName,
- });
-
try {
await angularStoriesGenerator(tree, { name: libName });
await angularStoriesGenerator(tree, {
name: libName,
- generateCypressSpecs: true,
});
} catch {
fail('Should not fail when running it twice.');
@@ -162,14 +112,8 @@ describe('angularStories generator: libraries', () => {
});
it('should handle modules with variable declarations rather than literals', async () => {
- await cypressProjectGenerator(tree, {
- linter: Linter.EsLint,
- name: libName,
- });
-
await angularStoriesGenerator(tree, {
name: libName,
- generateCypressSpecs: true,
});
expect(
@@ -182,27 +126,11 @@ describe('angularStories generator: libraries', () => {
`libs/${libName}/src/lib/variable-declare/variable-declare-view/variable-declare-view.component.stories.ts`
)
).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/variable-declare-button/variable-declare-button.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/variable-declare-view/variable-declare-view.component.cy.ts`
- )
- ).toBeTruthy();
});
it('should handle modules with where components are spread into the declarations array', async () => {
- await cypressProjectGenerator(tree, {
- linter: Linter.EsLint,
- name: libName,
- });
-
await angularStoriesGenerator(tree, {
name: libName,
- generateCypressSpecs: true,
});
expect(
@@ -220,33 +148,11 @@ describe('angularStories generator: libraries', () => {
`libs/${libName}/src/lib/variable-spread-declare/variable-spread-declare-view/variable-spread-declare-view.component.stories.ts`
)
).toBeTruthy();
-
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/variable-spread-declare-button/variable-spread-declare-button.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/variable-spread-declare-view/variable-spread-declare-view.component.cy.ts`
- )
- ).toBeTruthy();
- expect(
- tree.exists(
- `apps/${libName}-e2e/src/e2e/variable-spread-declare-anotherview/variable-spread-declare-anotherview.component.cy.ts`
- )
- ).toBeTruthy();
});
it('should handle modules using static members for declarations rather than literals', async () => {
- await cypressProjectGenerator(tree, {
- linter: Linter.EsLint,
- name: libName,
- });
-
await angularStoriesGenerator(tree, {
name: libName,
- generateCypressSpecs: true,
});
expect(
@@ -259,12 +165,6 @@ describe('angularStories generator: libraries', () => {
`libs/${libName}/src/lib/static-member-declarations/cmp2/cmp2.component.stories.ts`
)
).toBeTruthy();
- expect(
- tree.exists(`apps/${libName}-e2e/src/e2e/cmp1/cmp1.component.cy.ts`)
- ).toBeTruthy();
- expect(
- tree.exists(`apps/${libName}-e2e/src/e2e/cmp2/cmp2.component.cy.ts`)
- ).toBeTruthy();
});
it('should generate stories file for scam component', async () => {
diff --git a/packages/angular/src/generators/stories/stories.ts b/packages/angular/src/generators/stories/stories.ts
index 3d1370bdda8b22..c8ee6aa93264ea 100644
--- a/packages/angular/src/generators/stories/stories.ts
+++ b/packages/angular/src/generators/stories/stories.ts
@@ -1,4 +1,14 @@
-import { formatFiles, joinPathFragments, logger, Tree } from '@nx/devkit';
+import {
+ addDependenciesToPackageJson,
+ ensurePackage,
+ formatFiles,
+ GeneratorCallback,
+ joinPathFragments,
+ logger,
+ readProjectConfiguration,
+ runTasksInSerial,
+ Tree,
+} from '@nx/devkit';
import componentCypressSpecGenerator from '../component-cypress-spec/component-cypress-spec';
import componentStoryGenerator from '../component-story/component-story';
import type { ComponentInfo } from '../utils/storybook-ast/component-info';
@@ -11,11 +21,12 @@ import { getE2EProject } from './lib/get-e2e-project';
import { getModuleFilePaths } from '../utils/storybook-ast/module-info';
import type { StoriesGeneratorOptions } from './schema';
import minimatch = require('minimatch');
+import { nxVersion } from '../../utils/versions';
export async function angularStoriesGenerator(
tree: Tree,
options: StoriesGeneratorOptions
-): Promise {
+): Promise {
const e2eProjectName = options.cypressProject ?? `${options.name}-e2e`;
const e2eProject = getE2EProject(tree, e2eProjectName);
const entryPoints = getProjectEntryPoints(tree, options.name);
@@ -59,6 +70,7 @@ export async function angularStoriesGenerator(
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName,
+ interactionTests: options.interactionTests ?? true,
skipFormat: true,
});
@@ -75,10 +87,24 @@ export async function angularStoriesGenerator(
});
}
}
+ const tasks: GeneratorCallback[] = [];
+
+ if (options.interactionTests) {
+ const { interactionTestsDependencies, addInteractionsInAddons } =
+ ensurePackage('@nx/storybook', nxVersion);
+
+ const projectConfiguration = readProjectConfiguration(tree, options.name);
+ addInteractionsInAddons(tree, projectConfiguration);
+
+ tasks.push(
+ addDependenciesToPackageJson(tree, {}, interactionTestsDependencies())
+ );
+ }
if (!options.skipFormat) {
await formatFiles(tree);
}
+ return runTasksInSerial(...tasks);
}
export default angularStoriesGenerator;
diff --git a/packages/angular/src/generators/storybook-configuration/__snapshots__/storybook-configuration.spec.ts.snap b/packages/angular/src/generators/storybook-configuration/__snapshots__/storybook-configuration.spec.ts.snap
index 88398ee8f08a46..4d1f9c5e47ae1a 100644
--- a/packages/angular/src/generators/storybook-configuration/__snapshots__/storybook-configuration.spec.ts.snap
+++ b/packages/angular/src/generators/storybook-configuration/__snapshots__/storybook-configuration.spec.ts.snap
@@ -1,9 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`StorybookConfiguration generator should configure everything at once - and interaction tests too 1`] = `
+"import type { Meta, StoryObj } from '@storybook/angular';
+import { TestButtonComponent } from './test-button.component';
+
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
+ component: TestButtonComponent,
+ title: 'TestButtonComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {
+ buttonType: 'button',
+ style: 'default',
+ age: 0,
+ isOn: false,
+ },
+};
+
+export const Heading: Story = {
+ args: {
+ buttonType: 'button',
+ style: 'default',
+ age: 0,
+ isOn: false,
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/test-button works!/gi)).toBeTruthy();
+ },
+};
+"
+`;
+
+exports[`StorybookConfiguration generator should configure everything at once - and interaction tests too 2`] = `
+"import type { Meta, StoryObj } from '@storybook/angular';
+import { TestOtherComponent } from './test-other.component';
+
+import { within } from '@storybook/testing-library';
+import { expect } from '@storybook/jest';
+
+const meta: Meta = {
+ component: TestOtherComponent,
+ title: 'TestOtherComponent',
+};
+export default meta;
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
+
+export const Heading: Story = {
+ args: {},
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText(/test-other works!/gi)).toBeTruthy();
+ },
+};
+"
+`;
+
exports[`StorybookConfiguration generator should configure storybook to use webpack 5 1`] = `
-"const config = {
+"import type { StorybookConfig } from '@storybook/angular';
+
+const config: StorybookConfig = {
stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
- addons: ['@storybook/addon-essentials'],
+ addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/angular',
options: {},
@@ -26,33 +94,12 @@ exports[`StorybookConfiguration generator should generate in the correct folder
".prettierignore",
".prettierrc",
"apps/.gitignore",
- "apps/one/two/test-ui-lib-e2e/.eslintrc.json",
- "apps/one/two/test-ui-lib-e2e/cypress.config.ts",
- "apps/one/two/test-ui-lib-e2e/project.json",
- "apps/one/two/test-ui-lib-e2e/src/e2e/barrel-button/barrel-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/cmp1/cmp1.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/cmp2/cmp2.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/nested-button/nested-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/secondary-entry-point/secondary-button/secondary-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/secondary-entry-point/secondary-standalone/secondary-standalone.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/standalone/standalone.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/test-button/test-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/test-other/test-other.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/variable-declare-button/variable-declare-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/variable-declare-view/variable-declare-view.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/variable-spread-declare-anotherview/variable-spread-declare-anotherview.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/variable-spread-declare-button/variable-spread-declare-button.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/e2e/variable-spread-declare-view/variable-spread-declare-view.component.cy.ts",
- "apps/one/two/test-ui-lib-e2e/src/fixtures/example.json",
- "apps/one/two/test-ui-lib-e2e/src/support/commands.ts",
- "apps/one/two/test-ui-lib-e2e/src/support/e2e.ts",
- "apps/one/two/test-ui-lib-e2e/tsconfig.json",
"jest.config.ts",
"jest.preset.js",
"libs/.gitignore",
"libs/test-ui-lib/.eslintrc.json",
- "libs/test-ui-lib/.storybook/main.js",
- "libs/test-ui-lib/.storybook/preview.js",
+ "libs/test-ui-lib/.storybook/main.ts",
+ "libs/test-ui-lib/.storybook/preview.ts",
"libs/test-ui-lib/.storybook/tsconfig.json",
"libs/test-ui-lib/jest.config.ts",
"libs/test-ui-lib/package.json",
@@ -158,33 +205,12 @@ exports[`StorybookConfiguration generator should generate the right files 1`] =
".prettierignore",
".prettierrc",
"apps/.gitignore",
- "apps/test-ui-lib-e2e/.eslintrc.json",
- "apps/test-ui-lib-e2e/cypress.config.ts",
- "apps/test-ui-lib-e2e/project.json",
- "apps/test-ui-lib-e2e/src/e2e/barrel-button/barrel-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/cmp1/cmp1.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/cmp2/cmp2.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/nested-button/nested-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/secondary-entry-point/secondary-button/secondary-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/secondary-entry-point/secondary-standalone/secondary-standalone.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/standalone/standalone.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/test-button/test-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/test-other/test-other.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/variable-declare-button/variable-declare-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/variable-declare-view/variable-declare-view.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/variable-spread-declare-anotherview/variable-spread-declare-anotherview.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/variable-spread-declare-button/variable-spread-declare-button.component.cy.ts",
- "apps/test-ui-lib-e2e/src/e2e/variable-spread-declare-view/variable-spread-declare-view.component.cy.ts",
- "apps/test-ui-lib-e2e/src/fixtures/example.json",
- "apps/test-ui-lib-e2e/src/support/commands.ts",
- "apps/test-ui-lib-e2e/src/support/e2e.ts",
- "apps/test-ui-lib-e2e/tsconfig.json",
"jest.config.ts",
"jest.preset.js",
"libs/.gitignore",
"libs/test-ui-lib/.eslintrc.json",
- "libs/test-ui-lib/.storybook/main.js",
- "libs/test-ui-lib/.storybook/preview.js",
+ "libs/test-ui-lib/.storybook/main.ts",
+ "libs/test-ui-lib/.storybook/preview.ts",
"libs/test-ui-lib/.storybook/tsconfig.json",
"libs/test-ui-lib/jest.config.ts",
"libs/test-ui-lib/package.json",
diff --git a/packages/angular/src/generators/storybook-configuration/lib/generate-stories.ts b/packages/angular/src/generators/storybook-configuration/lib/generate-stories.ts
index de21bbcc90ee3c..80db75de6d2556 100644
--- a/packages/angular/src/generators/storybook-configuration/lib/generate-stories.ts
+++ b/packages/angular/src/generators/storybook-configuration/lib/generate-stories.ts
@@ -21,6 +21,7 @@ export async function generateStories(
options.configureCypress && options.generateCypressSpecs,
cypressProject: e2eProjectName,
ignorePaths: options.ignorePaths,
+ interactionTests: options.interactionTests,
skipFormat: true,
});
}
diff --git a/packages/angular/src/generators/storybook-configuration/schema.d.ts b/packages/angular/src/generators/storybook-configuration/schema.d.ts
index 9c8867f4627a62..37717879659150 100644
--- a/packages/angular/src/generators/storybook-configuration/schema.d.ts
+++ b/packages/angular/src/generators/storybook-configuration/schema.d.ts
@@ -1,9 +1,9 @@
import type { Linter } from '@nx/linter';
export interface StorybookConfigurationOptions {
- configureCypress: boolean;
+ configureCypress?: boolean;
configureStaticServe?: boolean;
- generateCypressSpecs: boolean;
+ generateCypressSpecs?: boolean;
generateStories: boolean;
linter: Linter;
name: string;
diff --git a/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts b/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts
index cc4c2a8e40068d..ac1757a2a28f66 100644
--- a/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts
+++ b/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts
@@ -44,31 +44,16 @@ describe('StorybookConfiguration generator', () => {
jest.resetModules();
});
- it('should throw when generateCypressSpecs is true and generateStories is false', async () => {
- await expect(
- storybookConfigurationGenerator(tree, {
- name: libName,
- generateCypressSpecs: true,
- generateStories: false,
- })
- ).rejects.toThrow(
- 'Cannot set generateCypressSpecs to true when generateStories is set to false.'
- );
- });
-
it('should only configure storybook', async () => {
await storybookConfigurationGenerator(tree, {
name: libName,
- configureCypress: false,
- generateCypressSpecs: false,
generateStories: false,
});
- expect(tree.exists('libs/test-ui-lib/.storybook/main.js')).toBeTruthy();
+ expect(tree.exists('libs/test-ui-lib/.storybook/main.ts')).toBeTruthy();
expect(
tree.exists('libs/test-ui-lib/.storybook/tsconfig.json')
).toBeTruthy();
- expect(tree.exists('apps/test-ui-lib-e2e/cypress.config.ts')).toBeFalsy();
expect(
tree.exists(
'libs/test-ui-lib/src/lib/test-button/test-button.component.stories.ts'
@@ -79,65 +64,42 @@ describe('StorybookConfiguration generator', () => {
'libs/test-ui-lib/src/lib/test-other/test-other.component.stories.ts'
)
).toBeFalsy();
- expect(
- tree.exists(
- 'apps/test-ui-lib-e2e/src/integration/test-button/test-button.component.spec.ts'
- )
- ).toBeFalsy();
- expect(
- tree.exists(
- 'apps/test-ui-lib-e2e/src/integration/test-other/test-other.component.spec.ts'
- )
- ).toBeFalsy();
});
it('should configure storybook to use webpack 5', async () => {
await storybookConfigurationGenerator(tree, {
name: libName,
- configureCypress: false,
- generateCypressSpecs: false,
generateStories: false,
linter: Linter.None,
});
expect(
- tree.read('libs/test-ui-lib/.storybook/main.js', 'utf-8')
+ tree.read('libs/test-ui-lib/.storybook/main.ts', 'utf-8')
).toMatchSnapshot();
});
- it('should configure everything at once', async () => {
+ it('should configure everything at once - and interaction tests too', async () => {
await storybookConfigurationGenerator(tree, {
name: libName,
- configureCypress: true,
- generateCypressSpecs: true,
generateStories: true,
});
- expect(tree.exists('libs/test-ui-lib/.storybook/main.js')).toBeTruthy();
+ expect(tree.exists('libs/test-ui-lib/.storybook/main.ts')).toBeTruthy();
expect(
tree.exists('libs/test-ui-lib/.storybook/tsconfig.json')
).toBeTruthy();
- expect(tree.exists('apps/test-ui-lib-e2e/cypress.config.ts')).toBeTruthy();
- expect(
- tree.exists(
- 'libs/test-ui-lib/src/lib/test-button/test-button.component.stories.ts'
- )
- ).toBeTruthy();
expect(
- tree.exists(
- 'libs/test-ui-lib/src/lib/test-other/test-other.component.stories.ts'
+ tree.read(
+ 'libs/test-ui-lib/src/lib/test-button/test-button.component.stories.ts',
+ 'utf-8'
)
- ).toBeTruthy();
- expect(
- tree.exists(
- 'apps/test-ui-lib-e2e/src/e2e/test-button/test-button.component.cy.ts'
- )
- ).toBeTruthy();
+ ).toMatchSnapshot();
expect(
- tree.exists(
- 'apps/test-ui-lib-e2e/src/e2e/test-other/test-other.component.cy.ts'
+ tree.read(
+ 'libs/test-ui-lib/src/lib/test-other/test-other.component.stories.ts',
+ 'utf-8'
)
- ).toBeTruthy();
+ ).toMatchSnapshot();
});
it('should generate the right files', async () => {
@@ -171,8 +133,6 @@ describe('StorybookConfiguration generator', () => {
await storybookConfigurationGenerator(tree, {
name: libName,
- configureCypress: true,
- generateCypressSpecs: true,
generateStories: true,
});
@@ -210,10 +170,7 @@ describe('StorybookConfiguration generator', () => {
await storybookConfigurationGenerator(tree, {
name: libName,
- configureCypress: true,
- generateCypressSpecs: true,
generateStories: true,
- cypressDirectory: 'one/two',
});
expect(listFiles(tree)).toMatchSnapshot();
diff --git a/packages/angular/src/generators/storybook-configuration/storybook-configuration.ts b/packages/angular/src/generators/storybook-configuration/storybook-configuration.ts
index 6164f0215af50a..3bc83e2cc9da00 100644
--- a/packages/angular/src/generators/storybook-configuration/storybook-configuration.ts
+++ b/packages/angular/src/generators/storybook-configuration/storybook-configuration.ts
@@ -5,6 +5,7 @@ import { generateStorybookConfiguration } from './lib/generate-storybook-configu
import { validateOptions } from './lib/validate-options';
import type { StorybookConfigurationOptions } from './schema';
+// TODO(katerina): remove Cypress for Nx 18
export async function storybookConfigurationGenerator(
tree: Tree,
options: StorybookConfigurationOptions
@@ -14,11 +15,19 @@ export async function storybookConfigurationGenerator(
const storybookGeneratorInstallTask = await generateStorybookConfiguration(
tree,
- options
+ {
+ ...options,
+ interactionTests: options.interactionTests ?? true, // default is true
+ tsConfiguration: options.tsConfiguration ?? true, // default is true
+ }
);
if (options.generateStories) {
- await generateStories(tree, { ...options, skipFormat: true });
+ await generateStories(tree, {
+ ...options,
+ interactionTests: options.interactionTests ?? true,
+ skipFormat: true,
+ });
}
if (!options.skipFormat) {
diff --git a/packages/angular/src/generators/utils/storybook-ast/storybook-inputs.ts b/packages/angular/src/generators/utils/storybook-ast/storybook-inputs.ts
index fb9ecb2dd1a31f..35bd7575f9f03e 100644
--- a/packages/angular/src/generators/utils/storybook-ast/storybook-inputs.ts
+++ b/packages/angular/src/generators/utils/storybook-ast/storybook-inputs.ts
@@ -6,11 +6,11 @@ import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'
let tsModule: typeof import('typescript');
-export type KnobType = 'text' | 'boolean' | 'number' | 'select';
+export type ArgType = 'text' | 'boolean' | 'number' | 'select';
export interface InputDescriptor {
name: string;
- type: KnobType;
+ type: ArgType;
defaultValue?: string;
}
@@ -62,7 +62,7 @@ export function getComponentProps(
: node.name.getText()
: node.name.getText();
- const type = getKnobType(node);
+ const type = getArgType(node);
const defaultValue = getArgsDefaultValueFn(node);
return {
@@ -76,27 +76,27 @@ export function getComponentProps(
return props;
}
-export function getKnobType(property: PropertyDeclaration): KnobType {
+export function getArgType(property: PropertyDeclaration): ArgType {
if (!tsModule) {
tsModule = ensureTypescript();
}
if (property.type) {
const typeName = property.type.getText();
- const typeNameToKnobType: Record = {
+ const typeNameToArgType: Record = {
string: 'text',
number: 'number',
boolean: 'boolean',
};
- return typeNameToKnobType[typeName] || 'text';
+ return typeNameToArgType[typeName] || 'text';
}
if (property.initializer) {
- const initializerKindToKnobType: Record = {
+ const initializerKindToArgType: Record = {
[tsModule.SyntaxKind.StringLiteral]: 'text',
[tsModule.SyntaxKind.NumericLiteral]: 'number',
[tsModule.SyntaxKind.TrueKeyword]: 'boolean',
[tsModule.SyntaxKind.FalseKeyword]: 'boolean',
};
- return initializerKindToKnobType[property.initializer.kind] || 'text';
+ return initializerKindToArgType[property.initializer.kind] || 'text';
}
return 'text';
}
diff --git a/packages/react/src/generators/component-story/__snapshots__/component-story.spec.ts.snap b/packages/react/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
index ef8678b685a41e..397b6af85630e2 100644
--- a/packages/react/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
+++ b/packages/react/src/generators/component-story/__snapshots__/component-story.spec.ts.snap
@@ -22,6 +22,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -52,6 +56,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -82,6 +90,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -112,6 +124,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -142,6 +158,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -172,6 +192,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -202,6 +226,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -232,6 +260,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -262,6 +294,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -292,6 +328,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -322,6 +362,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -352,6 +396,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -382,6 +430,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -412,6 +464,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -442,6 +498,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -472,6 +532,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -502,6 +566,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -532,6 +600,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -559,6 +631,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to One!/gi)).toBeTruthy();
@@ -586,6 +659,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Two!/gi)).toBeTruthy();
@@ -615,6 +689,9 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Three!/gi)).toBeTruthy();
@@ -648,6 +725,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -678,6 +759,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -705,6 +790,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
@@ -732,6 +818,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to TestUiLib!/gi)).toBeTruthy();
diff --git a/packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__ b/packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__
index 257b72934ad3b2..0045de48e0bb69 100644
--- a/packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__
+++ b/packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__
@@ -25,6 +25,9 @@ export const Primary = {
<% if ( interactionTests ) { %>
export const Heading: Story = {
+ args: {<% for (let prop of props) { %>
+ <%= prop.name %>: <%- prop.defaultValue %>,<% } %>
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to <%=componentName%>!/gi)).toBeTruthy();
diff --git a/packages/react/src/generators/stories/__snapshots__/stories.app.spec.ts.snap b/packages/react/src/generators/stories/__snapshots__/stories.app.spec.ts.snap
index 9c6abe28dbe8ab..cdbb8fc9ebd6b7 100644
--- a/packages/react/src/generators/stories/__snapshots__/stories.app.spec.ts.snap
+++ b/packages/react/src/generators/stories/__snapshots__/stories.app.spec.ts.snap
@@ -19,6 +19,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to NxWelcome!/gi)).toBeTruthy();
@@ -49,6 +50,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
diff --git a/packages/react/src/generators/stories/__snapshots__/stories.lib.spec.ts.snap b/packages/react/src/generators/stories/__snapshots__/stories.lib.spec.ts.snap
index 4b3b4afe271248..4606673a016f0b 100644
--- a/packages/react/src/generators/stories/__snapshots__/stories.lib.spec.ts.snap
+++ b/packages/react/src/generators/stories/__snapshots__/stories.lib.spec.ts.snap
@@ -19,6 +19,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to TestUiLib!/gi)).toBeTruthy();
@@ -49,6 +50,10 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {
+ name: '',
+ displayAge: false,
+ },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to Test!/gi)).toBeTruthy();
diff --git a/packages/react/src/generators/stories/stories.ts b/packages/react/src/generators/stories/stories.ts
index 15be2f42562592..27de3918d9f455 100644
--- a/packages/react/src/generators/stories/stories.ts
+++ b/packages/react/src/generators/stories/stories.ts
@@ -5,18 +5,23 @@ import {
getComponentNode,
} from '../../utils/ast-utils';
import {
+ addDependenciesToPackageJson,
convertNxGenerator,
+ ensurePackage,
formatFiles,
+ GeneratorCallback,
getProjects,
joinPathFragments,
logger,
ProjectConfiguration,
+ runTasksInSerial,
Tree,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { basename, join } from 'path';
import minimatch = require('minimatch');
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
+import { nxVersion } from '../../utils/versions';
let tsModule: typeof import('typescript');
@@ -87,13 +92,14 @@ export async function createAllStories(
projectName: string,
interactionTests: boolean,
js: boolean,
+ projects: Map,
+ projectConfiguration: ProjectConfiguration,
generateCypressSpecs?: boolean,
cypressProject?: string,
ignorePaths?: string[]
) {
const { isTheFileAStory } = await import('@nx/storybook/src/utils/utilities');
- const projects = getProjects(tree);
- const projectConfiguration = projects.get(projectName);
+
const { sourceRoot, root } = projectConfiguration;
let componentPaths: string[] = [];
@@ -164,19 +170,35 @@ export async function storiesGenerator(
host: Tree,
schema: StorybookStoriesSchema
) {
+ const projects = getProjects(host);
+ const projectConfiguration = projects.get(schema.project);
await createAllStories(
host,
schema.project,
schema.interactionTests ?? true,
schema.js,
+ projects,
+ projectConfiguration,
schema.generateCypressSpecs,
schema.cypressProject,
schema.ignorePaths
);
+ const tasks: GeneratorCallback[] = [];
+
+ if (schema.interactionTests) {
+ const { interactionTestsDependencies, addInteractionsInAddons } =
+ ensurePackage('@nx/storybook', nxVersion);
+ tasks.push(
+ addDependenciesToPackageJson(host, {}, interactionTestsDependencies())
+ );
+ addInteractionsInAddons(host, projectConfiguration);
+ }
+
if (!schema.skipFormat) {
await formatFiles(host);
}
+ return runTasksInSerial(...tasks);
}
export default storiesGenerator;
diff --git a/packages/react/src/generators/storybook-configuration/__snapshots__/configuration.spec.ts.snap b/packages/react/src/generators/storybook-configuration/__snapshots__/configuration.spec.ts.snap
index 54b8e3f57a168a..051b1a82796331 100644
--- a/packages/react/src/generators/storybook-configuration/__snapshots__/configuration.spec.ts.snap
+++ b/packages/react/src/generators/storybook-configuration/__snapshots__/configuration.spec.ts.snap
@@ -43,6 +43,7 @@ export const Primary = {
};
export const Heading: Story = {
+ args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText(/Welcome to MyComponent!/gi)).toBeTruthy();
diff --git a/packages/react/src/utils/component-props.ts b/packages/react/src/utils/component-props.ts
index c8c3bf8bd3c570..655be8b17d02d7 100644
--- a/packages/react/src/utils/component-props.ts
+++ b/packages/react/src/utils/component-props.ts
@@ -26,7 +26,18 @@ export function getArgsDefaultValue(property: ts.SyntaxKind): string {
export function getDefaultsForComponent(
sourceFile: ts.SourceFile,
cmpDeclaration: ts.Node
-) {
+): {
+ propsTypeName: string;
+ props: {
+ name: string;
+ defaultValue: any;
+ }[];
+ argTypes: {
+ name: string;
+ type: string;
+ actionText: string;
+ }[];
+} {
if (!tsModule) {
tsModule = ensureTypescript();
}
diff --git a/packages/storybook/index.ts b/packages/storybook/index.ts
index 731af7dde6584c..5ddc34d2cd395f 100644
--- a/packages/storybook/index.ts
+++ b/packages/storybook/index.ts
@@ -1,3 +1,7 @@
export { configurationGenerator } from './src/generators/configuration/configuration';
export { cypressProjectGenerator } from './src/generators/cypress-project/cypress-project';
export { storybookVersion } from './src/utils/versions';
+export {
+ interactionTestsDependencies,
+ addInteractionsInAddons,
+} from './src/generators/configuration/lib/interaction-testing.utils';
diff --git a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts
index 124d6560b67b5e..95b00b59e02632 100644
--- a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts
+++ b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts
@@ -82,7 +82,7 @@ describe('@nx/storybook:configuration for workspaces with Root project', () => {
}));
});
- it('should generate files for root app', async () => {
+ it('should generate files for root app - js for tsConfiguration: false', async () => {
await configurationGenerator(tree, {
name: 'web',
uiFramework: '@storybook/react-webpack5',
diff --git a/packages/storybook/src/generators/configuration/configuration.spec.ts b/packages/storybook/src/generators/configuration/configuration.spec.ts
index 681ddb8ec20b17..ae6ceb745cb8c9 100644
--- a/packages/storybook/src/generators/configuration/configuration.spec.ts
+++ b/packages/storybook/src/generators/configuration/configuration.spec.ts
@@ -56,11 +56,10 @@ describe('@nx/storybook:configuration for Storybook v7', () => {
}));
});
- it('should generate TypeScript Configuration files', async () => {
+ it('should generate TypeScript Configuration files by default', async () => {
await configurationGenerator(tree, {
name: 'test-ui-lib',
standaloneConfig: false,
- tsConfiguration: true,
uiFramework: '@storybook/angular',
});
const project = readProjectConfiguration(tree, 'test-ui-lib');
@@ -172,11 +171,10 @@ describe('@nx/storybook:configuration for Storybook v7', () => {
).toBeTruthy();
});
- it('should generate TS config for project if tsConfiguration true', async () => {
+ it('should generate TS config for project by default', async () => {
await configurationGenerator(tree, {
name: 'test-ui-lib',
standaloneConfig: false,
- tsConfiguration: true,
uiFramework: '@storybook/angular',
});
@@ -278,7 +276,6 @@ describe('@nx/storybook:configuration for Storybook v7', () => {
});
await configurationGenerator(tree, {
name: 'main-vite-ts',
- tsConfiguration: true,
uiFramework: '@storybook/react-vite',
});
await configurationGenerator(tree, {
diff --git a/packages/storybook/src/generators/configuration/configuration.ts b/packages/storybook/src/generators/configuration/configuration.ts
index 1465d010bece20..bd59dac312396c 100644
--- a/packages/storybook/src/generators/configuration/configuration.ts
+++ b/packages/storybook/src/generators/configuration/configuration.ts
@@ -37,12 +37,10 @@ import {
import {
coreJsVersion,
nxVersion,
- storybookJestVersion,
- storybookTestingLibraryVersion,
- storybookTestRunnerVersion,
storybookVersion,
tsNodeVersion,
} from '../../utils/versions';
+import { interactionTestsDependencies } from './lib/interaction-testing.utils';
export async function configurationGenerator(
tree: Tree,
@@ -174,7 +172,7 @@ export async function configurationGenerator(
}
}
- const devDeps = {};
+ let devDeps = {};
if (schema.tsConfiguration) {
devDeps['@storybook/core-common'] = storybookVersion;
@@ -182,10 +180,10 @@ export async function configurationGenerator(
}
if (schema.interactionTests) {
- devDeps['@storybook/test-runner'] = storybookTestRunnerVersion;
- devDeps['@storybook/addon-interactions'] = storybookVersion;
- devDeps['@storybook/testing-library'] = storybookTestingLibraryVersion;
- devDeps['@storybook/jest'] = storybookJestVersion;
+ devDeps = {
+ ...devDeps,
+ ...interactionTestsDependencies(),
+ };
}
if (schema.configureStaticServe) {
diff --git a/packages/storybook/src/generators/configuration/lib/__snapshots__/interaction-testing.utils.spec.ts.snap b/packages/storybook/src/generators/configuration/lib/__snapshots__/interaction-testing.utils.spec.ts.snap
new file mode 100644
index 00000000000000..15ce4a62e3b01d
--- /dev/null
+++ b/packages/storybook/src/generators/configuration/lib/__snapshots__/interaction-testing.utils.spec.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Helper functions for the Storybook 7 migration generator should add addon-interactions in main.ts if it does not exist 1`] = `
+"import type { StorybookConfig } from '@storybook/angular';
+
+ const config: StorybookConfig = {
+ stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
+ addons: ['@storybook/addon-interactions', '@storybook/addon-essentials'],
+ framework: {
+ name: '@storybook/angular',
+ options: {},
+ },
+ };
+
+ export default config;"
+`;
+
+exports[`Helper functions for the Storybook 7 migration generator should do nothing if addon-interactions already exists in main.ts 1`] = `
+"import type { StorybookConfig } from '@storybook/angular';
+
+ const config: StorybookConfig = {
+ stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
+ addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
+ framework: {
+ name: '@storybook/angular',
+ options: {},
+ },
+ };
+
+ export default config;"
+`;
diff --git a/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.spec.ts b/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.spec.ts
new file mode 100644
index 00000000000000..19762aeb9df2b3
--- /dev/null
+++ b/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.spec.ts
@@ -0,0 +1,50 @@
+import { Tree, joinPathFragments } from '@nx/devkit';
+import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
+import { addInteractionsInAddons } from './interaction-testing.utils';
+describe('Helper functions for the Storybook 7 migration generator', () => {
+ let tree: Tree;
+
+ beforeEach(async () => {
+ tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ });
+
+ it('should add addon-interactions in main.ts if it does not exist', () => {
+ tree.write(
+ `.storybook/main.ts`,
+ `import type { StorybookConfig } from '@storybook/angular';
+
+ const config: StorybookConfig = {
+ stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
+ addons: ['@storybook/addon-essentials'],
+ framework: {
+ name: '@storybook/angular',
+ options: {},
+ },
+ };
+
+ export default config;`
+ );
+ addInteractionsInAddons(tree, `.storybook/main.ts`);
+ expect(tree.read(`.storybook/main.ts`, 'utf-8')).toMatchSnapshot();
+ });
+
+ it('should do nothing if addon-interactions already exists in main.ts', () => {
+ tree.write(
+ `.storybook/main.ts`,
+ `import type { StorybookConfig } from '@storybook/angular';
+
+ const config: StorybookConfig = {
+ stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)'],
+ addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
+ framework: {
+ name: '@storybook/angular',
+ options: {},
+ },
+ };
+
+ export default config;`
+ );
+ addInteractionsInAddons(tree, `.storybook/main.ts`);
+ expect(tree.read(`.storybook/main.ts`, 'utf-8')).toMatchSnapshot();
+ });
+});
diff --git a/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.ts b/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.ts
new file mode 100644
index 00000000000000..da45bfa651cb8b
--- /dev/null
+++ b/packages/storybook/src/generators/configuration/lib/interaction-testing.utils.ts
@@ -0,0 +1,90 @@
+import {
+ applyChangesToString,
+ ChangeType,
+ ProjectConfiguration,
+ Tree,
+} from '@nx/devkit';
+import { tsquery } from '@phenomnomnominal/tsquery';
+import {
+ storybookJestVersion,
+ storybookTestingLibraryVersion,
+ storybookTestRunnerVersion,
+ storybookVersion,
+} from '../../../utils/versions';
+
+export function interactionTestsDependencies(): { [key: string]: string } {
+ return {
+ '@storybook/test-runner': storybookTestRunnerVersion,
+ '@storybook/addon-interactions': storybookVersion,
+ '@storybook/testing-library': storybookTestingLibraryVersion,
+ '@storybook/jest': storybookJestVersion,
+ };
+}
+
+export function addInteractionsInAddons(
+ tree: Tree,
+ projectConfig: ProjectConfiguration
+) {
+ const mainJsTsPath = getMainTsJsPath(tree, projectConfig);
+ if (mainJsTsPath) {
+ let mainJsTs = tree.read(mainJsTsPath, 'utf-8');
+ if (mainJsTs) {
+ const addonsArray = tsquery.query(
+ mainJsTs,
+ `PropertyAssignment:has(Identifier:has([name="addons"]))`
+ );
+ if (addonsArray?.length > 0) {
+ const addonsArrayHasAddonInteractions = tsquery.query(
+ addonsArray[0],
+ `StringLiteral:has([text="@storybook/addon-interactions"])`
+ );
+ if (addonsArrayHasAddonInteractions?.length > 0) {
+ return;
+ } else {
+ const arrayLiteralExpression = tsquery.query(
+ addonsArray[0],
+ `ArrayLiteralExpression`
+ )?.[0];
+ if (arrayLiteralExpression) {
+ // normally it should exist
+ mainJsTs = applyChangesToString(mainJsTs, [
+ {
+ type: ChangeType.Insert,
+ index: arrayLiteralExpression.getStart() + 1,
+ text: `'@storybook/addon-interactions', `,
+ },
+ ]);
+ tree.write(mainJsTsPath, mainJsTs);
+ }
+ }
+ } else {
+ // No addons array.
+ // Do nothing, because user may be importing stories in another project.
+ }
+ }
+ }
+}
+
+function getMainTsJsPath(
+ host: Tree,
+ projectConfig: ProjectConfiguration
+): string | undefined {
+ let mainJsTsPath: string | undefined = undefined;
+ Object.entries(projectConfig.targets).forEach(
+ ([_targetName, targetConfig]) => {
+ if (
+ targetConfig.executor === '@nx/storybook:storybook' ||
+ targetConfig.executor === '@storybook/angular:start-storybook'
+ ) {
+ const configDir = targetConfig.options?.configDir;
+ if (host.exists(`${configDir}/main.js`)) {
+ mainJsTsPath = `${configDir}/main.js`;
+ }
+ if (host.exists(`${configDir}/main.ts`)) {
+ mainJsTsPath = `${configDir}/main.ts`;
+ }
+ }
+ }
+ );
+ return mainJsTsPath;
+}