Caleb Ukle 8154191eb1
feat(testing): Cypress 10 and component testing support (#9201)
* feat(testing): add generator to aid in the migration to cypress 10

cypress 10 introduces a new configuration format and new layout that requires update settings and
files for e2e projects

* feat(testing): cypress component tests for react/next

initial work for cypress component tests for react and next

* feat(testing): add support for v10 e2e cypress projects

create the correct files for cypress projects >v10 and reorganize tests based on version to allow
easier parsing of tests

* feat(testing): add utils for modifying cypress v10 config

provide ts transformers to take in an existing cypress config and update/add properties within the
given configuration

* fix(testing): fix tests affected by the cypress v10 changes

update tests to assert the correct files/folders/file contents due to the cypress changes in v10

* cleanup(testing): move cypress component testing plugins into the respective packages

move the plugins into out of cypress plugins into the specific vertical plugin to prevent issues
with circular refs

* cleanup(testing): bump cypress version

bump to latest cypress v10 release

* docs(testing): update docs for cypress 10

update cypress docs to include info about component testing and migration to cypress v10

* fix(repo): revert cypress version bump

keep v9 of cypress installed for nx repo until v10 release

* fix(testing): update cypress gen tsconfig and infer targets with converter

* fix(testing): make sure tests use the cypress v10 (for the intermediate)

* fix(testing): update target name after feedback

* fix(testing): support multiple target w/custom configs for cypress v10 migration

* fix(testing): refactor cy component tests into seperate verticals

* feat(testing): create storybook cypress preset

* fix(testing): clean up cy v10 migration

* fix(testing): don't branch for cypress executor testingType

* fix(testing): move cy comp test generator to next

* fix(testing): bump cypress deps

* fix(testing): clean up cy component testing generators

* fix(testing): update cy component testing docs

* fix(testign): dep check. runtime plugin pulls from @nrwl/react

* fix(testing): move e2e into verticals

* fix(testing): address PR feedback

* fix(testing): clean up unit tests

* feat(angular): support migrating angular cli workspaces using cypress v10

* chore(testing): update e2e tests

* fix(testing): address pr feedback

* chore(testing): remove cypress component testing for next.js

* fix(testing): address pr feedback

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2022-07-08 14:34:00 -05:00

168 lines
4.3 KiB
TypeScript

import {
convertNxGenerator,
generateFiles,
getProjects,
joinPathFragments,
Tree,
} from '@nrwl/devkit';
import { basename, join } from 'path';
import * as ts from 'typescript';
import {
findExportDeclarationsForJsx,
getComponentNode,
getComponentPropsInterface,
} from '../../utils/ast-utils';
export interface CreateComponentSpecFileSchema {
project: string;
componentPath: string;
js?: boolean;
cypressProject?: string;
}
export function componentCypressGenerator(
host: Tree,
schema: CreateComponentSpecFileSchema
) {
createComponentSpecFile(host, schema);
}
// TODO: candidate to refactor with the angular component story
export function getArgsDefaultValue(property: ts.SyntaxKind): string {
const typeNameToDefault: Record<number, any> = {
[ts.SyntaxKind.StringKeyword]: '',
[ts.SyntaxKind.NumberKeyword]: 0,
[ts.SyntaxKind.BooleanKeyword]: false,
};
const resolvedValue = typeNameToDefault[property];
if (typeof 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
) {
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 = ts.createSourceFile(
componentFilePath,
contents,
ts.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 propsInterface = getComponentPropsInterface(sourceFile, cmpDeclaration);
let props: {
name: string;
defaultValue: any;
}[] = [];
if (propsInterface) {
props = propsInterface.members.map((member: ts.PropertySignature) => {
return {
name: (member.name as ts.Identifier).text,
defaultValue: 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;
export const componentCypressSchematic = convertNxGenerator(
componentCypressGenerator
);