From b4e119f4f56a74d7f9661824a7fe5b76a6d9a17c Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Wed, 12 Jul 2023 14:13:35 +0300 Subject: [PATCH] feat(storybook): interaction testing story generator React --- .../react/generators/component-story.json | 6 ++++ .../packages/react/generators/stories.json | 13 ++++++-- .../component-story/component-story.spec.ts | 1 + .../component-story/component-story.ts | 22 +++++-------- .../__componentFileName__.stories.__fileExt__ | 32 ------------------ .../__componentFileName__.stories.jsx__tmpl__ | 29 ++++++++++++++++ .../__componentFileName__.stories.tsx__tmpl__ | 33 +++++++++++++++++++ .../generators/component-story/schema.json | 6 ++++ .../react/src/generators/stories/schema.json | 13 ++++++-- .../react/src/generators/stories/stories.ts | 10 ++++-- .../storybook-configuration/configuration.ts | 1 + 11 files changed, 111 insertions(+), 55 deletions(-) delete mode 100644 packages/react/src/generators/component-story/files/__componentFileName__.stories.__fileExt__ create mode 100644 packages/react/src/generators/component-story/files/jsx/__componentFileName__.stories.jsx__tmpl__ create mode 100644 packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__ diff --git a/docs/generated/packages/react/generators/component-story.json b/docs/generated/packages/react/generators/component-story.json index 1a959e4cac315e..5122138cb60da4 100644 --- a/docs/generated/packages/react/generators/component-story.json +++ b/docs/generated/packages/react/generators/component-story.json @@ -30,6 +30,12 @@ "type": "boolean", "default": false, "x-priority": "internal" + }, + "interactionTests": { + "type": "boolean", + "description": "Set up Storybook interaction tests.", + "default": true, + "x-priority": "important" } }, "required": ["project", "componentPath"], diff --git a/docs/generated/packages/react/generators/stories.json b/docs/generated/packages/react/generators/stories.json index 76138b57a15aae..480c7d55022516 100644 --- a/docs/generated/packages/react/generators/stories.json +++ b/docs/generated/packages/react/generators/stories.json @@ -20,12 +20,19 @@ "generateCypressSpecs": { "type": "boolean", "description": "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 Testing instead." }, "cypressProject": { "type": "string", - "description": "The Cypress project to generate the stories under. This is inferred from `project` by default." + "description": "The Cypress project to generate the stories under. This is inferred from `project` by default.", + "x-deprecated": "Please use Storybook Interaction Testing instead." + }, + "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 }, "js": { "type": "boolean", diff --git a/packages/react/src/generators/component-story/component-story.spec.ts b/packages/react/src/generators/component-story/component-story.spec.ts index 9a77bdd4dde4e4..3c5716182dad53 100644 --- a/packages/react/src/generators/component-story/component-story.spec.ts +++ b/packages/react/src/generators/component-story/component-story.spec.ts @@ -27,6 +27,7 @@ describe('react:component-story', () => { await componentStoryGenerator(appTree, { componentPath: 'lib/test-ui-lib.tsx', project: 'test-ui-lib', + interactionTests: true, }); } catch (e) { expect(e.message).toContain( diff --git a/packages/react/src/generators/component-story/component-story.ts b/packages/react/src/generators/component-story/component-story.ts index 6c1f494f962624..e8d1b929c6fa8d 100644 --- a/packages/react/src/generators/component-story/component-story.ts +++ b/packages/react/src/generators/component-story/component-story.ts @@ -20,12 +20,13 @@ let tsModule: typeof import('typescript'); export interface CreateComponentStoriesFileSchema { project: string; componentPath: string; + interactionTests?: boolean; skipFormat?: boolean; } export function createComponentStoriesFile( host: Tree, - { project, componentPath }: CreateComponentStoriesFileSchema + { project, componentPath, interactionTests }: CreateComponentStoriesFileSchema ) { if (!tsModule) { tsModule = ensureTypescript(); @@ -42,12 +43,6 @@ export function createComponentStoriesFile( const isPlainJs = componentFilePath.endsWith('.jsx') || componentFilePath.endsWith('.js'); - let fileExt = 'tsx'; - if (componentFilePath.endsWith('.jsx')) { - fileExt = 'jsx'; - } else if (componentFilePath.endsWith('.js')) { - fileExt = 'js'; - } const componentFileName = componentFilePath .slice(componentFilePath.lastIndexOf('/') + 1) @@ -81,8 +76,8 @@ export function createComponentStoriesFile( declaration, componentDirectory, name, + interactionTests, isPlainJs, - fileExt, componentNodes.length > 1 ); }); @@ -98,8 +93,8 @@ export function createComponentStoriesFile( cmpDeclaration, componentDirectory, name, - isPlainJs, - fileExt + interactionTests, + isPlainJs ); } } @@ -110,8 +105,8 @@ export function findPropsAndGenerateFile( cmpDeclaration: ts.Node, componentDirectory: string, name: string, + interactionTests: boolean, isPlainJs: boolean, - fileExt: string, fromNodeArray?: boolean ) { const { propsTypeName, props, argTypes } = getDefaultsForComponent( @@ -121,7 +116,7 @@ export function findPropsAndGenerateFile( generateFiles( host, - joinPathFragments(__dirname, './files'), + joinPathFragments(__dirname, `${'./files' + isPlainJs ? '/jsx' : '/tsx'}`), normalizePath(componentDirectory), { componentFileName: fromNodeArray @@ -132,8 +127,7 @@ export function findPropsAndGenerateFile( props, argTypes, componentName: (cmpDeclaration as any).name.text, - isPlainJs, - fileExt, + interactionTests, } ); } diff --git a/packages/react/src/generators/component-story/files/__componentFileName__.stories.__fileExt__ b/packages/react/src/generators/component-story/files/__componentFileName__.stories.__fileExt__ deleted file mode 100644 index 12b9ccd9b1f5cf..00000000000000 --- a/packages/react/src/generators/component-story/files/__componentFileName__.stories.__fileExt__ +++ /dev/null @@ -1,32 +0,0 @@ -<% if ( !isPlainJs ) { %>import type { Meta } from '@storybook/react';<% } %> -import<% if ( !isPlainJs ) { %> { <% } %> <%= componentName %> <% if ( !isPlainJs ) { %> } <% } %> from './<%= componentImportFileName %>'; - -<% if ( isPlainJs ) { %> -export default { - component: <%= componentName %>, - title: '<%= componentName %>',<% if ( argTypes && argTypes.length > 0 ) { %> - argTypes: {<% for (let argType of argTypes) { %> - <%= argType.name %>: { <%- argType.type %> : "<%- argType.actionText %>" },<% } %> -} - <% } %> -}; -<% } %> - - -<% if ( !isPlainJs ) { %> -const Story: Meta> = { - component: <%= componentName %>, - title: '<%= componentName %>',<% if ( argTypes && argTypes.length > 0 ) { %> - argTypes: {<% for (let argType of argTypes) { %> - <%= argType.name %>: { <%- argType.type %> : "<%- argType.actionText %>" },<% } %> -} - <% } %> -}; -export default Story; -<% } %> - -export const Primary = { - args: {<% for (let prop of props) { %> - <%= prop.name %>: <%- prop.defaultValue %>,<% } %> - }, -}; \ No newline at end of file diff --git a/packages/react/src/generators/component-story/files/jsx/__componentFileName__.stories.jsx__tmpl__ b/packages/react/src/generators/component-story/files/jsx/__componentFileName__.stories.jsx__tmpl__ new file mode 100644 index 00000000000000..5750a734eab0a4 --- /dev/null +++ b/packages/react/src/generators/component-story/files/jsx/__componentFileName__.stories.jsx__tmpl__ @@ -0,0 +1,29 @@ +import componentName from './<%= componentImportFileName %>'; +<% if ( interactionTests ) { %> +import { within } from '@storybook/testing-library'; +import { expect } from '@storybook/jest'; +<% } %> + +export default { + component: <%= componentName %>, + title: '<%= componentName %>',<% if ( argTypes && argTypes.length > 0 ) { %> + argTypes: {<% for (let argType of argTypes) { %> + <%= argType.name %>: { <%- argType.type %> : "<%- argType.actionText %>" },<% } %> +} + <% } %> +}; + +export const Primary = { + args: {<% for (let prop of props) { %> + <%= prop.name %>: <%- prop.defaultValue %>,<% } %> + }, +}; + +<% if ( interactionTests ) { %> +export const Heading: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(canvas.getByText(/Welcome to <%=componentName%>!/gi)).toBeTruthy(); + }, +}; +<% } %> \ No newline at end of file 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__ new file mode 100644 index 00000000000000..257b72934ad3b2 --- /dev/null +++ b/packages/react/src/generators/component-story/files/tsx/__componentFileName__.stories.tsx__tmpl__ @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { <%= componentName %> } from './<%= componentImportFileName %>'; +<% if ( interactionTests ) { %> +import { within } from '@storybook/testing-library'; +import { expect } from '@storybook/jest'; +<% } %> + +const meta: Meta> = { + component: <%= componentName %>, + title: '<%= componentName %>',<% if ( argTypes && argTypes.length > 0 ) { %> + argTypes: {<% for (let argType of argTypes) { %> + <%= argType.name %>: { <%- argType.type %> : "<%- argType.actionText %>" },<% } %> +} + <% } %> +}; +export default meta; +type Story = StoryObj>; + + +export const Primary = { + args: {<% for (let prop of props) { %> + <%= prop.name %>: <%- prop.defaultValue %>,<% } %> + }, +}; + +<% if ( interactionTests ) { %> +export const Heading: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(canvas.getByText(/Welcome to <%=componentName%>!/gi)).toBeTruthy(); + }, +}; +<% } %> diff --git a/packages/react/src/generators/component-story/schema.json b/packages/react/src/generators/component-story/schema.json index df34160896a388..d7e48a93d92c4e 100644 --- a/packages/react/src/generators/component-story/schema.json +++ b/packages/react/src/generators/component-story/schema.json @@ -30,6 +30,12 @@ "type": "boolean", "default": false, "x-priority": "internal" + }, + "interactionTests": { + "type": "boolean", + "description": "Set up Storybook interaction tests.", + "default": true, + "x-priority": "important" } }, "required": ["project", "componentPath"] diff --git a/packages/react/src/generators/stories/schema.json b/packages/react/src/generators/stories/schema.json index 3fdd31177510f5..68e974fc93caa7 100644 --- a/packages/react/src/generators/stories/schema.json +++ b/packages/react/src/generators/stories/schema.json @@ -20,12 +20,19 @@ "generateCypressSpecs": { "type": "boolean", "description": "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 Testing instead." }, "cypressProject": { "type": "string", - "description": "The Cypress project to generate the stories under. This is inferred from `project` by default." + "description": "The Cypress project to generate the stories under. This is inferred from `project` by default.", + "x-deprecated": "Please use Storybook Interaction Testing instead." + }, + "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 }, "js": { "type": "boolean", diff --git a/packages/react/src/generators/stories/stories.ts b/packages/react/src/generators/stories/stories.ts index 30afa8be0d42b6..5e0f1edc7f4bd1 100644 --- a/packages/react/src/generators/stories/stories.ts +++ b/packages/react/src/generators/stories/stories.ts @@ -22,9 +22,10 @@ let tsModule: typeof import('typescript'); export interface StorybookStoriesSchema { project: string; - generateCypressSpecs: boolean; + interactionTests: boolean; js?: boolean; cypressProject?: string; + generateCypressSpecs?: boolean; ignorePaths?: string[]; skipFormat?: boolean; } @@ -84,8 +85,9 @@ export function containsComponentDeclaration( export async function createAllStories( tree: Tree, projectName: string, - generateCypressSpecs: boolean, + interactionTests: boolean, js: boolean, + generateCypressSpecs?: boolean, cypressProject?: string, ignorePaths?: string[] ) { @@ -142,6 +144,7 @@ export async function createAllStories( componentPath: relativeCmpDir, project: projectName, skipFormat: true, + interactionTests, }); if (generateCypressSpecs && e2eProject) { @@ -164,8 +167,9 @@ export async function storiesGenerator( await createAllStories( host, schema.project, - schema.generateCypressSpecs, + schema.interactionTests, schema.js, + schema.generateCypressSpecs, schema.cypressProject, schema.ignorePaths ); diff --git a/packages/react/src/generators/storybook-configuration/configuration.ts b/packages/react/src/generators/storybook-configuration/configuration.ts index 16d9f81d33aa93..30774ea2bb8104 100644 --- a/packages/react/src/generators/storybook-configuration/configuration.ts +++ b/packages/react/src/generators/storybook-configuration/configuration.ts @@ -29,6 +29,7 @@ async function generateStories(host: Tree, schema: StorybookConfigureSchema) { cypressProject, ignorePaths: schema.ignorePaths, skipFormat: true, + interactionTests: schema.interactionTests, }); }