<!-- 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` --> ## Current Behavior <!-- This is the behavior we have today --> The `@nx/storybook:cypress-project` generator expects there to be a defined target for e2e target. However, since the introduction of inference, this may not be the case. This results in the generator erroring ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> The `@nx/storybook:cypress-project` generator should handle inference and generate an appropriate cypress.config.ts file ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #21770
This commit is contained in:
parent
99543b5a01
commit
61129f49c2
@ -2841,7 +2841,7 @@
|
|||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
"/nx-api/storybook/generators/cypress-project": {
|
"/nx-api/storybook/generators/cypress-project": {
|
||||||
"description": "Add cypress e2e app to test a UI library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
"file": "generated/packages/storybook/generators/cypress-project.json",
|
"file": "generated/packages/storybook/generators/cypress-project.json",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"name": "cypress-project",
|
"name": "cypress-project",
|
||||||
|
|||||||
@ -2811,7 +2811,7 @@
|
|||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Add cypress e2e app to test a UI library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
"file": "generated/packages/storybook/generators/cypress-project.json",
|
"file": "generated/packages/storybook/generators/cypress-project.json",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"name": "cypress-project",
|
"name": "cypress-project",
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"$id": "cypress-configure",
|
"$id": "cypress-configure",
|
||||||
"title": "Cypress Configuration",
|
"title": "Cypress Configuration",
|
||||||
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
|
"x-deprecated": "Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
@ -53,7 +54,8 @@
|
|||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
"presets": []
|
"presets": []
|
||||||
},
|
},
|
||||||
"description": "Add cypress e2e app to test a UI library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
|
"x-deprecated": "Deprecated: Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"implementation": "/packages/storybook/src/generators/cypress-project/cypress-project.ts",
|
"implementation": "/packages/storybook/src/generators/cypress-project/cypress-project.ts",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|||||||
@ -18,7 +18,8 @@
|
|||||||
"cypress-project": {
|
"cypress-project": {
|
||||||
"factory": "./src/generators/cypress-project/cypress-project",
|
"factory": "./src/generators/cypress-project/cypress-project",
|
||||||
"schema": "./src/generators/cypress-project/schema.json",
|
"schema": "./src/generators/cypress-project/schema.json",
|
||||||
"description": "Add cypress e2e app to test a UI library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
|
"x-deprecated": "Deprecated: Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
||||||
"hidden": false
|
"hidden": false
|
||||||
},
|
},
|
||||||
"migrate-7": {
|
"migrate-7": {
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
import {
|
||||||
|
NxCypressE2EPresetOptions,
|
||||||
|
nxE2EPreset,
|
||||||
|
} from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
|
||||||
export function nxE2EStorybookPreset(filePath: string) {
|
export function nxE2EStorybookPreset(
|
||||||
|
filePath: string,
|
||||||
|
options?: NxCypressE2EPresetOptions
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
...nxE2EPreset(filePath),
|
...nxE2EPreset(filePath, options),
|
||||||
baseUrl: 'http://localhost:4400',
|
baseUrl: 'http://localhost:4400',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,22 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`@nx/storybook:cypress-project should generate a correct cypress.config.ts file when using inferred plugins 1`] = `
|
||||||
|
"import { defineConfig } from 'cypress';
|
||||||
|
import { nxE2EStorybookPreset } from '@nx/storybook/presets/cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: nxE2EStorybookPreset(__dirname, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'npx nx run test-ui-lib:storybook',
|
||||||
|
ci: 'npx nx run test-ui-lib:storybook:ci',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'npx nx run test-ui-lib:storybook:ci',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`@nx/storybook:cypress-project should generate files 1`] = `
|
exports[`@nx/storybook:cypress-project should generate files 1`] = `
|
||||||
"import { defineConfig } from 'cypress';
|
"import { defineConfig } from 'cypress';
|
||||||
import { nxE2EStorybookPreset } from '@nx/storybook/presets/cypress';
|
import { nxE2EStorybookPreset } from '@nx/storybook/presets/cypress';
|
||||||
|
|||||||
@ -1,4 +1,16 @@
|
|||||||
import { readJson, readProjectConfiguration, Tree } from '@nx/devkit';
|
jest.mock('nx/src/project-graph/plugins/loader', () => ({
|
||||||
|
...jest.requireActual('nx/src/project-graph/plugins/loader'),
|
||||||
|
loadNxPlugin: jest.fn().mockImplementation(() => {
|
||||||
|
return [Promise.resolve({}), () => {}];
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
import {
|
||||||
|
readJson,
|
||||||
|
readNxJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint';
|
import { Linter } from '@nx/eslint';
|
||||||
import { libraryGenerator } from '@nx/js';
|
import { libraryGenerator } from '@nx/js';
|
||||||
@ -60,4 +72,26 @@ describe('@nx/storybook:cypress-project', () => {
|
|||||||
tree.exists('apps/one/two/test-ui-lib-e2e/cypress.config.ts')
|
tree.exists('apps/one/two/test-ui-lib-e2e/cypress.config.ts')
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a correct cypress.config.ts file when using inferred plugins', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push('@nx/cypress/plugin');
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await cypressProjectGenerator(tree, {
|
||||||
|
name: 'test-ui-lib',
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists('apps/test-ui-lib-e2e/cypress.config.ts')).toBeTruthy();
|
||||||
|
const cypressConfig = tree.read(
|
||||||
|
'apps/test-ui-lib-e2e/cypress.config.ts',
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(cypressConfig).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import {
|
|||||||
formatFiles,
|
formatFiles,
|
||||||
generateFiles,
|
generateFiles,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
|
logger,
|
||||||
readJson,
|
readJson,
|
||||||
|
readNxJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
Tree,
|
Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
@ -43,6 +45,9 @@ export async function cypressProjectGeneratorInternal(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
schema: CypressConfigureSchema
|
schema: CypressConfigureSchema
|
||||||
) {
|
) {
|
||||||
|
logger.warn(
|
||||||
|
`Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.`
|
||||||
|
);
|
||||||
const { configurationGenerator } = ensurePackage<
|
const { configurationGenerator } = ensurePackage<
|
||||||
typeof import('@nx/cypress')
|
typeof import('@nx/cypress')
|
||||||
>('@nx/cypress', nxVersion);
|
>('@nx/cypress', nxVersion);
|
||||||
@ -73,7 +78,6 @@ export async function cypressProjectGeneratorInternal(
|
|||||||
project: projectName,
|
project: projectName,
|
||||||
js: schema.js,
|
js: schema.js,
|
||||||
linter: schema.linter,
|
linter: schema.linter,
|
||||||
directory: projectRoot,
|
|
||||||
devServerTarget: `${schema.name}:storybook`,
|
devServerTarget: `${schema.name}:storybook`,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
@ -84,12 +88,28 @@ export async function cypressProjectGeneratorInternal(
|
|||||||
schema.directory
|
schema.directory
|
||||||
);
|
);
|
||||||
removeUnneededFiles(tree, generatedCypressProjectName, schema.js);
|
removeUnneededFiles(tree, generatedCypressProjectName, schema.js);
|
||||||
addBaseUrlToCypressConfig(tree, generatedCypressProjectName);
|
|
||||||
updateAngularJsonBuilder(tree, {
|
const project = readProjectConfiguration(tree, generatedCypressProjectName);
|
||||||
e2eProjectName: generatedCypressProjectName,
|
if (project.targets.e2e && project.targets.e2e.options) {
|
||||||
targetProjectName: schema.name,
|
addBaseUrlToCypressConfig(tree, generatedCypressProjectName, project.root);
|
||||||
ciTargetName: schema.ciTargetName,
|
updateAngularJsonBuilder(tree, {
|
||||||
});
|
e2eProjectName: generatedCypressProjectName,
|
||||||
|
targetProjectName: schema.name,
|
||||||
|
ciTargetName: schema.ciTargetName,
|
||||||
|
});
|
||||||
|
} else if (hasCypressPlugin(tree)) {
|
||||||
|
generateCypressConfigForInferredPlugin(
|
||||||
|
tree,
|
||||||
|
generatedCypressProjectName,
|
||||||
|
project.root,
|
||||||
|
schema.name,
|
||||||
|
schema.ciTargetName
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to generate Cypress Project for Storybook project. Please report this issue at https://github.com/nrwl/nx/issues/new/choose`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!schema.skipFormat) {
|
if (!schema.skipFormat) {
|
||||||
await formatFiles(tree);
|
await formatFiles(tree);
|
||||||
@ -98,10 +118,19 @@ export async function cypressProjectGeneratorInternal(
|
|||||||
return cypressTask;
|
return cypressTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasCypressPlugin(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
return nxJson.plugins?.some((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/cypress/plugin'
|
||||||
|
: p.plugin === '@nx/cypress/plugin'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function removeUnneededFiles(tree: Tree, projectName: string, js: boolean) {
|
function removeUnneededFiles(tree: Tree, projectName: string, js: boolean) {
|
||||||
const { sourceRoot, root } = readProjectConfiguration(tree, projectName);
|
const { sourceRoot, root } = readProjectConfiguration(tree, projectName);
|
||||||
const fileType = js ? 'js' : 'ts';
|
const fileType = js ? 'js' : 'ts';
|
||||||
if (tree.exists(join(root, 'cypress.config.ts'))) {
|
if (tree.exists(joinPathFragments(root, 'cypress.config.ts'))) {
|
||||||
safeFileDelete(tree, `${sourceRoot}/e2e/app.cy.${fileType}`);
|
safeFileDelete(tree, `${sourceRoot}/e2e/app.cy.${fileType}`);
|
||||||
safeFileDelete(tree, `${sourceRoot}/support/app.po.${fileType}`);
|
safeFileDelete(tree, `${sourceRoot}/support/app.po.${fileType}`);
|
||||||
} else {
|
} else {
|
||||||
@ -110,10 +139,44 @@ function removeUnneededFiles(tree: Tree, projectName: string, js: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBaseUrlToCypressConfig(tree: Tree, projectName: string) {
|
function generateCypressConfigForInferredPlugin(
|
||||||
const projectRoot = readProjectConfiguration(tree, projectName).root;
|
tree: Tree,
|
||||||
const cypressJson = join(projectRoot, 'cypress.json');
|
projectName: string,
|
||||||
const cypressTs = join(projectRoot, 'cypress.config.ts');
|
projectRoot: string,
|
||||||
|
targetProjectName: string,
|
||||||
|
ciTargetName?: string
|
||||||
|
) {
|
||||||
|
const cypressJson = joinPathFragments(projectRoot, 'cypress.json');
|
||||||
|
const cypressTs = joinPathFragments(projectRoot, 'cypress.config.ts');
|
||||||
|
|
||||||
|
if (tree.exists(cypressJson)) {
|
||||||
|
tree.delete(cypressJson);
|
||||||
|
}
|
||||||
|
if (tree.exists(cypressTs)) {
|
||||||
|
// cypress >= v10
|
||||||
|
tree.delete(cypressTs);
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
join(__dirname, 'files', 'inferred-target'),
|
||||||
|
projectRoot,
|
||||||
|
{
|
||||||
|
defaultWebServerCommand: `npx nx run ${targetProjectName}:storybook`,
|
||||||
|
ciWebServerCommand: ciTargetName
|
||||||
|
? `npx nx run ${targetProjectName}:${ciTargetName}:ci`
|
||||||
|
: `npx nx run ${targetProjectName}:storybook:ci`,
|
||||||
|
tpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBaseUrlToCypressConfig(
|
||||||
|
tree: Tree,
|
||||||
|
projectName: string,
|
||||||
|
projectRoot: string
|
||||||
|
) {
|
||||||
|
const cypressJson = joinPathFragments(projectRoot, 'cypress.json');
|
||||||
|
const cypressTs = joinPathFragments(projectRoot, 'cypress.config.ts');
|
||||||
// TODO(caleb): remove this when cypress < v10 is deprecated
|
// TODO(caleb): remove this when cypress < v10 is deprecated
|
||||||
|
|
||||||
if (tree.exists(cypressJson)) {
|
if (tree.exists(cypressJson)) {
|
||||||
@ -125,9 +188,14 @@ function addBaseUrlToCypressConfig(tree: Tree, projectName: string) {
|
|||||||
} else if (tree.exists(cypressTs)) {
|
} else if (tree.exists(cypressTs)) {
|
||||||
// cypress >= v10
|
// cypress >= v10
|
||||||
tree.delete(cypressTs);
|
tree.delete(cypressTs);
|
||||||
generateFiles(tree, join(__dirname, 'files'), projectRoot, {
|
generateFiles(
|
||||||
tpl: '',
|
tree,
|
||||||
});
|
join(__dirname, 'files', 'explicit-target'),
|
||||||
|
projectRoot,
|
||||||
|
{
|
||||||
|
tpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +208,7 @@ function updateAngularJsonBuilder(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const project = readProjectConfiguration(tree, opts.e2eProjectName);
|
const project = readProjectConfiguration(tree, opts.e2eProjectName);
|
||||||
const e2eTarget = project.targets.e2e;
|
const e2eTarget = project.targets.e2e ?? {};
|
||||||
project.targets.e2e = {
|
project.targets.e2e = {
|
||||||
...e2eTarget,
|
...e2eTarget,
|
||||||
options: <any>{
|
options: <any>{
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
import { nxE2EStorybookPreset } from '@nx/storybook/presets/cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: nxE2EStorybookPreset(__dirname, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
webServerCommands: {
|
||||||
|
default: "<%= defaultWebServerCommand %>",
|
||||||
|
ci: "<%= ciWebServerCommand %>"
|
||||||
|
},
|
||||||
|
ciWebServerCommand: "<%= ciWebServerCommand %>"
|
||||||
|
}),
|
||||||
|
});
|
||||||
@ -4,6 +4,7 @@
|
|||||||
"$id": "cypress-configure",
|
"$id": "cypress-configure",
|
||||||
"title": "Cypress Configuration",
|
"title": "Cypress Configuration",
|
||||||
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
"description": "Add cypress E2E app to test a ui library that is set up for Storybook.",
|
||||||
|
"x-deprecated": "Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user