Skip to content

Commit

Permalink
feat(storybook): interaction tests generators for angular
Browse files Browse the repository at this point in the history
  • Loading branch information
mandarini committed Jul 19, 2023
1 parent b1a9ae7 commit 1e0079e
Show file tree
Hide file tree
Showing 35 changed files with 688 additions and 423 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 10 additions & 3 deletions docs/generated/packages/angular/generators/stories.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
81 changes: 1 addition & 80 deletions e2e/storybook-angular/src/storybook-angular.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ 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');
beforeAll(() => {
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`
);
});

Expand All @@ -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`
),
`
<button
class="my-btn"
[ngStyle]="{ backgroundColor: color }"
[disabled]="disabled"
>
{{ text }}
</button>
`
);
}
Original file line number Diff line number Diff line change
@@ -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<TestButtonComponent> = {
component: TestButtonComponent,
} as Meta<TestButtonComponent>;
title: 'TestButtonComponent',
};
export default meta;
type Story = StoryObj<TestButtonComponent>;
export const Primary = {
render: (args: TestButtonComponent) => ({
props: args,
}),
export const Primary: Story = {
args: {
buttonType: 'button',
style: 'default',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: '',
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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 %>,<% } %>
},
};
};

<% 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();
},
};
<% } %>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ComponentStoryGeneratorOptions {
projectPath: string;
interactionTests?: boolean;
componentName: string;
componentPath: string;
componentFileName: string;
Expand Down
7 changes: 7 additions & 0 deletions packages/angular/src/generators/component-story/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MyScamComponent> = {
component: MyScamComponent,
} as Meta<MyScamComponent>;
title: 'MyScamComponent',
};
export default meta;
type Story = StoryObj<MyScamComponent>;
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<AppComponent> = {
component: AppComponent,
title: 'AppComponent',
};
export default meta;
type Story = StoryObj<AppComponent>;
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<ComponentBComponent> = {
component: ComponentBComponent,
} as Meta<ComponentBComponent>;
title: 'ComponentBComponent',
};
export default meta;
type Story = StoryObj<ComponentBComponent>;
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();
},
};
"
`;
Loading

0 comments on commit 1e0079e

Please sign in to comment.