fix(angular): do not force explicit targets for separate e2e projects (#21865)

This commit is contained in:
Leosvel Pérez Espinosa 2024-02-20 15:57:28 +01:00 committed by GitHub
parent fe17fc3287
commit ca3965fcf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 83 additions and 203 deletions

View File

@ -4,6 +4,7 @@ import {
cleanupProject,
killProcessAndPorts,
newProject,
readFile,
readJson,
runCLI,
runCommandUntil,
@ -376,10 +377,22 @@ describe('Angular Module Federation', () => {
const buildRemoteOutput = runCLI(`build ${remote}`);
expect(buildRemoteOutput).toContain('Successfully ran target build');
// increase default timeout for the Cypress web server, MF apps can take longer to start
const cypressConfig = readFile(`${host}-e2e/cypress.config.ts`);
updateFile(
`${host}-e2e/cypress.config.ts`,
cypressConfig.replace(
`nxE2EPreset(__filename, {`,
`nxE2EPreset(__filename, {
webServerConfig: {
timeout: 30000,
},`
)
);
if (runE2ETests('cypress')) {
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e --no-watch`,
(output) => output.includes('All specs passed!')
const e2eProcess = await runCommandUntil(`e2e ${host}-e2e`, (output) =>
output.includes('All specs passed!')
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
}
@ -468,10 +481,22 @@ describe('Angular Module Federation', () => {
const buildRemoteOutput = runCLI(`build ${remote}`);
expect(buildRemoteOutput).toContain('Successfully ran target build');
// increase default timeout for the Cypress web server, MF apps can take longer to start
const cypressConfig = readFile(`${host}-e2e/cypress.config.ts`);
updateFile(
`${host}-e2e/cypress.config.ts`,
cypressConfig.replace(
`nxE2EPreset(__filename, {`,
`nxE2EPreset(__filename, {
webServerConfig: {
timeout: 30000,
},`
)
);
if (runE2ETests('cypress')) {
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e --no-watch`,
(output) => output.includes('All specs passed!')
const e2eProcess = await runCommandUntil(`e2e ${host}-e2e`, (output) =>
output.includes('All specs passed!')
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
}

View File

@ -1,8 +1,8 @@
import { ProjectConfiguration, readNxJson, Tree } from '@nx/devkit';
import {
ProjectConfiguration,
Tree,
addProjectConfiguration,
readJson,
readProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import * as linter from '@nx/eslint';
@ -32,7 +32,6 @@ describe('addLinting generator', () => {
projectName: appProjectName,
projectRoot: appProjectRoot,
skipFormat: true,
addPlugin: true,
});
expect(linter.lintProjectGenerator).toHaveBeenCalled();
@ -44,7 +43,6 @@ describe('addLinting generator', () => {
projectName: appProjectName,
projectRoot: appProjectRoot,
skipFormat: true,
addPlugin: true,
});
const { devDependencies } = readJson(tree, 'package.json');
@ -61,31 +59,9 @@ describe('addLinting generator', () => {
projectName: appProjectName,
projectRoot: appProjectRoot,
skipFormat: true,
addPlugin: true,
});
const eslintConfig = readJson(tree, `${appProjectRoot}/.eslintrc.json`);
expect(eslintConfig).toMatchSnapshot();
});
it('should add @nx/eslint/plugin', async () => {
await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
skipFormat: true,
addPlugin: true,
});
const nxJson = readNxJson(tree);
expect(
nxJson.plugins.find((p) => {
if (typeof p === 'string') {
return p === '@nx/eslint/plugin';
} else {
return p.plugin === '@nx/eslint/plugin';
}
})
).toBeTruthy();
});
});

View File

@ -36,7 +36,7 @@ export async function addLintingGenerator(
setParserOptionsProject: options.setParserOptionsProject,
skipFormat: true,
rootProject: rootProject,
addPlugin: options.addPlugin,
addPlugin: false,
addExplicitTargets: true,
});
tasks.push(lintTask);

View File

@ -6,6 +6,4 @@ export interface AddLintingGeneratorSchema {
skipFormat?: boolean;
skipPackageJson?: boolean;
unitTestRunner?: string;
addPlugin?: boolean;
addExplicitTargets?: boolean;
}

View File

@ -314,27 +314,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"root": "apps/my-dir/my-app-e2e",
"sourceRoot": "apps/my-dir/my-app-e2e/src",
"tags": [],
"targets": {
"e2e": {
"configurations": {
"ci": {
"devServerTarget": "my-dir-my-app:serve-static",
},
"production": {
"devServerTarget": "my-dir-my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "apps/my-dir/my-app-e2e/cypress.config.ts",
"devServerTarget": "my-dir-my-app:serve:development",
"testingType": "e2e",
},
},
"lint": {
"executor": "@nx/eslint:lint",
},
},
"targets": {},
}
`;
@ -533,27 +513,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"root": "apps/my-app-e2e",
"sourceRoot": "apps/my-app-e2e/src",
"tags": [],
"targets": {
"e2e": {
"configurations": {
"ci": {
"devServerTarget": "my-app:serve-static",
},
"production": {
"devServerTarget": "my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "apps/my-app-e2e/cypress.config.ts",
"devServerTarget": "my-app:serve:development",
"testingType": "e2e",
},
},
"lint": {
"executor": "@nx/eslint:lint",
},
},
"targets": {},
}
`;
@ -1047,27 +1007,7 @@ exports[`app nested should create project configs 2`] = `
"root": "my-dir/my-app-e2e",
"sourceRoot": "my-dir/my-app-e2e/src",
"tags": [],
"targets": {
"e2e": {
"configurations": {
"ci": {
"devServerTarget": "my-app:serve-static",
},
"production": {
"devServerTarget": "my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "my-dir/my-app-e2e/cypress.config.ts",
"devServerTarget": "my-app:serve:development",
"testingType": "e2e",
},
},
"lint": {
"executor": "@nx/eslint:lint",
},
},
"targets": {},
}
`;
@ -1179,27 +1119,7 @@ exports[`app not nested should create project configs 2`] = `
"root": "my-app-e2e",
"sourceRoot": "my-app-e2e/src",
"tags": [],
"targets": {
"e2e": {
"configurations": {
"ci": {
"devServerTarget": "my-app:serve-static",
},
"production": {
"devServerTarget": "my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "my-app-e2e/cypress.config.ts",
"devServerTarget": "my-app:serve:development",
"testingType": "e2e",
},
},
"lint": {
"executor": "@nx/eslint:lint",
},
},
"targets": {},
}
`;

View File

@ -24,8 +24,7 @@ import {
import { generateTestApplication } from '../utils/testing';
import type { Schema } from './schema';
// 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
// need to mock cypress otherwise it'll use installed version in this repo's package.json
jest.mock('@nx/cypress/src/utils/cypress-version');
jest.mock('enquirer');
jest.mock('@nx/devkit', () => {
@ -43,7 +42,7 @@ describe('app', () => {
> = installedCypressVersion as never;
beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10);
mockedInstalledCypressVersion.mockReturnValue(null);
// @ts-ignore
enquirer.prompt = jest
.fn()
@ -530,7 +529,7 @@ describe('app', () => {
describe('--linter', () => {
describe('eslint', () => {
it('should add lint target', async () => {
it('should add lint target to application', async () => {
await generateApp(appTree, 'my-app', { linter: Linter.EsLint });
expect(readProjectConfiguration(appTree, 'my-app').targets.lint)
.toMatchInlineSnapshot(`
@ -538,12 +537,40 @@ describe('app', () => {
"executor": "@nx/eslint:lint",
}
`);
expect(readProjectConfiguration(appTree, 'my-app-e2e').targets.lint)
.toMatchInlineSnapshot(`
});
it('should add eslint plugin and no lint target to e2e project', async () => {
await generateApp(appTree, 'my-app', { linter: Linter.EsLint });
expect(readNxJson(appTree).plugins).toMatchInlineSnapshot(`
[
{
"executor": "@nx/eslint:lint",
}
"options": {
"componentTestingTargetName": "component-test",
"targetName": "e2e",
},
"plugin": "@nx/cypress/plugin",
},
{
"options": {
"targetName": "lint",
},
"plugin": "@nx/eslint/plugin",
},
]
`);
expect(
readProjectConfiguration(appTree, 'my-app-e2e').targets.lint
).toBeUndefined();
});
it('should not add eslint plugin when no e2e test runner', async () => {
await generateApp(appTree, 'my-app', {
linter: Linter.EsLint,
e2eTestRunner: E2eTestRunner.None,
});
expect(readNxJson(appTree).plugins).toBeUndefined();
});
it('should add valid eslint JSON configuration which extends from Nx presets', async () => {
@ -1262,7 +1289,6 @@ async function generateApp(
unitTestRunner: UnitTestRunner.Jest,
linter: Linter.EsLint,
standalone: false,
addPlugin: false,
...options,
});
}

View File

@ -34,7 +34,6 @@ export async function applicationGenerator(
): Promise<GeneratorCallback> {
return await applicationGeneratorInternal(tree, {
projectNameAndRootFormat: 'derived',
addPlugin: false,
...schema,
});
}

View File

@ -14,6 +14,9 @@ import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema';
export async function addE2e(tree: Tree, options: NormalizedSchema) {
// since e2e are separate projects, default to adding plugins
const addPlugin = process.env.NX_ADD_PLUGINS !== 'false';
if (options.e2eTestRunner === 'cypress') {
// TODO: This can call `@nx/web:static-config` generator when ready
addFileServerTarget(tree, options, 'serve-static');
@ -34,8 +37,7 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
devServerTarget: `${options.name}:serve:development`,
baseUrl: 'http://localhost:4200',
rootProject: options.rootProject,
addPlugin: false,
addExplicitTargets: false, // since e2e is a separate project, use inferred targets
addPlugin,
});
} else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator: playwrightConfigurationGenerator } =
@ -63,7 +65,7 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
}`,
webServerAddress: `http://localhost:${options.port ?? 4200}`,
rootProject: options.rootProject,
addPlugin: false,
addPlugin,
});
}
}

View File

@ -16,6 +16,5 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
skipPackageJson: options.skipPackageJson,
unitTestRunner: options.unitTestRunner,
skipFormat: true,
addPlugin: false,
});
}

View File

@ -10,7 +10,6 @@ export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
projectRoot: options.appProjectRoot,
skipPackageJson: options.skipPackageJson,
strict: options.strict,
addPlugin: false,
});
}
}

View File

@ -26,7 +26,6 @@ export async function normalizeOptions(
});
options.rootProject = appProjectRoot === '.';
options.projectNameAndRootFormat = projectNameAndRootFormat;
options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false';
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;

View File

@ -31,5 +31,4 @@ export interface Schema {
minimal?: boolean;
bundler?: 'webpack' | 'esbuild';
ssr?: boolean;
addPlugin?: boolean;
}

View File

@ -33,7 +33,6 @@ export function cypressComponentConfiguration(
options: CypressComponentConfigSchema
) {
return cypressComponentConfigurationInternal(tree, {
addPlugin: false,
...options,
});
}
@ -46,8 +45,6 @@ export async function cypressComponentConfigurationInternal(
tree: Tree,
options: CypressComponentConfigSchema
) {
options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false';
const projectConfig = readProjectConfiguration(tree, options.project);
const installTask = await baseCyCTConfig(tree, {
project: options.project,

View File

@ -3,5 +3,4 @@ export interface CypressComponentConfigSchema {
generateTests: boolean;
skipFormat?: boolean;
buildTarget?: string;
addPlugin?: boolean;
}

View File

@ -73,7 +73,6 @@ export async function normalizeOptions(
standaloneComponentName: `${
names(projectNames.projectSimpleName).className
}Component`,
addPlugin: options.addPlugin ?? process.env.NX_ADD_PLUGINS === 'true',
};
const {

View File

@ -38,7 +38,6 @@ export interface NormalizedSchema {
parsedTags: string[];
ngCliSchematicLibRoot: string;
standaloneComponentName: string;
addPlugin?: boolean;
};
componentOptions: {
name: string;

View File

@ -134,7 +134,6 @@ async function addUnitTestRunner(
projectRoot: options.projectRoot,
skipPackageJson: options.skipPackageJson,
strict: options.strict,
addPlugin: false,
});
}
}

View File

@ -191,7 +191,7 @@ export async function updateRootEsLintConfig(
unitTestRunner?: string
): Promise<void> {
await lintInitGenerator(tree, {
addPlugin: process.env.NX_ADD_PLUGINS === 'true',
addPlugin: false,
});
if (!existingEsLintConfig) {

View File

@ -6,8 +6,6 @@ export async function generateStorybookConfiguration(
tree: Tree,
options: StorybookConfigurationOptions
): Promise<GeneratorCallback> {
const addPlugin = process.env.NX_ADD_PLUGINS === 'true';
const { configurationGenerator } = ensurePackage<
typeof import('@nx/storybook')
>('@nx/storybook', nxVersion);
@ -21,7 +19,7 @@ export async function generateStorybookConfiguration(
interactionTests: options.interactionTests,
configureStaticServe: options.configureStaticServe,
skipFormat: true,
addPlugin: addPlugin,
addExplicitTargets: !addPlugin,
addPlugin: false,
addExplicitTargets: true,
});
}

View File

@ -8,7 +8,6 @@ export type AddJestOptions = {
projectRoot: string;
skipPackageJson: boolean;
strict: boolean;
addPlugin?: boolean;
};
export async function addJest(

View File

@ -82,7 +82,6 @@ async function setup(tree: Tree, name: string) {
skipPackageJson: true,
projectNameAndRootFormat: 'derived',
skipFormat: true,
addPlugin: false,
});
const projectConfig = readProjectConfiguration(tree, name);

View File

@ -554,48 +554,6 @@ export default defineConfig({
"
`);
});
it('should support generating explicit targets', async () => {
mockedInstalledCypressVersion.mockReturnValue(undefined); // ensure init is called
addProject(tree, { name: 'explicit-lib', type: 'apps' });
addProject(tree, { name: 'inferred-lib', type: 'apps' });
await cypressE2EConfigurationGenerator(tree, {
project: 'explicit-lib',
baseUrl: 'http://localhost:4200',
addPlugin: true,
addExplicitTargets: true,
});
await cypressE2EConfigurationGenerator(tree, {
project: 'inferred-lib',
baseUrl: 'http://localhost:4200',
addPlugin: true,
addExplicitTargets: false,
});
expect(readProjectConfiguration(tree, 'explicit-lib').targets.e2e)
.toMatchInlineSnapshot(`
{
"configurations": {
"ci": {
"devServerTarget": "explicit-lib:serve-static",
},
"production": {
"devServerTarget": "explicit-lib:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "apps/explicit-lib/cypress.config.ts",
"devServerTarget": "explicit-lib:serve",
"testingType": "e2e",
},
}
`);
expect(
readProjectConfiguration(tree, 'inferred-lib').targets.e2e
).toBeUndefined();
});
});
});

View File

@ -48,7 +48,6 @@ export interface CypressE2EConfigSchema {
webServerCommands?: Record<string, string>;
ciWebServerCommand?: string;
addPlugin?: boolean;
addExplicitTargets?: boolean;
}
type NormalizedSchema = ReturnType<typeof normalizeOptions>;
@ -90,7 +89,7 @@ export async function configurationGeneratorInternal(
);
await addFiles(tree, opts, projectGraph, hasPlugin);
if (!hasPlugin || options.addExplicitTargets) {
if (!hasPlugin) {
addTarget(tree, opts);
}
@ -98,7 +97,6 @@ export async function configurationGeneratorInternal(
...opts,
cypressDir: opts.directory,
addPlugin: opts.addPlugin,
addExplicitTargets: opts.addExplicitTargets,
});
tasks.push(linterTask);

View File

@ -40,7 +40,6 @@ export interface CyLinterOptions {
**/
overwriteExisting?: boolean;
addPlugin?: boolean;
addExplicitTargets?: boolean;
}
export async function addLinterToCyProject(
@ -66,7 +65,6 @@ export async function addLinterToCyProject(
skipPackageJson: options.skipPackageJson,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
addExplicitTargets: options.addExplicitTargets,
})
);
}

View File

@ -331,7 +331,6 @@ describe('app', () => {
it('should configure proxy', async () => {
await angularApplicationGenerator(tree, {
name: 'my-frontend',
addPlugin: true,
});
await applicationGenerator(tree, {
@ -349,7 +348,6 @@ describe('app', () => {
it('should configure proxies for multiple node projects with the same frontend app', async () => {
await angularApplicationGenerator(tree, {
name: 'my-frontend',
addPlugin: true,
});
await applicationGenerator(tree, {
@ -375,7 +373,6 @@ describe('app', () => {
it('should work with unnormalized project names', async () => {
await angularApplicationGenerator(tree, {
name: 'myFrontend',
addPlugin: true,
});
await applicationGenerator(tree, {

View File

@ -34,7 +34,6 @@ async function createPreset(tree: Tree, options: Schema) {
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
bundler: options.bundler,
ssr: options.ssr,
addPlugin,
});
} else if (options.preset === Preset.AngularStandalone) {
const {
@ -53,7 +52,6 @@ async function createPreset(tree: Tree, options: Schema) {
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
bundler: options.bundler,
ssr: options.ssr,
addPlugin,
});
} else if (options.preset === Preset.ReactMonorepo) {
const { applicationGenerator: reactApplicationGenerator } = require('@nx' +