feat(storybook): remove cypress options for e2e testing (#27850)

- feat(storybook): remove cypress options from configuration generator
- feat(react): remove cypress options from storybook-configuration
- feat(react): remove cypress options from stories generator
- feat(react): remove component-cypress-spec generator
- chore(storybook): restore @nx/cypress dep
- feat(remix): remove cypress options from storybook
- feat(angular): remove cypress options from storybook-configuration
- feat(angular): remove cypress options from stories generator
- feat(angular): remove component-cypress-spec generator
- feat(vue): remove cypress options from stories generator

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
With Storybook Interaction Testing, there's no longer a need to setup
Cypress to specifically test storybook instances


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Remove cypress options for creating an e2e project specifically for
testing storybook instances.

Use Storybook Interaction Testing instead

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Colum Ferry 2024-09-24 15:54:58 +01:00 committed by GitHub
parent 72cd1c15e6
commit 8290969cb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 8 additions and 1475 deletions

View File

@ -7028,14 +7028,6 @@
"isExternal": false,
"disableCollapsible": false
},
{
"id": "component-cypress-spec",
"path": "/nx-api/angular/generators/component-cypress-spec",
"name": "component-cypress-spec",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "component-story",
"path": "/nx-api/angular/generators/component-story",
@ -9222,14 +9214,6 @@
"isExternal": false,
"disableCollapsible": false
},
{
"id": "component-cypress-spec",
"path": "/nx-api/react/generators/component-cypress-spec",
"name": "component-cypress-spec",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "hook",
"path": "/nx-api/react/generators/hook",

View File

@ -181,15 +181,6 @@
"path": "/nx-api/angular/generators/component",
"type": "generator"
},
"/nx-api/angular/generators/component-cypress-spec": {
"description": "Creates a Cypress spec for a UI component that has a story.",
"file": "generated/packages/angular/generators/component-cypress-spec.json",
"hidden": true,
"name": "component-cypress-spec",
"originalFilePath": "/packages/angular/src/generators/component-cypress-spec/schema.json",
"path": "/nx-api/angular/generators/component-cypress-spec",
"type": "generator"
},
"/nx-api/angular/generators/component-story": {
"description": "Creates a stories.ts file for a component.",
"file": "generated/packages/angular/generators/component-story.json",
@ -2358,15 +2349,6 @@
"path": "/nx-api/react/generators/stories",
"type": "generator"
},
"/nx-api/react/generators/component-cypress-spec": {
"description": "Create a Cypress spec for a UI component that has a story.",
"file": "generated/packages/react/generators/component-cypress-spec.json",
"hidden": false,
"name": "component-cypress-spec",
"originalFilePath": "/packages/react/src/generators/component-cypress-spec/schema.json",
"path": "/nx-api/react/generators/component-cypress-spec",
"type": "generator"
},
"/nx-api/react/generators/hook": {
"description": "Create a hook.",
"file": "generated/packages/react/generators/hook.json",

View File

@ -176,15 +176,6 @@
"path": "angular/generators/component",
"type": "generator"
},
{
"description": "Creates a Cypress spec for a UI component that has a story.",
"file": "generated/packages/angular/generators/component-cypress-spec.json",
"hidden": true,
"name": "component-cypress-spec",
"originalFilePath": "/packages/angular/src/generators/component-cypress-spec/schema.json",
"path": "angular/generators/component-cypress-spec",
"type": "generator"
},
{
"description": "Creates a stories.ts file for a component.",
"file": "generated/packages/angular/generators/component-story.json",
@ -2332,15 +2323,6 @@
"path": "react/generators/stories",
"type": "generator"
},
{
"description": "Create a Cypress spec for a UI component that has a story.",
"file": "generated/packages/react/generators/component-cypress-spec.json",
"hidden": false,
"name": "component-cypress-spec",
"originalFilePath": "/packages/react/src/generators/component-cypress-spec/schema.json",
"path": "react/generators/component-cypress-spec",
"type": "generator"
},
{
"description": "Create a hook.",
"file": "generated/packages/react/generators/hook.json",

View File

@ -1,75 +0,0 @@
{
"name": "component-cypress-spec",
"factory": "./src/generators/component-cypress-spec/component-cypress-spec",
"schema": {
"$schema": "https://json-schema.org/schema",
"$id": "NxAngularComponentCypressSpecGenerator",
"type": "object",
"cli": "nx",
"description": "Creates a Storybook Cypress spec for a UI component that has a story.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20.",
"properties": {
"projectName": {
"type": "string",
"description": "The name of the project.",
"$default": { "$source": "projectName" },
"examples": ["ui-samples"],
"x-priority": "important"
},
"projectPath": {
"type": "string",
"description": "Path to the project.",
"examples": ["libs/ui-samples"],
"x-priority": "important"
},
"componentName": {
"type": "string",
"description": "Class name of the component.",
"examples": ["AwesomeComponent"],
"x-priority": "important"
},
"componentPath": {
"type": "string",
"description": "Relative path to the component file from the project root.",
"examples": ["awesome"],
"x-priority": "important"
},
"componentFileName": {
"type": "string",
"description": "Component file name without the `.ts` extension.",
"examples": ["awesome.component"],
"x-priority": "important"
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. By default, inferred from `projectName`."
},
"specDirectory": {
"type": "string",
"description": "Directory where to place the generated spec file. By default matches the value of the `componentPath` option."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": [
"projectName",
"projectPath",
"componentName",
"componentPath",
"componentFileName"
],
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Basic Usage\" %}\n\nCreate a cypress spec for a component that is set up with Storybook.\n\n```bash\nnx g @nx/angular:component-cypress-spec --componentName=MyButtonComponent --componentPath=libs/ui/src/lib/button/button.component.ts --componentFileName=button.component --projectName=ui --projectPath=libs/ui\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
"presets": []
},
"description": "Creates a Cypress spec for a UI component that has a story.",
"hidden": true,
"implementation": "/packages/angular/src/generators/component-cypress-spec/component-cypress-spec.ts",
"aliases": [],
"path": "/packages/angular/src/generators/component-cypress-spec/schema.json",
"type": "generator"
}

View File

@ -25,16 +25,6 @@
"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-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `name` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",

View File

@ -26,11 +26,6 @@
"x-priority": "important",
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Specifies whether to configure Cypress or not.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Specifies whether to automatically generate `*.stories.ts` files for components declared in this project or not.",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Specifies whether to automatically generate test files in the generated Cypress e2e app.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",

View File

@ -1,53 +0,0 @@
{
"name": "component-cypress-spec",
"factory": "./src/generators/component-cypress-spec/component-cypress-spec#componentCypressGenerator",
"schema": {
"$schema": "https://json-schema.org/schema",
"cli": "nx",
"$id": "NxReactComponentCypressSpec",
"title": "Create component Cypress spec",
"description": "Create a Storybook Cypress spec for a UI component that has a story.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20.",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The project name for which to generate tests.",
"examples": ["shared-ui-component"],
"$default": { "$source": "projectName", "index": 0 },
"x-prompt": "What's name of the project for which to generate tests?",
"x-priority": "important"
},
"componentPath": {
"type": "string",
"description": "Relative path to the component file from the library root?",
"examples": ["lib/components"],
"x-prompt": "What's path of the component relative to the project's lib root for which to generate a test?",
"x-priority": "important"
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. By default, inferred from `project`."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
}
},
"required": ["project", "componentPath"],
"presets": []
},
"description": "Create a Cypress spec for a UI component that has a story.",
"hidden": false,
"implementation": "/packages/react/src/generators/component-cypress-spec/component-cypress-spec#componentCypressGenerator.ts",
"aliases": [],
"path": "/packages/react/src/generators/component-cypress-spec/schema.json",
"type": "generator"
}

View File

@ -17,16 +17,6 @@
"x-prompt": "For which project do you want to generate stories?",
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate `*.spec.ts` files in the cypress e2e app generated by the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `project` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"interactionTests": {
"type": "boolean",
"description": "Set up Storybook interaction tests.",

View File

@ -26,11 +26,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate test files in the Cypress E2E app generated by the `cypress-configure` generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"js": {
"type": "boolean",
"description": "Generate JavaScript story files rather than TypeScript story files.",

View File

@ -26,11 +26,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate test files in the Cypress E2E app generated by the `cypress-configure` generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"js": {
"type": "boolean",
"description": "Generate JavaScript story files rather than TypeScript story files.",

View File

@ -25,16 +25,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Added at root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",

View File

@ -17,16 +17,6 @@
"x-prompt": "For which project do you want to generate stories?",
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate `*.spec.ts` files in the cypress e2e app generated by the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `project` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"interactionTests": {
"type": "boolean",
"description": "Set up Storybook interaction tests.",

View File

@ -347,7 +347,6 @@
- [add-linting](/nx-api/angular/generators/add-linting)
- [application](/nx-api/angular/generators/application)
- [component](/nx-api/angular/generators/component)
- [component-cypress-spec](/nx-api/angular/generators/component-cypress-spec)
- [component-story](/nx-api/angular/generators/component-story)
- [component-test](/nx-api/angular/generators/component-test)
- [convert-to-application-executor](/nx-api/angular/generators/convert-to-application-executor)
@ -613,7 +612,6 @@
- [storybook-configuration](/nx-api/react/generators/storybook-configuration)
- [component-story](/nx-api/react/generators/component-story)
- [stories](/nx-api/react/generators/stories)
- [component-cypress-spec](/nx-api/react/generators/component-cypress-spec)
- [hook](/nx-api/react/generators/hook)
- [host](/nx-api/react/generators/host)
- [remote](/nx-api/react/generators/remote)

View File

@ -15,10 +15,6 @@ const pages: Array<{ title: string; path: string }> = [
title: '@nx/angular:component',
path: '/packages/angular/generators/component',
},
{
title: '@nx/angular:component-cypress-spec',
path: '/packages/angular/generators/component-cypress-spec',
},
{
title: '@nx/angular:component-story',
path: '/packages/angular/generators/component-story',
@ -298,10 +294,6 @@ const pages: Array<{ title: string; path: string }> = [
title: '@nx/react:stories',
path: '/packages/react/generators/stories',
},
{
title: '@nx/react:component-cypress-spec',
path: '/packages/react/generators/component-cypress-spec',
},
{ title: '@nx/react:hook', path: '/packages/react/generators/hook' },
{ title: '@nx/react:host', path: '/packages/react/generators/host' },
{ title: '@nx/react:remote', path: '/packages/react/generators/remote' },

View File

@ -22,12 +22,6 @@
"aliases": ["c"],
"description": "Generate an Angular Component."
},
"component-cypress-spec": {
"factory": "./src/generators/component-cypress-spec/component-cypress-spec",
"schema": "./src/generators/component-cypress-spec/schema.json",
"description": "Creates a Cypress spec for a UI component that has a story.",
"hidden": true
},
"component-story": {
"factory": "./src/generators/component-story/component-story",
"schema": "./src/generators/component-story/schema.json",

View File

@ -1,6 +1,5 @@
export * from './src/generators/add-linting/add-linting';
export * from './src/generators/application/application';
export * from './src/generators/component-cypress-spec/component-cypress-spec';
export * from './src/generators/component-story/component-story';
export * from './src/generators/component/component';
export * from './src/generators/directive/directive';

View File

@ -1,31 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`componentCypressSpec generator should generate .spec.ts when using cypress.json 1`] = `
"describe('ng-app1', () => {
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[`componentCypressSpec generator should generate the component spec file 1`] = `
"describe('ng-app1', () => {
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');
});
});
"
`;

View File

@ -1,112 +0,0 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import type { Tree } from '@nx/devkit';
import * as devkit from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { componentGenerator } from '../component/component';
import * as storybookUtils from '../utils/storybook-ast/storybook-inputs';
import { generateTestApplication } from '../utils/testing';
import { componentCypressSpecGenerator } from './component-cypress-spec';
import { E2eTestRunner } from '../../utils/test-runners';
// 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');
describe('componentCypressSpec generator', () => {
let tree: Tree;
const appName = 'ng-app1';
const specFile = `${appName}-e2e/src/e2e/test-button/test-button.component.cy.ts`;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestApplication(tree, {
name: appName,
skipFormat: true,
e2eTestRunner: E2eTestRunner.Cypress,
});
await componentGenerator(tree, {
name: 'test-button',
directory: `${appName}/src/app/test-button`,
skipFormat: true,
});
tree.write(
`${appName}/src/app/test-button/test-button.component.ts`,
`import { Component, Input } from '@angular/core';
export type ButtonStyle = 'default' | 'primary' | 'accent';
@Component({
selector: 'proj-test-button',
templateUrl: './test-button.component.html',
styleUrls: ['./test-button.component.css']
})
export class TestButtonComponent {
@Input('buttonType') type = 'button';
@Input() style: ButtonStyle = 'default';
@Input() age?: number;
@Input() isOn = false;
}`
);
});
it('should not generate the component spec file when it already exists', async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
jest.spyOn(storybookUtils, 'getComponentProps');
jest.spyOn(devkit, 'generateFiles');
tree.write(specFile, '');
await componentCypressSpecGenerator(tree, {
componentFileName: 'test-button.component',
componentName: 'TestButtonComponent',
componentPath: `test-button`,
projectPath: `${appName}/src/app`,
projectName: appName,
skipFormat: true,
});
expect(storybookUtils.getComponentProps).not.toHaveBeenCalled();
expect(devkit.generateFiles).not.toHaveBeenCalled();
expect(tree.read(specFile).toString()).toBe('');
});
it('should generate the component spec file', async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
await componentCypressSpecGenerator(tree, {
componentFileName: 'test-button.component',
componentName: 'TestButtonComponent',
componentPath: `test-button`,
projectPath: `${appName}/src/app`,
projectName: appName,
});
expect(tree.exists(specFile)).toBe(true);
const specFileContent = tree.read(specFile).toString();
expect(specFileContent).toMatchSnapshot();
});
it('should generate .spec.ts when using cypress.json', async () => {
mockedInstalledCypressVersion.mockReturnValue(9);
const v9SpecFile = `${appName}-e2e/src/integration/test-button/test-button.component.spec.ts`;
tree.delete(`${appName}-e2e/cypress.config.ts`);
tree.write(`${appName}-e2e/cypress.json`, `{}`);
await componentCypressSpecGenerator(tree, {
componentFileName: 'test-button.component',
componentName: 'TestButtonComponent',
componentPath: `test-button`,
projectPath: `${appName}/src/app`,
projectName: appName,
skipFormat: true,
});
expect(tree.exists(v9SpecFile)).toBe(true);
const specFileContent = tree.read(v9SpecFile).toString();
expect(specFileContent).toMatchSnapshot();
});
});

View File

@ -1,72 +0,0 @@
import type { Tree } from '@nx/devkit';
import {
formatFiles,
generateFiles,
joinPathFragments,
readProjectConfiguration,
} from '@nx/devkit';
import { getComponentProps } from '../utils/storybook-ast/storybook-inputs';
import { getArgsDefaultValue } from './lib/get-args-default-value';
import { getComponentSelector } from './lib/get-component-selector';
import type { ComponentCypressSpecGeneratorOptions } from './schema';
export async function componentCypressSpecGenerator(
tree: Tree,
options: ComponentCypressSpecGeneratorOptions
): Promise<void> {
const {
cypressProject,
projectName,
projectPath,
componentPath,
componentFileName,
componentName,
specDirectory,
} = options;
const e2eProjectName = cypressProject || `${projectName}-e2e`;
const { sourceRoot, root } = readProjectConfiguration(tree, e2eProjectName);
const isCypressV10 = tree.exists(
joinPathFragments(root, 'cypress.config.ts')
);
const e2eLibIntegrationFolderPath = joinPathFragments(
sourceRoot,
isCypressV10 ? 'e2e' : 'integration'
);
const templatesDir = joinPathFragments(__dirname, 'files');
const destinationDir = joinPathFragments(
e2eLibIntegrationFolderPath,
specDirectory ?? componentPath
);
const storyFile = joinPathFragments(
destinationDir,
`${componentFileName}.${isCypressV10 ? 'cy' : 'spec'}.ts`
);
if (tree.exists(storyFile)) {
return;
}
const fullComponentPath = joinPathFragments(
projectPath,
componentPath,
`${componentFileName}.ts`
);
const props = getComponentProps(tree, fullComponentPath, getArgsDefaultValue);
const componentSelector = getComponentSelector(tree, fullComponentPath);
generateFiles(tree, templatesDir, destinationDir, {
projectName,
componentFileName,
componentName,
componentSelector,
props,
fileExt: isCypressV10 ? 'cy.ts' : 'spec.ts',
});
if (!options.skipFormat) {
await formatFiles(tree);
}
}
export default componentCypressSpecGenerator;

View File

@ -1,17 +0,0 @@
describe('<%=projectName%>', () => {
beforeEach(() =>
cy.visit(
'/iframe.html?id=<%= componentName.toLowerCase() %>--primary<% if ( props && props.length > 0 ) { %>&args=<% } %><%
for(let prop of props) {
%><%=prop.name%><%
if(prop.defaultValue !== undefined && (prop.defaultValue || prop.defaultValue === false)) {
%>:<%=prop.defaultValue%><%
} %>;<%
}%>'
)
);
it('should render the component', () => {
cy.get('<%=componentSelector%>').should('exist');
});
});

View File

@ -1,26 +0,0 @@
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import type { PropertyDeclaration } from 'typescript';
let tsModule: typeof import('typescript');
export function getArgsDefaultValue(
property: PropertyDeclaration
): string | undefined {
if (!property.initializer) {
return undefined;
}
if (!tsModule) {
tsModule = ensureTypescript();
}
switch (property.initializer.kind) {
case tsModule.SyntaxKind.StringLiteral:
const returnString = property.initializer.getText().slice(1, -1);
return returnString.replace(/\s/g, '+');
case tsModule.SyntaxKind.NumericLiteral:
case tsModule.SyntaxKind.TrueKeyword:
case tsModule.SyntaxKind.FalseKeyword:
return property.initializer.getText();
default:
return undefined;
}
}

View File

@ -1,38 +0,0 @@
import type { Tree } from '@nx/devkit';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { findNodes } from '@nx/js';
import type { PropertyAssignment } from 'typescript';
import {
getDecoratorMetadata,
getTsSourceFile,
} from '../../../utils/nx-devkit/ast-utils';
let tsModule: typeof import('typescript');
export function getComponentSelector(tree: Tree, path: string): string {
if (!tsModule) {
tsModule = ensureTypescript();
}
const file = getTsSourceFile(tree, path);
const componentDecorators = getDecoratorMetadata(
file,
'Component',
'@angular/core'
);
if (componentDecorators.length === 0) {
throw new Error(`No @Component decorator in ${path}.`);
}
const componentDecorator = componentDecorators[0];
const selectorNode = <PropertyAssignment>(
findNodes(componentDecorator, tsModule.SyntaxKind.PropertyAssignment).find(
(node: PropertyAssignment) => node.name.getText() === 'selector'
)
);
if (!selectorNode) {
throw new Error(`No selector defined for the component in ${path}.`);
}
return selectorNode.initializer.getText().slice(1, -1);
}

View File

@ -1,10 +0,0 @@
export interface ComponentCypressSpecGeneratorOptions {
projectName: string;
projectPath: string;
componentName: string;
componentPath: string;
componentFileName: string;
cypressProject?: string;
specDirectory?: string;
skipFormat?: boolean;
}

View File

@ -1,66 +0,0 @@
{
"$schema": "https://json-schema.org/schema",
"$id": "NxAngularComponentCypressSpecGenerator",
"type": "object",
"cli": "nx",
"description": "Creates a Storybook Cypress spec for a UI component that has a story.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20.",
"properties": {
"projectName": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
},
"examples": ["ui-samples"],
"x-priority": "important"
},
"projectPath": {
"type": "string",
"description": "Path to the project.",
"examples": ["libs/ui-samples"],
"x-priority": "important"
},
"componentName": {
"type": "string",
"description": "Class name of the component.",
"examples": ["AwesomeComponent"],
"x-priority": "important"
},
"componentPath": {
"type": "string",
"description": "Relative path to the component file from the project root.",
"examples": ["awesome"],
"x-priority": "important"
},
"componentFileName": {
"type": "string",
"description": "Component file name without the `.ts` extension.",
"examples": ["awesome.component"],
"x-priority": "important"
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. By default, inferred from `projectName`."
},
"specDirectory": {
"type": "string",
"description": "Directory where to place the generated spec file. By default matches the value of the `componentPath` option."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": [
"projectName",
"projectPath",
"componentName",
"componentPath",
"componentFileName"
],
"examplesFile": "../../../docs/component-cypress-spec-examples.md"
}

View File

@ -1,16 +0,0 @@
import type { ProjectConfiguration, Tree } from '@nx/devkit';
import { readProjectConfiguration } from '@nx/devkit';
export function getE2EProject(
tree: Tree,
e2eProjectName: string
): ProjectConfiguration {
let e2eProject: ProjectConfiguration;
try {
e2eProject = readProjectConfiguration(tree, e2eProjectName);
} catch {
e2eProject = undefined;
}
return e2eProject;
}

View File

@ -3,6 +3,4 @@ export interface StoriesGeneratorOptions {
interactionTests?: boolean;
skipFormat?: boolean;
ignorePaths?: string[];
cypressProject?: string;
generateCypressSpecs?: boolean;
}

View File

@ -25,16 +25,6 @@
"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-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `name` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",

View File

@ -1,6 +1,5 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import type { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { componentGenerator } from '../component/component';
@ -9,21 +8,11 @@ import { generateTestApplication } from '../utils/testing';
import { angularStoriesGenerator } from './stories';
import { stripIndents } from '@nx/devkit';
// 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): Nx 19 -> remove Cypress
describe('angularStories generator: applications', () => {
let tree: Tree;
const appName = 'test-app';
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestApplication(tree, {
name: appName,

View File

@ -1,6 +1,5 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
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';
@ -13,20 +12,8 @@ import {
} from '../utils/testing';
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): Nx 19 -> remove Cypress
describe('angularStories generator: libraries', () => {
const libName = 'test-ui-lib';
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10);
});
describe('Stories for empty Angular library', () => {
let tree: Tree;

View File

@ -4,12 +4,10 @@ import {
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';
import {
@ -17,7 +15,6 @@ import {
getStandaloneComponentsInfo,
} from '../utils/storybook-ast/component-info';
import { getProjectEntryPoints } from '../utils/storybook-ast/entry-point';
import { getE2EProject } from './lib/get-e2e-project';
import { getModuleFilePaths } from '../utils/storybook-ast/module-info';
import type { StoriesGeneratorOptions } from './schema';
import { minimatch } from 'minimatch';
@ -27,8 +24,6 @@ export async function angularStoriesGenerator(
tree: Tree,
options: StoriesGeneratorOptions
): Promise<GeneratorCallback> {
const e2eProjectName = options.cypressProject ?? `${options.name}-e2e`;
const e2eProject = getE2EProject(tree, e2eProjectName);
const entryPoints = getProjectEntryPoints(tree, options.name);
const componentsInfo: ComponentInfo[] = [];
for (const entryPoint of entryPoints) {
@ -39,12 +34,6 @@ export async function angularStoriesGenerator(
);
}
if (options.generateCypressSpecs && !e2eProject) {
logger.info(
`There was no e2e project "${e2eProjectName}" found, so cypress specs will not be generated. Pass "--cypressProject" to specify a different e2e project name.`
);
}
const componentInfos = componentsInfo.filter(
(f) =>
!options.ignorePaths?.some((pattern) => {
@ -73,19 +62,6 @@ export async function angularStoriesGenerator(
interactionTests: options.interactionTests ?? true,
skipFormat: true,
});
if (options.generateCypressSpecs && e2eProject) {
await componentCypressSpecGenerator(tree, {
projectName: options.name,
projectPath: info.moduleFolderPath,
cypressProject: options.cypressProject,
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName,
specDirectory: joinPathFragments(info.entryPointName, info.path),
skipFormat: true,
});
}
}
const tasks: GeneratorCallback[] = [];

View File

@ -1,6 +1,5 @@
import type { Tree } from '@nx/devkit';
import { ensurePackage, readProjectConfiguration } from '@nx/devkit';
import { nxVersion } from '../../../utils/versions';
import { readProjectConfiguration } from '@nx/devkit';
import { angularStoriesGenerator } from '../../stories/stories';
import type { StorybookConfigurationOptions } from '../schema';
@ -9,21 +8,9 @@ export async function generateStories(
options: StorybookConfigurationOptions
) {
const project = readProjectConfiguration(tree, options.project);
ensurePackage('@nx/cypress', nxVersion);
const { getE2eProjectName } = <
typeof import('@nx/cypress/src/utils/project-name')
>require('@nx/cypress/src/utils/project-name');
const e2eProjectName = getE2eProjectName(
options.project,
project.root,
options.cypressDirectory
);
await angularStoriesGenerator(tree, {
name: options.project,
generateCypressSpecs:
options.configureCypress && options.generateCypressSpecs,
cypressProject: e2eProjectName,
ignorePaths: options.ignorePaths,
interactionTests: options.interactionTests,
skipFormat: true,

View File

@ -12,9 +12,7 @@ export async function generateStorybookConfiguration(
return await configurationGenerator(tree, {
project: options.project,
uiFramework: '@storybook/angular',
configureCypress: options.configureCypress,
linter: options.linter,
cypressDirectory: options.cypressDirectory,
tsConfiguration: options.tsConfiguration,
interactionTests: options.interactionTests,
configureStaticServe: options.configureStaticServe,

View File

@ -1,9 +0,0 @@
import type { StorybookConfigurationOptions } from '../schema';
export function validateOptions(options: StorybookConfigurationOptions): void {
if (options.generateCypressSpecs && !options.generateStories) {
throw new Error(
'Cannot set generateCypressSpecs to true when generateStories is set to false.'
);
}
}

View File

@ -9,7 +9,4 @@ export interface StorybookConfigurationOptions {
skipFormat?: boolean;
ignorePaths?: string[];
interactionTests?: boolean;
configureCypress?: boolean;
generateCypressSpecs?: boolean;
cypressDirectory?: string;
}

View File

@ -26,11 +26,6 @@
"x-priority": "important",
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Specifies whether to configure Cypress or not.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Specifies whether to automatically generate `*.stories.ts` files for components declared in this project or not.",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Specifies whether to automatically generate test files in the generated Cypress e2e app.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",

View File

@ -1,4 +1,3 @@
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import type { Tree } from '@nx/devkit';
import { readJson, writeJson } from '@nx/devkit';
import { Linter } from '@nx/eslint/src/generators/utils/linter';
@ -11,10 +10,7 @@ import {
import type { StorybookConfigurationOptions } from './schema';
import { storybookConfigurationGenerator } from './storybook-configuration';
// 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');
// nested code imports graph from the repo, which might have innacurate graph version
// nested code imports graph from the repo, which might have inaccurate graph version
jest.mock('nx/src/project-graph/project-graph', () => ({
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
createProjectGraphAsync: jest
@ -36,12 +32,8 @@ function listFiles(tree: Tree): string[] {
describe('StorybookConfiguration generator', () => {
let tree: Tree;
const libName = 'test-ui-lib';
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
tree = await createStorybookTestWorkspaceForLib(libName);
jest.resetModules();

View File

@ -8,16 +8,13 @@ import { updateAppEditorTsConfigExcludedFiles } from '../utils/update-app-editor
import { assertCompatibleStorybookVersion } from './lib/assert-compatible-storybook-version';
import { generateStories } from './lib/generate-stories';
import { generateStorybookConfiguration } from './lib/generate-storybook-configuration';
import { validateOptions } from './lib/validate-options';
import type { StorybookConfigurationOptions } from './schema';
// TODO(katerina): Nx 19 -> remove Cypress
export async function storybookConfigurationGenerator(
tree: Tree,
options: StorybookConfigurationOptions
): Promise<GeneratorCallback> {
assertCompatibleStorybookVersion();
validateOptions(options);
const storybookGeneratorInstallTask = await generateStorybookConfiguration(
tree,

View File

@ -54,12 +54,6 @@
"description": "Create stories/specs for all components declared in an app or library.",
"hidden": false
},
"component-cypress-spec": {
"factory": "./src/generators/component-cypress-spec/component-cypress-spec#componentCypressGenerator",
"schema": "./src/generators/component-cypress-spec/schema.json",
"description": "Create a Cypress spec for a UI component that has a story.",
"hidden": false
},
"hook": {
"factory": "./src/generators/hook/hook",
"schema": "./src/generators/hook/schema.json",

View File

@ -14,7 +14,6 @@ export { reactDomVersion, reactVersion } from './src/utils/versions';
export { applicationGenerator } from './src/generators/application/application';
export { componentGenerator } from './src/generators/component/component';
export { hookGenerator } from './src/generators/hook/hook';
export { componentCypressGenerator } from './src/generators/component-cypress-spec/component-cypress-spec';
export { componentStoryGenerator } from './src/generators/component-story/component-story';
export { libraryGenerator } from './src/generators/library/library';
export { reactInitGenerator } from './src/generators/init/init';

View File

@ -1,235 +0,0 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/eslint';
import { formatFile } from '../../utils/format-file';
import applicationGenerator from '../application/application';
import libraryGenerator from '../library/library';
import componentCypressSpecGenerator from './component-cypress-spec';
describe('react:component-cypress-spec', () => {
let appTree: Tree;
[
{
plainJS: false,
testCmpSrcWithProps: `import React from 'react';
import './test.scss';
export interface TestProps {
name: string;
displayAge: boolean;
}
export const Test = (props: TestProps) => {
return (
<div>
<h1>Welcome to test component, {props.name}</h1>
</div>
);
};
export default Test;
`,
testCmpSrcWithoutProps: `import React from 'react';
import './test.scss';
export const Test = () => {
return (
<div>
<h1>Welcome to test component</h1>
</div>
);
};
export default Test;
`,
},
{
plainJS: true,
testCmpSrcWithProps: `import React from 'react';
import './test.scss';
export const Test = (props: TestProps) => {
return (
<div>
<h1>Welcome to test component, {props.name}</h1>
</div>
);
};
export default Test;
`,
testCmpSrcWithoutProps: `import React from 'react';
import './test.scss';
export const Test = () => {
return (
<div>
<h1>Welcome to test component</h1>
</div>
);
};
export default Test;
`,
},
].forEach((testConfig) => {
let fileCmpExt = testConfig.plainJS ? 'js' : 'tsx';
let fileExt = testConfig.plainJS ? 'js' : 'ts';
describe(`using ${
testConfig.plainJS ? 'plain JS' : 'TypeScript'
} setup`, () => {
let cmpPath = `test-ui-lib/src/lib/test-ui-lib.${fileCmpExt}`;
let cypressStorySpecFilePath = `test-ui-lib-e2e/src/integration/test-ui-lib/test-ui-lib.spec.${fileExt}`;
if (!testConfig.plainJS) {
// hacky, but we should do this check only if we run with TypeScript,
// detecting component props in plain JS is "not possible"
describe('component with properties', () => {
beforeEach(async () => {
appTree = await createTestUILib('test-ui-lib', testConfig.plainJS);
appTree.write(cmpPath, testConfig.testCmpSrcWithProps);
await componentCypressSpecGenerator(appTree, {
componentPath: `lib/test-ui-lib.${fileCmpExt}`,
project: 'test-ui-lib',
js: testConfig.plainJS,
});
});
it('should properly set up the spec', () => {
expect(
formatFile`${appTree.read(cypressStorySpecFilePath, 'utf-8')}`
)
.toContain(formatFile`describe('test-ui-lib: Test component', () => {
beforeEach(() => cy.visit('/iframe.html?id=test--primary&args=name;displayAge:false;'));
it('should render the component', () => {
cy.get('h1').should('contain', 'Welcome to Test!');
});
})
`);
});
});
}
describe('component without properties', () => {
beforeEach(async () => {
appTree = await createTestUILib('test-ui-lib', testConfig.plainJS);
appTree.write(cmpPath, testConfig.testCmpSrcWithoutProps);
await componentCypressSpecGenerator(appTree, {
componentPath: `lib/test-ui-lib.${fileCmpExt}`,
project: 'test-ui-lib',
js: testConfig.plainJS,
});
});
it('should properly set up the spec', () => {
expect(formatFile`${appTree.read(cypressStorySpecFilePath, 'utf-8')}`)
.toContain(formatFile`describe('test-ui-lib: Test component', () => {
beforeEach(() => cy.visit('/iframe.html?id=test--primary'));
it('should render the component', () => {
cy.get('h1').should('contain', 'Welcome to Test!');
});
});
`);
});
});
});
});
it('should target the correct cypress suite', async () => {
appTree = await createTestUILib('test-ui-lib');
await applicationGenerator(appTree, {
e2eTestRunner: 'none',
linter: Linter.EsLint,
name: `other-e2e`,
skipFormat: true,
style: 'css',
unitTestRunner: 'none',
});
// since other-e2e isn't a real cypress project we mock the v10 cypress config
appTree.write('other-e2e/cypress.config.ts', `export default {}`);
await componentCypressSpecGenerator(appTree, {
componentPath: `lib/test-ui-lib.tsx`,
project: 'test-ui-lib',
cypressProject: 'other-e2e',
});
expect(
appTree.exists('other-e2e/src/e2e/test-ui-lib/test-ui-lib.cy.ts')
).toBeTruthy();
expect(
appTree.exists('test-ui-lib/src/e2e/test-ui-lib/test-ui-lib.cy.ts')
).toBeFalsy();
});
it('should generate a .spec.ts file with cypress.json', async () => {
appTree = await createTestUILib('test-ui-lib');
await applicationGenerator(appTree, {
e2eTestRunner: 'none',
linter: Linter.EsLint,
name: `other-e2e`,
skipFormat: true,
style: 'css',
unitTestRunner: 'none',
});
appTree.delete(`other-e2e/cypress.config.ts`);
appTree.write(`other-e2e/cypress.json`, '{}');
await componentCypressSpecGenerator(appTree, {
componentPath: `lib/test-ui-lib.tsx`,
project: 'test-ui-lib',
cypressProject: 'other-e2e',
});
expect(
appTree.exists(
'other-e2e/src/integration/test-ui-lib/test-ui-lib.spec.ts'
)
).toBeTruthy();
expect(
appTree.exists(
'test-ui-lib/src/integration/test-ui-lib/test-ui-lib.spec.ts'
)
).toBeFalsy();
});
});
export async function createTestUILib(
libName: string,
plainJS = false
): Promise<Tree> {
let appTree = createTreeWithEmptyWorkspace();
await libraryGenerator(appTree, {
name: libName,
linter: Linter.EsLint,
js: plainJS,
component: true,
skipFormat: true,
skipTsConfig: false,
style: 'css',
unitTestRunner: 'jest',
projectNameAndRootFormat: 'as-provided',
});
// create some Nx app that we'll use to generate the cypress
// spec into it. We don't need a real Cypress setup
await applicationGenerator(appTree, {
js: plainJS,
e2eTestRunner: 'none',
linter: Linter.EsLint,
name: `${libName}-e2e`,
skipFormat: true,
style: 'css',
unitTestRunner: 'none',
projectNameAndRootFormat: 'as-provided',
});
return appTree;
}

View File

@ -1,184 +0,0 @@
import {
formatFiles,
generateFiles,
getProjects,
joinPathFragments,
Tree,
} from '@nx/devkit';
import { basename, join } from 'path';
import type * as ts from 'typescript';
import {
findExportDeclarationsForJsx,
getComponentNode,
parseComponentPropsInfo,
} from '../../utils/ast-utils';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
let tsModule: typeof import('typescript');
export interface CreateComponentSpecFileSchema {
project: string;
componentPath: string;
js?: boolean;
cypressProject?: string;
skipFormat?: boolean;
}
export async function componentCypressGenerator(
host: Tree,
schema: CreateComponentSpecFileSchema
) {
createComponentSpecFile(host, schema);
if (!schema.skipFormat) {
await formatFiles(host);
}
}
// TODO: candidate to refactor with the angular component story
export function getArgsDefaultValue(property: ts.SyntaxKind): string {
if (!tsModule) {
tsModule = ensureTypescript();
}
const typeNameToDefault: Record<number, any> = {
[tsModule.SyntaxKind.StringKeyword]: '',
[tsModule.SyntaxKind.NumberKeyword]: 0,
[tsModule.SyntaxKind.BooleanKeyword]: false,
};
const resolvedValue = typeNameToDefault[property];
if (resolvedValue === undefined) {
return '';
} else if (typeof resolvedValue === 'string') {
return resolvedValue.replace(/\s/g, '+');
} else {
return resolvedValue;
}
}
export function createComponentSpecFile(
tree: Tree,
{ project, componentPath, js, cypressProject }: CreateComponentSpecFileSchema
) {
if (!tsModule) {
tsModule = ensureTypescript();
}
const e2eProjectName = cypressProject || `${project}-e2e`;
const projects = getProjects(tree);
const e2eProject = projects.get(e2eProjectName);
// cypress >= v10 will have a cypress.config.ts < v10 will have a cypress.json
const isCypressV10 = tree.exists(join(e2eProject.root, 'cypress.config.ts'));
const e2eLibIntegrationFolderPath = join(
e2eProject.sourceRoot,
isCypressV10 ? 'e2e' : 'integration'
);
const proj = projects.get(project);
const componentFilePath = joinPathFragments(proj.sourceRoot, componentPath);
const componentName = componentFilePath
.slice(componentFilePath.lastIndexOf('/') + 1)
.replace('.tsx', '')
.replace('.jsx', '')
.replace('.js', '');
const contents = tree.read(componentFilePath, 'utf-8');
if (contents === null) {
throw new Error(`Failed to read ${componentFilePath}`);
}
const sourceFile = tsModule.createSourceFile(
componentFilePath,
contents,
tsModule.ScriptTarget.Latest,
true
);
const cmpDeclaration = getComponentNode(sourceFile);
if (!cmpDeclaration) {
const componentNodes = findExportDeclarationsForJsx(sourceFile);
if (componentNodes?.length) {
componentNodes.forEach((declaration) => {
findPropsAndGenerateFileForCypress(
tree,
sourceFile,
declaration,
e2eLibIntegrationFolderPath,
componentName,
project,
js,
true
);
});
} else {
throw new Error(
`Could not find any React component in file ${componentFilePath}`
);
}
} else {
findPropsAndGenerateFileForCypress(
tree,
sourceFile,
cmpDeclaration,
e2eLibIntegrationFolderPath,
componentName,
project,
js
);
}
}
function findPropsAndGenerateFileForCypress(
tree: Tree,
sourceFile: ts.SourceFile,
cmpDeclaration: ts.Node,
e2eLibIntegrationFolderPath: string,
componentName: string,
project: string,
js: boolean,
fromNodeArray?: boolean
) {
const info = parseComponentPropsInfo(sourceFile, cmpDeclaration);
let props: {
name: string;
defaultValue: any;
}[] = [];
if (info) {
if (!tsModule) {
tsModule = ensureTypescript();
}
props = info.props.map((member) => {
return {
name: (member.name as ts.Identifier).text,
defaultValue: tsModule.isBindingElement(member)
? getArgsDefaultValue(member.kind)
: getArgsDefaultValue(member.type.kind),
};
});
}
const isCypressV10 = basename(e2eLibIntegrationFolderPath) === 'e2e';
const cyFilePrefix = isCypressV10 ? 'cy' : 'spec';
generateFiles(
tree,
joinPathFragments(__dirname, './files'),
`${e2eLibIntegrationFolderPath}/${
fromNodeArray
? componentName + '--' + (cmpDeclaration as any).name.text
: componentName
}`,
{
projectName: project,
componentName,
componentSelector: (cmpDeclaration as any).name.text,
props,
fileExt: js ? `${cyFilePrefix}.js` : `${cyFilePrefix}.ts`,
}
);
}
export default componentCypressGenerator;

View File

@ -1,13 +0,0 @@
describe('<%=projectName%>: <%= componentSelector %> component', () => {
beforeEach(() => cy.visit('/iframe.html?id=<%= componentSelector.toLowerCase() %>--primary<% if ( props && props.length > 0 ) { %>&args=<% } %><%
for(let prop of props) {
%><%=prop.name%><%
if(prop.defaultValue !== undefined && (prop.defaultValue || prop.defaultValue === false)) {
%>:<%=prop.defaultValue%><%
} %>;<%
}%>'));
it('should render the component', () => {
cy.get('h1').should('contain', 'Welcome to <%=componentSelector%>!');
});
});

View File

@ -1,45 +0,0 @@
{
"$schema": "https://json-schema.org/schema",
"cli": "nx",
"$id": "NxReactComponentCypressSpec",
"title": "Create component Cypress spec",
"description": "Create a Storybook Cypress spec for a UI component that has a story.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20.",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The project name for which to generate tests.",
"examples": ["shared-ui-component"],
"$default": {
"$source": "projectName",
"index": 0
},
"x-prompt": "What's name of the project for which to generate tests?",
"x-priority": "important"
},
"componentPath": {
"type": "string",
"description": "Relative path to the component file from the library root?",
"examples": ["lib/components"],
"x-prompt": "What's path of the component relative to the project's lib root for which to generate a test?",
"x-priority": "important"
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. By default, inferred from `project`."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
}
},
"required": ["project", "componentPath"]
}

View File

@ -17,16 +17,6 @@
"x-prompt": "For which project do you want to generate stories?",
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate `*.spec.ts` files in the cypress e2e app generated by the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `project` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"interactionTests": {
"type": "boolean",
"description": "Set up Storybook interaction tests.",

View File

@ -1,21 +1,15 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/eslint';
import applicationGenerator from '../application/application';
import storiesGenerator 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');
describe('react:stories for applications', () => {
let appTree: Tree;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
appTree = await createTestUIApp('test-ui-app');
// create another component

View File

@ -3,7 +3,6 @@ import 'nx/src/internal-testing-utils/mock-project-graph';
import { Tree } from '@nx/devkit';
import storiesGenerator from './stories';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import applicationGenerator from '../application/application';
import { Linter } from '@nx/eslint';
import libraryGenerator from '../library/library';
@ -244,18 +243,5 @@ export async function createTestUILib(
projectNameAndRootFormat: 'as-provided',
});
// create some Nx app that we'll use to generate the cypress
// spec into it. We don't need a real Cypress setup
await applicationGenerator(appTree, {
e2eTestRunner: 'none',
linter: Linter.EsLint,
skipFormat: true,
style: 'css',
unitTestRunner: 'none',
name: `${libName}-e2e`,
js: plainJS,
projectNameAndRootFormat: 'as-provided',
});
return appTree;
}

View File

@ -1,5 +1,4 @@
import componentStoryGenerator from '../component-story/component-story';
import componentCypressSpecGenerator from '../component-cypress-spec/component-cypress-spec';
import {
findExportDeclarationsForJsx,
getComponentNode,
@ -11,7 +10,6 @@ import {
GeneratorCallback,
getProjects,
joinPathFragments,
logger,
ProjectConfiguration,
runTasksInSerial,
Tree,
@ -30,8 +28,6 @@ export interface StorybookStoriesSchema {
js?: boolean;
ignorePaths?: string[];
skipFormat?: boolean;
cypressProject?: string;
generateCypressSpecs?: boolean;
}
export async function projectRootPath(
@ -88,8 +84,6 @@ export async function createAllStories(
js: boolean,
projects: Map<string, ProjectConfiguration>,
projectConfiguration: ProjectConfiguration,
generateCypressSpecs?: boolean,
cypressProject?: string,
ignorePaths?: string[]
) {
const { isTheFileAStory } = await import('@nx/storybook/src/utils/utilities');
@ -123,15 +117,6 @@ export async function createAllStories(
}
});
const e2eProjectName = cypressProject || `${projectName}-e2e`;
const e2eProject = projects.get(e2eProjectName);
if (generateCypressSpecs && !e2eProject) {
logger.info(
`There was no e2e project "${e2eProjectName}" found, so cypress specs will not be generated. Pass "--cypressProject" to specify a different e2e project name`
);
}
await Promise.all(
componentPaths.map(async (componentPath) => {
const relativeCmpDir = componentPath.replace(join(sourceRoot, '/'), '');
@ -146,16 +131,6 @@ export async function createAllStories(
skipFormat: true,
interactionTests,
});
if (generateCypressSpecs && e2eProject) {
await componentCypressSpecGenerator(tree, {
project: projectName,
componentPath: relativeCmpDir,
js,
cypressProject,
skipFormat: true,
});
}
})
);
}
@ -174,8 +149,6 @@ export async function storiesGenerator(
schema.js,
projects,
projectConfiguration,
schema.generateCypressSpecs,
schema.cypressProject,
schema.ignorePaths
);

View File

@ -1,5 +1,3 @@
// TODO(katerina): Nx 19 -> remove Cypress
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import { logger, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/eslint';
@ -7,9 +5,7 @@ import applicationGenerator from '../application/application';
import componentGenerator from '../component/component';
import libraryGenerator from '../library/library';
import storybookConfigurationGenerator from './configuration';
// 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');
// nested code imports graph from the repo, which might have innacurate graph version
jest.mock('nx/src/project-graph/project-graph', () => ({
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
@ -20,11 +16,8 @@ jest.mock('nx/src/project-graph/project-graph', () => ({
describe('react:storybook-configuration', () => {
let appTree;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
jest.spyOn(logger, 'warn').mockImplementation(() => {});
jest.spyOn(logger, 'debug').mockImplementation(() => {});
});

View File

@ -14,23 +14,11 @@ import {
import { nxVersion, reactViteVersion } from '../../utils/versions';
async function generateStories(host: Tree, schema: StorybookConfigureSchema) {
// TODO(katerina): Nx 19 -> remove Cypress
ensurePackage('@nx/cypress', nxVersion);
const { getE2eProjectName } = await import(
'@nx/cypress/src/utils/project-name'
);
const projectConfig = readProjectConfiguration(host, schema.project);
const cypressProject = getE2eProjectName(
schema.project,
projectConfig.root,
schema.cypressDirectory
);
await storiesGenerator(host, {
project: schema.project,
generateCypressSpecs:
schema.configureCypress && schema.generateCypressSpecs,
js: schema.js,
cypressProject,
ignorePaths: schema.ignorePaths,
skipFormat: true,
interactionTests: schema.interactionTests ?? true,
@ -85,10 +73,8 @@ export async function storybookConfigurationGeneratorInternal(
const installTask = await configurationGenerator(host, {
project: schema.project,
configureCypress: schema.configureCypress,
js: schema.js,
linter: schema.linter,
cypressDirectory: schema.cypressDirectory,
tsConfiguration: schema.tsConfiguration ?? true, // default is true
interactionTests: schema.interactionTests ?? true, // default is true
configureStaticServe: schema.configureStaticServe,

View File

@ -9,8 +9,5 @@ export interface StorybookConfigureSchema {
linter?: Linter | LinterType;
ignorePaths?: string[];
configureStaticServe?: boolean;
configureCypress?: boolean;
generateCypressSpecs?: boolean;
cypressDirectory?: string;
addPlugin?: boolean;
}

View File

@ -26,11 +26,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate test files in the Cypress E2E app generated by the `cypress-configure` generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"js": {
"type": "boolean",
"description": "Generate JavaScript story files rather than TypeScript story files.",

View File

@ -3,13 +3,10 @@ import { Linter, LinterType } from '@nx/eslint';
export interface StorybookConfigurationSchema {
project: string;
interactionTests?: boolean;
configureCypress: boolean;
generateStories?: boolean;
generateCypressSpecs?: boolean;
js?: boolean;
tsConfiguration?: boolean;
linter?: Linter | LinterType;
cypressDirectory?: string;
ignorePaths?: string[];
configureTestRunner?: boolean;
configureStaticServe?: boolean;

View File

@ -26,11 +26,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"generateStories": {
"type": "boolean",
"description": "Automatically generate `*.stories.ts` files for components declared in this project?",
@ -38,11 +33,6 @@
"default": true,
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate test files in the Cypress E2E app generated by the `cypress-configure` generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"configureStaticServe": {
"type": "boolean",
"description": "Specifies whether to configure a static file server target for serving storybook. Helpful for speeding up CI build/test times.",
@ -50,11 +40,6 @@
"default": true,
"x-priority": "important"
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Placed at the root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"js": {
"type": "boolean",
"description": "Generate JavaScript story files rather than TypeScript story files.",

View File

@ -22,7 +22,6 @@ describe('Storybook Configuration', () => {
// ACT
await storybookConfigurationGenerator(tree, {
project: 'storybook-test',
configureCypress: false,
configureStaticServe: false,
generateStories: true,
addPlugin: true,

View File

@ -17,7 +17,7 @@ import { nxVersion, storybookVersion } from '../../utils/versions';
import configurationGenerator from './configuration';
import * as variousProjects from './test-configs/various-projects.json';
// nested code imports graph from the repo, which might have innacurate graph version
// nested code imports graph from the repo, which might have inaccurate graph version
jest.mock('nx/src/project-graph/project-graph', () => ({
...jest.requireActual<any>('nx/src/project-graph/project-graph'),
createProjectGraphAsync: jest

View File

@ -10,7 +10,6 @@ import {
} from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { cypressProjectGenerator } from '../cypress-project/cypress-project';
import { StorybookConfigureSchema } from './schema';
import { initGenerator } from '../init/init';
@ -29,7 +28,6 @@ import {
findMetroConfig,
findNextConfig,
findViteConfig,
getE2EProjectName,
projectIsRootProjectInStandaloneWorkspace,
updateLintConfig,
} from './lib/util-functions';
@ -197,29 +195,6 @@ export async function configurationGeneratorInternal(
devDeps['storybook'] = storybookVersion;
}
// TODO(katerina): Nx 19 -> remove Cypress
if (schema.configureCypress) {
const e2eProject = await getE2EProjectName(tree, schema.project);
if (!e2eProject) {
const cypressTask = await cypressProjectGenerator(tree, {
name: schema.project,
js: schema.js,
linter: schema.linter,
directory: schema.cypressDirectory,
standaloneConfig: schema.standaloneConfig,
ciTargetName: schema.configureStaticServe
? 'static-storybook'
: undefined,
skipFormat: true,
});
tasks.push(cypressTask);
} else {
logger.warn(
`There is already an e2e project setup for ${schema.project}, called ${e2eProject}.`
);
}
}
if (schema.tsConfiguration) {
devDeps['ts-node'] = tsNodeVersion;
}

View File

@ -681,36 +681,6 @@ export function rootFileIsTs(
}
}
export async function getE2EProjectName(
tree: Tree,
mainProject: string
): Promise<string | undefined> {
let e2eProject: string;
const graph = await createProjectGraphAsync();
forEachExecutorOptions(
tree,
'@nx/cypress:cypress',
(options, projectName) => {
if (e2eProject) {
return;
}
if (options['devServerTarget']) {
const { project, target } = parseTargetString(
options['devServerTarget'],
graph
);
if (
(project === mainProject && target === 'serve') ||
(project === mainProject && target === 'storybook')
) {
e2eProject = projectName;
}
}
}
);
return e2eProject;
}
export function findViteConfig(
tree: Tree,
projectRoot: string

View File

@ -11,14 +11,6 @@ export interface StorybookConfigureSchema {
standaloneConfig?: boolean;
configureStaticServe?: boolean;
skipFormat?: boolean;
/**
* @deprecated Use interactionTests instead. This option will be removed in v20.
*/
configureCypress?: boolean;
/**
* @deprecated Use interactionTests instead. This option will be removed in v20.
*/
cypressDirectory?: string;
addPlugin?: boolean;
/**

View File

@ -25,16 +25,6 @@
"alias": ["configureTestRunner"],
"default": true
},
"configureCypress": {
"type": "boolean",
"description": "Run the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressDirectory": {
"type": "string",
"description": "A directory where the Cypress project will be placed. Added at root by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",

View File

@ -17,16 +17,6 @@
"x-prompt": "For which project do you want to generate stories?",
"x-priority": "important"
},
"generateCypressSpecs": {
"type": "boolean",
"description": "Automatically generate `*.spec.ts` files in the cypress e2e app generated by the cypress-configure generator.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"cypressProject": {
"type": "string",
"description": "The Cypress project to generate the stories under. This is inferred from `project` by default.",
"x-deprecated": "Use interactionTests instead. This option will be removed in v20."
},
"interactionTests": {
"type": "boolean",
"description": "Set up Storybook interaction tests.",

View File

@ -21,8 +21,6 @@ export interface StorybookStoriesSchema {
js?: boolean;
ignorePaths?: string[];
skipFormat?: boolean;
cypressProject?: string;
generateCypressSpecs?: boolean;
}
export async function createAllStories(