From f1f4e64b08cd1946e80378f500de6610c410fff4 Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Wed, 12 Apr 2023 11:22:41 -0500 Subject: [PATCH] feat(testing): support NextJs Component Testing wip wip wip wip wipp wip working --- docs/generated/manifests/menus.json | 8 + docs/generated/manifests/packages.json | 9 + docs/generated/packages-metadata.json | 9 + .../cypress-component-configuration.json | 53 +++++ e2e/next/src/next-component-tests.test.ts | 190 ++++++++++++++++++ .../cypress-component-project.spec.ts | 2 +- .../files/cypress/support/commands.ts__ext__ | 16 +- .../files/cypress/tsconfig.cy.json | 2 +- ...ypress-component-configuration-examples.md | 79 ++++++++ packages/next/generators.json | 5 + packages/next/plugins/component-testing.ts | 131 ++++++++++++ ...press-component-configuration.spec.ts.snap | 47 +++++ .../cypress-component-configuration.spec.ts | 123 ++++++++++++ .../cypress-component-configuration.ts | 121 +++++++++++ .../files/cypress.config.ts__tpl__ | 6 + .../files/cypress/support/commands.ts__tpl__ | 42 ++++ .../cypress/support/styles.ct.css__tpl__ | 4 + .../schema.d.ts | 5 + .../schema.json | 42 ++++ .../cypress-component-configuration.ts | 17 +- .../lib/add-files.ts | 64 +----- .../lib/update-configs.ts | 51 ----- packages/react/src/utils/ct-utils.ts | 100 +++++++++ 23 files changed, 1009 insertions(+), 117 deletions(-) create mode 100644 docs/generated/packages/next/generators/cypress-component-configuration.json create mode 100644 e2e/next/src/next-component-tests.test.ts create mode 100644 packages/next/docs/cypress-component-configuration-examples.md create mode 100644 packages/next/plugins/component-testing.ts create mode 100644 packages/next/src/generators/cypress-component-configuration/__snapshots__/cypress-component-configuration.spec.ts.snap create mode 100644 packages/next/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts create mode 100644 packages/next/src/generators/cypress-component-configuration/cypress-component-configuration.ts create mode 100644 packages/next/src/generators/cypress-component-configuration/files/cypress.config.ts__tpl__ create mode 100644 packages/next/src/generators/cypress-component-configuration/files/cypress/support/commands.ts__tpl__ create mode 100644 packages/next/src/generators/cypress-component-configuration/files/cypress/support/styles.ct.css__tpl__ create mode 100644 packages/next/src/generators/cypress-component-configuration/schema.d.ts create mode 100644 packages/next/src/generators/cypress-component-configuration/schema.json delete mode 100644 packages/react/src/generators/cypress-component-configuration/lib/update-configs.ts create mode 100644 packages/react/src/utils/ct-utils.ts diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 25f2135f18272f..d3221ad8b43844 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -5183,6 +5183,14 @@ "children": [], "isExternal": false, "disableCollapsible": false + }, + { + "id": "cypress-component-configuration", + "path": "/packages/next/generators/cypress-component-configuration", + "name": "cypress-component-configuration", + "children": [], + "isExternal": false, + "disableCollapsible": false } ], "isExternal": false, diff --git a/docs/generated/manifests/packages.json b/docs/generated/manifests/packages.json index e1e9127390e4e0..2047aa15d07b70 100644 --- a/docs/generated/manifests/packages.json +++ b/docs/generated/manifests/packages.json @@ -1380,6 +1380,15 @@ "originalFilePath": "/packages/next/src/generators/custom-server/schema.json", "path": "/packages/next/generators/custom-server", "type": "generator" + }, + "/packages/next/generators/cypress-component-configuration": { + "description": "cypress-component-configuration generator", + "file": "generated/packages/next/generators/cypress-component-configuration.json", + "hidden": false, + "name": "cypress-component-configuration", + "originalFilePath": "/packages/next/src/generators/cypress-component-configuration/schema.json", + "path": "/packages/next/generators/cypress-component-configuration", + "type": "generator" } }, "path": "/packages/next" diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index 00179da8216fde..5a3551f3098cad 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1361,6 +1361,15 @@ "originalFilePath": "/packages/next/src/generators/custom-server/schema.json", "path": "next/generators/custom-server", "type": "generator" + }, + { + "description": "cypress-component-configuration generator", + "file": "generated/packages/next/generators/cypress-component-configuration.json", + "hidden": false, + "name": "cypress-component-configuration", + "originalFilePath": "/packages/next/src/generators/cypress-component-configuration/schema.json", + "path": "next/generators/cypress-component-configuration", + "type": "generator" } ], "githubRoot": "https://github.com/nrwl/nx/blob/master", diff --git a/docs/generated/packages/next/generators/cypress-component-configuration.json b/docs/generated/packages/next/generators/cypress-component-configuration.json new file mode 100644 index 00000000000000..5d60d013595dfb --- /dev/null +++ b/docs/generated/packages/next/generators/cypress-component-configuration.json @@ -0,0 +1,53 @@ +{ + "name": "cypress-component-configuration", + "factory": "./src/generators/cypress-component-configuration/cypress-component-configuration", + "schema": { + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "CypressComponentConfiguration", + "title": "NextJS Component Testing Configuration", + "description": "Add Cypress Componet Testing to an existing NextJS project.", + "type": "object", + "examples": [ + { + "command": "nx g @nrwl/next:cypress-component-configuration --project=my-next-project", + "description": "Add component testing to your Next project" + }, + { + "command": "nx g @nrwl/next:cypress-component-configuration --project=my-next-project --generate-tests", + "description": "Add component testing to your Next project and generate component tests for your existing components" + } + ], + "properties": { + "project": { + "type": "string", + "description": "The name of the project to add cypress component testing configuration to", + "x-dropdown": "projects", + "x-prompt": "What project should we add Cypress component testing to?", + "x-priority": "important" + }, + "generateTests": { + "type": "boolean", + "description": "Generate default component tests for existing components in the project", + "x-prompt": "Automatically generate tests for components declared in this project?", + "default": false, + "x-priority": "important" + }, + "skipFormat": { + "type": "boolean", + "description": "Skip formatting files", + "default": false, + "x-priority": "internal" + } + }, + "required": ["project"], + "examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nNext component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v10 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you want to test components via Storybook with Cypress, then check out the [storybook-configuration generator docs](/packages/react/generators/storybook-configuration)\n{% /callout %}\n\nThis generator is designed to get your Next project up and running with Cypress Component Testing.\n\n```shell\nnx g @nrwl/next:cypress-component-configuration --project=my-cool-next-project\n```\n\nRunning this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nrwl/next/plugins/component-testing';\n\nexport default defineConfig({\n component: nxComponentTestingPreset(__filename),\n});\n```\n\nHere is an example on how to add custom options to the configuration\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nrwl/next/plugins/component-testing';\n\nexport default defineConfig({\n component: {\n ...nxComponentTestingPreset(__filename),\n // extra options here\n },\n});\n```\n\n```shell\nnx g @nrwl/next:cypress-component-project --project=my-cool-next-project\n```\n\n## Auto Generating Tests\n\nYou can optionally use the `--generate-tests` flag to generate a test file for each component in your project.\n\n```shell\nnx g @nrwl/next:cypress-component-configuration --project=my-cool-next-project --generate-tests\n```\n\n## Running Component Tests\n\nA new `component-test` target will be added to the specified project to run your component tests.\n\n```shell\nnx g component-test my-cool-next-project\n```\n\nHere is an example of the project configuration that is generated.\n\n```json {% fileName=\"project.json\" %}\n{\n \"targets\" {\n \"component-test\": {\n \"executor\": \"@nrwl/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"/cypress.config.ts\",\n \"testingType\": \"component\",\n \"skipServe\": true\n }\n }\n }\n}\n```\n\nNx also supports [Angular component testing](/packages/angular/generators/cypress-component-configuration).\n", + "presets": [] + }, + "description": "cypress-component-configuration generator", + "implementation": "/packages/next/src/generators/cypress-component-configuration/cypress-component-configuration.ts", + "aliases": [], + "hidden": false, + "path": "/packages/next/src/generators/cypress-component-configuration/schema.json", + "type": "generator" +} diff --git a/e2e/next/src/next-component-tests.test.ts b/e2e/next/src/next-component-tests.test.ts new file mode 100644 index 00000000000000..98de2658362c22 --- /dev/null +++ b/e2e/next/src/next-component-tests.test.ts @@ -0,0 +1,190 @@ +import { + createFile, + newProject, + runCLI, + uniq, + updateFile, + runCypressTests, + updateJson, +} from '@nrwl/e2e/utils'; + +describe('NextJs Component Testing', () => { + beforeAll(() => { + newProject({ + name: uniq('next-ct'), + }); + }); + + it('should test a NextJs app', () => { + const appName = uniq('next-app'); + createAppWithCt(appName); + if (runCypressTests()) { + expect(runCLI(`component-test ${appName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + addTailwindToApp(appName); + if (runCypressTests()) { + expect(runCLI(`component-test ${appName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + }); + + it('should test a NextJs lib', async () => { + const libName = uniq('next-lib'); + createLibWithCt(libName, false); + if (runCypressTests()) { + expect(runCLI(`component-test ${libName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + addTailwindToLib(libName); + if (runCypressTests()) { + expect(runCLI(`component-test ${libName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + }); + + it('should test a NextJs buildable lib', async () => { + const buildableLibName = uniq('next-buildable-lib'); + createLibWithCt(buildableLibName, true); + if (runCypressTests()) { + expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + + addTailwindToLib(buildableLibName); + if (runCypressTests()) { + expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( + 'All specs passed!' + ); + } + }); +}); + +function createAppWithCt(appName: string) { + runCLI(`generate @nrwl/next:app ${appName} --no-interactive`); + runCLI( + `generate @nrwl/next:component button --project=${appName} --directory=components --flat --no-interactive` + ); + createFile( + `apps/${appName}/public/data.json`, + JSON.stringify({ message: 'loaded from app data.json' }) + ); + + updateFile(`apps/${appName}/components/button.tsx`, (content) => { + return `import { useEffect, useState } from 'react'; + +export interface ButtonProps { + text: string; +} + +const data = fetch('/data.json').then((r) => r.json()); +export default function Button(props: ButtonProps) { + const [state, setState] = useState>(); + useEffect(() => { + data.then(setState); + }, []); + return ( + <> + {state &&
{JSON.stringify(state, null, 2)}
} +

Button

+ + + ); +} +`; + }); + + runCLI( + `generate @nrwl/next:cypress-component-configuration --project=${appName} --generate-tests --no-interactive` + ); +} + +function addTailwindToApp(appName: string) { + runCLI( + `generate @nrwl/react:setup-tailwind --project=${appName} --no-interactive` + ); + updateFile(`apps/${appName}/cypress/support/component.ts`, (content) => { + return `${content} +import '../../pages/styles.css'`; + }); + + updateFile(`apps/${appName}/components/button.cy.tsx`, (content) => { + return `import * as React from 'react'; +import Button from './button'; + +describe(Button.name, () => { + it('renders', () => { + cy.mount( +} + +export default Button; +`; + }); + + runCLI( + `generate @nrwl/next:cypress-component-configuration --project=${libName} --generate-tests --no-interactive` + ); +} +function addTailwindToLib(libName: string) { + createFile(`libs/${libName}/src/lib/styles.css`, ``); + runCLI( + `generate @nrwl/react:setup-tailwind --project=${libName} --no-interactive` + ); + updateFile(`libs/${libName}/src/lib/button.cy.tsx`, (content) => { + return `import * as React from 'react'; +import Button from './button'; + +describe(Button.name, () => { + it('renders', () => { + cy.mount(