From 3086273ecae467cf0aa8598832e9fd61d3c00041 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Tue, 21 Mar 2023 19:30:07 +0200 Subject: [PATCH] chore(storybook): added storybook 7 testing suite --- .github/workflows/e2e-matrix.yml | 3 + .github/workflows/e2e-windows.yml | 3 + docs/shared/mental-model/large-tasks.json | 24 ++ e2e/storybook-7/jest.config.ts | 11 + e2e/storybook-7/project.json | 11 + .../src/storybook-7-nested.test.ts | 134 ++++++++++++ e2e/storybook-7/src/storybook-7.test.ts | 207 ++++++++++++++++++ e2e/storybook-7/tsconfig.json | 13 ++ e2e/storybook-7/tsconfig.spec.json | 20 ++ e2e/storybook/src/storybook.test.ts | 35 +++ 10 files changed, 461 insertions(+) create mode 100644 e2e/storybook-7/jest.config.ts create mode 100644 e2e/storybook-7/project.json create mode 100644 e2e/storybook-7/src/storybook-7-nested.test.ts create mode 100644 e2e/storybook-7/src/storybook-7.test.ts create mode 100644 e2e/storybook-7/tsconfig.json create mode 100644 e2e/storybook-7/tsconfig.spec.json diff --git a/.github/workflows/e2e-matrix.yml b/.github/workflows/e2e-matrix.yml index 65ccc28df08fc6..0f90bb427d8214 100644 --- a/.github/workflows/e2e-matrix.yml +++ b/.github/workflows/e2e-matrix.yml @@ -81,6 +81,7 @@ jobs: - e2e-web - e2e-rollup - e2e-storybook + - e2e-storybook-7 - e2e-vite - e2e-webpack - e2e-workspace-create @@ -140,6 +141,8 @@ jobs: codeowners: 'S04SJ6PL98X' - project: e2e-storybook codeowners: 'S04SVQ8H0G5' + - project: e2e-storybook-7 + codeowners: 'S04SVQ8H0G5' - project: e2e-vite codeowners: 'S04SJ6PL98X' - project: e2e-webpack diff --git a/.github/workflows/e2e-windows.yml b/.github/workflows/e2e-windows.yml index adbaa9c708b5fc..86d8f660da6b70 100644 --- a/.github/workflows/e2e-windows.yml +++ b/.github/workflows/e2e-windows.yml @@ -69,6 +69,7 @@ jobs: - e2e-web - e2e-rollup - e2e-storybook + - e2e-storybook-7 - e2e-vite - e2e-webpack - e2e-workspace-create @@ -116,6 +117,8 @@ jobs: - project: e2e-rollup codeowners: 'S04SJ6PL98X' - project: e2e-storybook + codeowners: 'S04SVQ8H0G5' + - project: e2e-storybook-7 codeowners: 'S04SVQ8H0G5' - project: e2e-vite codeowners: 'S04SJ6PL98X' diff --git a/docs/shared/mental-model/large-tasks.json b/docs/shared/mental-model/large-tasks.json index 0c68713cf7d180..02f25f45c5745f 100644 --- a/docs/shared/mental-model/large-tasks.json +++ b/docs/shared/mental-model/large-tasks.json @@ -36176,6 +36176,30 @@ }, "dependencies": { "e2e-storybook:run-e2e-tests": [] } }, + "e2e-storybook-7:e2e": { + "roots": ["e2e-storybook-7:e2e"], + "tasks": { + "e2e-storybook-7:e2e": { + "id": "e2e-storybook-7:e2e", + "target": { "project": "e2e-storybook-7", "target": "e2e" }, + "projectRoot": "e2e/storybook-7", + "overrides": {} + } + }, + "dependencies": { "e2e-storybook-7:e2e": [] } + }, + "e2e-storybook-7:run-e2e-tests": { + "roots": ["e2e-storybook-7:run-e2e-tests"], + "tasks": { + "e2e-storybook-7:run-e2e-tests": { + "id": "e2e-storybook-7:run-e2e-tests", + "target": { "project": "e2e-storybook-7", "target": "run-e2e-tests" }, + "projectRoot": "e2e/storybook-7", + "overrides": {} + } + }, + "dependencies": { "e2e-storybook-7:run-e2e-tests": [] } + }, "nx-dev:build": { "roots": ["graph-client:build-base:release"], "tasks": { diff --git a/e2e/storybook-7/jest.config.ts b/e2e/storybook-7/jest.config.ts new file mode 100644 index 00000000000000..05c2bb936fa459 --- /dev/null +++ b/e2e/storybook-7/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + transform: { + '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + maxWorkers: 1, + globals: {}, + displayName: 'e2e-storybook-7', + preset: '../../jest.preset.js', +}; diff --git a/e2e/storybook-7/project.json b/e2e/storybook-7/project.json new file mode 100644 index 00000000000000..24e22ed7f16ff2 --- /dev/null +++ b/e2e/storybook-7/project.json @@ -0,0 +1,11 @@ +{ + "name": "e2e-storybook-7", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/storybook-7", + "projectType": "application", + "targets": { + "e2e": {}, + "run-e2e-tests": {} + }, + "implicitDependencies": ["storybook"] +} diff --git a/e2e/storybook-7/src/storybook-7-nested.test.ts b/e2e/storybook-7/src/storybook-7-nested.test.ts new file mode 100644 index 00000000000000..d70e724ab19af6 --- /dev/null +++ b/e2e/storybook-7/src/storybook-7-nested.test.ts @@ -0,0 +1,134 @@ +import { + checkFilesExist, + cleanupProject, + killPorts, + readJson, + runCLI, + runCommandUntil, + runCreateWorkspace, + tmpProjPath, + uniq, +} from '@nrwl/e2e/utils'; +import { writeFileSync } from 'fs'; + +describe('Storybook generators and executors for standalone workspaces - using React + Vite', () => { + const wsName = uniq('react'); + const appName = uniq('app'); + + beforeAll(() => { + // create a workspace with a single react app at the root + runCreateWorkspace(wsName, { + preset: 'react-standalone', + appName, + style: 'css', + bundler: 'vite', + }); + + runCLI( + `generate @nrwl/react:storybook-configuration ${appName} --storybook7Configuration --generateStories --no-interactive` + ); + + runCLI(`report`); + }); + + afterAll(() => { + cleanupProject(); + }); + + describe('Storybook generated files', () => { + it('should generate storybook files', () => { + checkFilesExist( + '.storybook/main.js', + '.storybook/preview.js', + '.storybook/tsconfig.json' + ); + }); + + it('should edit root tsconfig.json', () => { + const tsconfig = readJson(`tsconfig.json`); + expect(tsconfig['ts-node']?.compilerOptions?.module).toEqual('commonjs'); + }); + }); + + describe('serve storybook', () => { + afterEach(() => killPorts()); + + it('should serve a React based Storybook setup that uses Vite', async () => { + const p = await runCommandUntil(`run ${appName}:storybook`, (output) => { + return /Storybook.*started/gi.test(output); + }); + p.kill(); + }, 60000); + }); + + describe('build storybook', () => { + it('should build a React based storybook that uses Vite', () => { + runCLI(`run ${appName}:build-storybook --verbose`); + checkFilesExist(`dist/storybook/${appName}/index.html`); + }, 60000); + + // This test makes sure path resolution works + it('should build a React based storybook that references another lib and uses Vite', () => { + const reactLib = uniq('test-lib-react'); + runCLI(`generate @nrwl/react:lib ${reactLib} --no-interactive`); + runCLI( + `generate @nrwl/react:storybook-configuration ${reactLib} --storybook7Configuration --generateStories --no-interactive` + ); + const reactLib2 = uniq('test-lib-react'); + runCLI(`generate @nrwl/react:lib ${reactLib2} --no-interactive`); + // create a React component we can reference + writeFileSync( + tmpProjPath(`${reactLib2}/src/lib/mytestcmp.tsx`), + ` + import React from 'react'; + + /* eslint-disable-next-line */ + export interface MyTestCmpProps {} + + export const MyTestCmp = (props: MyTestCmpProps) => { + return ( +
+

Welcome to test cmp!

+
+ ); + }; + + export default MyTestCmp; + ` + ); + // update index.ts and export it + writeFileSync( + tmpProjPath(`${reactLib2}/src/index.ts`), + ` + export * from './lib/mytestcmp'; + ` + ); + + // create a story in the first lib to reference the cmp from the 2nd lib + writeFileSync( + tmpProjPath(`${reactLib}/src/lib/myteststory.stories.tsx`), + ` + import React from 'react'; + + import { MyTestCmp, MyTestCmpProps } from '@${wsName}/${reactLib2}'; + + export default { + component: MyTestCmp, + title: 'MyTestCmp', + }; + + export const primary = () => { + /* eslint-disable-next-line */ + const props: MyTestCmpProps = {}; + + return ; + }; + ` + ); + + // build React lib + runCLI(`run ${reactLib}:build-storybook --verbose`); + checkFilesExist(`dist/storybook/${reactLib}/index.html`); + }, 60000); + }); +}); diff --git a/e2e/storybook-7/src/storybook-7.test.ts b/e2e/storybook-7/src/storybook-7.test.ts new file mode 100644 index 00000000000000..b58ebef9d07060 --- /dev/null +++ b/e2e/storybook-7/src/storybook-7.test.ts @@ -0,0 +1,207 @@ +import { + checkFilesExist, + cleanupProject, + killPorts, + runCLI, + runCommandUntil, + tmpProjPath, + uniq, + newProject, + runCypressTests, +} from '@nrwl/e2e/utils'; +import { writeFileSync } from 'fs'; + +describe('Storybook generators and executors for monorepos', () => { + const reactStorybookLib = uniq('test-ui-lib-react'); + const angularStorybookLib = uniq('test-ui-lib'); + let proj; + beforeAll(() => { + proj = newProject(); + runCLI(`generate @nrwl/react:lib ${reactStorybookLib} --no-interactive`); + runCLI( + `generate @nrwl/react:storybook-configuration ${reactStorybookLib} --storybook7Configuration --generateStories --no-interactive --bundler=webpack` + ); + + createTestUILib(angularStorybookLib); + runCLI( + `generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --storybook7Configuration --configureCypress --generateStories --generateCypressSpecs --no-interactive` + ); + + runCLI(`report`); + }); + + afterAll(() => { + cleanupProject(); + }); + + describe('serve storybook', () => { + afterEach(() => killPorts()); + + it('should serve a React based Storybook setup that uses webpack', async () => { + // serve the storybook + const p = await runCommandUntil( + `run ${reactStorybookLib}:storybook`, + (output) => { + return /Storybook.*started/gi.test(output); + } + ); + p.kill(); + }, 60000); + + it('should serve an Angular based Storybook setup', async () => { + // serve the storybook + const p = await runCommandUntil( + `run ${angularStorybookLib}:storybook`, + (output) => { + return /Storybook.*started/gi.test(output); + } + ); + p.kill(); + }, 60000); + }); + + describe('build storybook', () => { + it('should build a React based storybook setup that uses webpack', () => { + // build + runCLI(`run ${reactStorybookLib}:build-storybook --verbose`); + checkFilesExist(`dist/storybook/${reactStorybookLib}/index.html`); + }, 60000); + + it('shoud build an Angular based storybook', () => { + runCLI(`run ${angularStorybookLib}:build-storybook --verbose`); + checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`); + }); + + // This test makes sure path resolution works + it('should build a React based storybook that references another lib and uses webpack', () => { + const anotherReactLib = uniq('test-another-lib-react'); + runCLI(`generate @nrwl/react:lib ${anotherReactLib} --no-interactive`); + // create a React component we can reference + writeFileSync( + tmpProjPath(`libs/${anotherReactLib}/src/lib/mytestcmp.tsx`), + ` + export function MyTestCmp() { + return ( +
+

Welcome to OtherLib!

+
+ ); + } + + export default MyTestCmp; + ` + ); + // update index.ts and export it + writeFileSync( + tmpProjPath(`libs/${anotherReactLib}/src/index.ts`), + ` + export * from './lib/mytestcmp'; + ` + ); + + // create a story in the first lib to reference the cmp from the 2nd lib + writeFileSync( + tmpProjPath( + `libs/${reactStorybookLib}/src/lib/myteststory.stories.tsx` + ), + ` + import type { Meta } from '@storybook/react'; + import { MyTestCmp } from '@${proj}/${anotherReactLib}'; + + const Story: Meta = { + component: MyTestCmp, + title: 'MyTestCmp', + }; + export default Story; + + export const Primary = { + args: {}, + }; + + ` + ); + + // build React lib + runCLI(`run ${reactStorybookLib}:build-storybook --verbose`); + checkFilesExist(`dist/storybook/${reactStorybookLib}/index.html`); + }, 60000); + }); + + describe('run cypress tests using storybook', () => { + it('should execute e2e tests using Cypress running against Storybook', async () => { + if (runCypressTests()) { + 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(); + } + }, 60000); + }); +}); + +export function createTestUILib(libName: string): void { + runCLI(`g @nrwl/angular:library ${libName} --no-interactive`); + runCLI( + `g @nrwl/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` + ), + ` + + ` + ); + runCLI( + `g @nrwl/angular:component test-other --project=${libName} --no-interactive` + ); +} diff --git a/e2e/storybook-7/tsconfig.json b/e2e/storybook-7/tsconfig.json new file mode 100644 index 00000000000000..6d5abf84832009 --- /dev/null +++ b/e2e/storybook-7/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/storybook-7/tsconfig.spec.json b/e2e/storybook-7/tsconfig.spec.json new file mode 100644 index 00000000000000..1a24bfb0a13536 --- /dev/null +++ b/e2e/storybook-7/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx", + "**/*.d.ts", + "jest.config.ts" + ] +} diff --git a/e2e/storybook/src/storybook.test.ts b/e2e/storybook/src/storybook.test.ts index c1270f010e5fe7..4a14762bf0f8c8 100644 --- a/e2e/storybook/src/storybook.test.ts +++ b/e2e/storybook/src/storybook.test.ts @@ -182,6 +182,41 @@ describe('Storybook generators and executors for monorepos', () => { } }, 100000); }); + + describe('run cypress tests using storybook', () => { + it('should execute e2e tests using Cypress running against Storybook', async () => { + if (runCypressTests()) { + 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(); + } + }, 1000000); + }); }); export function createTestUILib(libName: string): void {