From 9dbc90d45e4954632a7ea142e361589108da92e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Tue, 28 Mar 2023 17:53:33 +0100 Subject: [PATCH] cleanup(angular): consolidate and improve e2e-angular-core tests (#15726) --- e2e/angular-core/src/angular-linting.test.ts | 79 ------ e2e/angular-core/src/config.test.ts | 258 ++++++++++-------- .../src/module-federation.test.ts | 227 +++++---------- e2e/angular-core/src/ng-add.test.ts | 70 ++--- e2e/angular-core/src/ng-cli.test.ts | 45 --- e2e/angular-core/src/projects.test.ts | 164 +++++++---- .../src/make-angular-cli-faster.test.ts | 6 +- e2e/nx-init/src/nx-init-angular.test.ts | 5 +- e2e/utils/create-project-utils.ts | 46 +++- e2e/utils/file-utils.ts | 6 + e2e/utils/process-utils.ts | 16 ++ 11 files changed, 399 insertions(+), 523 deletions(-) delete mode 100644 e2e/angular-core/src/angular-linting.test.ts delete mode 100644 e2e/angular-core/src/ng-cli.test.ts diff --git a/e2e/angular-core/src/angular-linting.test.ts b/e2e/angular-core/src/angular-linting.test.ts deleted file mode 100644 index 4e91baf76a568..0000000000000 --- a/e2e/angular-core/src/angular-linting.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - cleanupProject, - newProject, - runCLI, - uniq, - updateFile, -} from '@nrwl/e2e/utils'; -import * as path from 'path'; - -describe('Angular Package', () => { - describe('linting', () => { - beforeAll(() => newProject()); - afterAll(() => cleanupProject()); - - it('should support eslint and pass linting on the standard generated code', async () => { - const myapp = uniq('myapp'); - runCLI( - `generate @nrwl/angular:app ${myapp} --linter=eslint --no-interactive` - ); - expect(runCLI(`lint ${myapp}`)).toContain('All files pass linting.'); - - const mylib = uniq('mylib'); - runCLI( - `generate @nrwl/angular:lib ${mylib} --linter=eslint --no-interactive` - ); - expect(runCLI(`lint ${mylib}`)).toContain('All files pass linting.'); - }); - - it('should support eslint and successfully lint external HTML files and inline templates', async () => { - const myapp = uniq('myapp'); - - runCLI( - `generate @nrwl/angular:app ${myapp} --linter=eslint --no-interactive` - ); - - const templateWhichFailsBananaInBoxLintCheck = `
`; - const wrappedAsInlineTemplate = ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'inline-template-component', - template: \` - ${templateWhichFailsBananaInBoxLintCheck} - \`, - }) - export class InlineTemplateComponent {} - `; - - // External HTML template file - updateFile( - `apps/${myapp}/src/app/app.component.html`, - templateWhichFailsBananaInBoxLintCheck - ); - - // Inline template within component.ts file - updateFile( - `apps/${myapp}/src/app/inline-template.component.ts`, - wrappedAsInlineTemplate - ); - - const appLintStdOut = runCLI(`lint ${myapp}`, { silenceError: true }); - expect(appLintStdOut).toContain( - path.normalize(`apps/${myapp}/src/app/app.component.html`) - ); - expect(appLintStdOut).toContain(`1:6`); - expect(appLintStdOut).toContain(`Invalid binding syntax`); - expect(appLintStdOut).toContain( - path.normalize(`apps/${myapp}/src/app/inline-template.component.ts`) - ); - - expect(appLintStdOut).toContain(`5:21`); - expect(appLintStdOut).toContain( - `The selector should start with one of these prefixes` - ); - expect(appLintStdOut).toContain(`7:18`); - expect(appLintStdOut).toContain(`Invalid binding syntax`); - }); - }); -}); diff --git a/e2e/angular-core/src/config.test.ts b/e2e/angular-core/src/config.test.ts index 1151086edefa4..1a9076b3d2c4a 100644 --- a/e2e/angular-core/src/config.test.ts +++ b/e2e/angular-core/src/config.test.ts @@ -1,151 +1,169 @@ import { + checkFilesExist, cleanupProject, - expectTestsPass, newProject, + removeFile, runCLI, - runCLIAsync, uniq, updateFile, } from '@nrwl/e2e/utils'; -describe('Angular Config', () => { - beforeAll(() => newProject()); +describe('angular.json v1 config', () => { + const app1 = uniq('app1'); + + beforeAll(() => { + newProject(); + runCLI(`generate @nrwl/angular:app ${app1} --no-interactive`); + // reset workspace to use v1 config + updateFile(`angular.json`, angularV1Json(app1)); + removeFile(`apps/${app1}/project.json`); + removeFile(`apps/${app1}-e2e/project.json`); + }); afterAll(() => cleanupProject()); - it('should upgrade the config correctly', async () => { - const myapp = uniq('myapp'); - runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`); + it('should support projects in angular.json v1 config', async () => { + expect(runCLI(`build ${app1}`)).toContain('Successfully ran target build'); + expect(runCLI(`test ${app1} --no-watch`)).toContain( + 'Successfully ran target test' + ); + }, 1000000); - // update the angular.json, first reset to v1 config - updateFile(`angular.json`, angularV1Json(myapp)); + it('should generate new app with project.json and keep the existing in angular.json', async () => { + // create new app + const app2 = uniq('app2'); + runCLI(`generate @nrwl/angular:app ${app2} --no-interactive`); - const myapp2 = uniq('myapp'); - runCLI(`generate @nrwl/angular:app ${myapp2} --no-interactive`); - expectTestsPass(await runCLIAsync(`test ${myapp2} --no-watch`)); + // should generate project.json for new projects + checkFilesExist(`apps/${app2}/project.json`); + // check it works correctly + expect(runCLI(`build ${app2}`)).toContain('Successfully ran target build'); + expect(runCLI(`test ${app2} --no-watch`)).toContain( + 'Successfully ran target test' + ); + // check existing app in angular.json still works + expect(runCLI(`build ${app1}`)).toContain('Successfully ran target build'); + expect(runCLI(`test ${app1} --no-watch`)).toContain( + 'Successfully ran target test' + ); }, 1000000); }); const angularV1Json = (appName: string) => `{ - "version": 1, - "projects": { - "${appName}": { - "projectType": "application", - "root": "apps/${appName}", - "sourceRoot": "apps/${appName}/src", - "prefix": "v1anuglar", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "outputs": ["{options.outputPath}"], - "options": { - "outputPath": "dist/apps/${appName}", - "index": "apps/${appName}/src/index.html", - "main": "apps/${appName}/src/main.ts", - "polyfills": "apps/${appName}/src/polyfills.ts", - "tsConfig": "apps/${appName}/tsconfig.app.json", - "assets": ["apps/${appName}/src/favicon.ico", "apps/${appName}/src/assets"], - "styles": ["apps/${appName}/src/styles.css"], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], - "fileReplacements": [ - { - "replace": "apps/${appName}/src/environments/environment.ts", - "with": "apps/${appName}/src/environments/environment.prod.ts" - } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true - } - }, - "defaultConfiguration": "production" + "version": 1, + "projects": { + "${appName}": { + "projectType": "application", + "root": "apps/${appName}", + "sourceRoot": "apps/${appName}/src", + "prefix": "v1angular", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/${appName}", + "index": "apps/${appName}/src/index.html", + "main": "apps/${appName}/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/${appName}/tsconfig.app.json", + "assets": ["apps/${appName}/src/favicon.ico", "apps/${appName}/src/assets"], + "styles": ["apps/${appName}/src/styles.css"], + "scripts": [] }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "${appName}:build:production" - }, - "development": { - "browserTarget": "${appName}:build:development" - } + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "${appName}:build" + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true } }, - "lint": { - "builder": "@nrwl/linter:eslint", - "options": { - "lintFilePatterns": [ - "apps/${appName}/src/**/*.ts", - "apps/${appName}/src/**/*.html" - ] + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "${appName}:build:production" + }, + "development": { + "browserTarget": "${appName}:build:development" } }, - "test": { - "builder": "@nrwl/jest:jest", - "outputs": ["coverage/apps/${appName}"], - "options": { - "jestConfig": "apps/${appName}/jest.config.ts", - "passWithNoTests": true - } + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "${appName}:build" + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "apps/${appName}/src/**/*.ts", + "apps/${appName}/src/**/*.html" + ] } }, - "tags": [] + "test": { + "builder": "@nrwl/jest:jest", + "outputs": ["coverage/apps/${appName}"], + "options": { + "jestConfig": "apps/${appName}/jest.config.ts", + "passWithNoTests": true + } + } }, - "${appName}-e2e": { - "root": "apps/${appName}-e2e", - "sourceRoot": "apps/${appName}-e2e/src", - "projectType": "application", - "architect": { - "e2e": { - "builder": "@nrwl/cypress:cypress", - "options": { - "cypressConfig": "apps/${appName}-e2e/cypress.json", - "devServerTarget": "${appName}:serve:development" - }, - "configurations": { - "production": { - "devServerTarget": "${appName}:serve:production" - } - } + "tags": [] + }, + "${appName}-e2e": { + "root": "apps/${appName}-e2e", + "sourceRoot": "apps/${appName}-e2e/src", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/${appName}-e2e/cypress.json", + "devServerTarget": "${appName}:serve:development", + "testingType": "e2e" }, - "lint": { - "builder": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["apps/${appName}-e2e/**/*.{js,ts}"] + "configurations": { + "production": { + "devServerTarget": "${appName}:serve:production" } } }, - "tags": [], - "implicitDependencies": ["${appName}"] - } + "lint": { + "builder": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/${appName}-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["${appName}"] } } - `; +} +`; diff --git a/e2e/angular-core/src/module-federation.test.ts b/e2e/angular-core/src/module-federation.test.ts index ad95725d5c94b..65279cd8db52e 100644 --- a/e2e/angular-core/src/module-federation.test.ts +++ b/e2e/angular-core/src/module-federation.test.ts @@ -1,61 +1,58 @@ +import { names } from '@nrwl/devkit'; import { cleanupProject, - killPort, + killProcessAndPorts, newProject, - promisifiedTreeKill, readProjectConfig, runCLI, runCommandUntil, uniq, updateFile, - updateProjectConfig, } from '@nrwl/e2e/utils'; -import { ChildProcess } from 'child_process'; - -import { names } from '@nrwl/devkit'; -describe('Angular Projects', () => { +describe('Angular Module Federation', () => { let proj: string; - let oldValue; + let oldVerboseLoggingValue: string; beforeAll(() => { proj = newProject(); - oldValue = process.env.NX_E2E_VERBOSE_LOGGING; + oldVerboseLoggingValue = process.env.NX_E2E_VERBOSE_LOGGING; process.env.NX_E2E_VERBOSE_LOGGING = 'true'; }); afterAll(() => { cleanupProject(); - process.env.NX_E2E_VERBOSE_LOGGING = oldValue; + process.env.NX_E2E_VERBOSE_LOGGING = oldVerboseLoggingValue; }); - it('should serve the host and remote apps successfully, even with a shared library with a secondary entry point between them', async () => { - // ACT + ASSERT - const port1 = 4200; - const port2 = 4206; + it('should generate valid host and remote apps', async () => { const hostApp = uniq('app'); const remoteApp1 = uniq('remote'); const sharedLib = uniq('shared-lib'); const secondaryEntry = uniq('secondary'); + const hostPort = 4300; + const remotePort = 4301; // generate host app runCLI( `generate @nrwl/angular:host ${hostApp} --style=css --no-interactive` ); - - // generate remote apps + // generate remote app runCLI( - `generate @nrwl/angular:remote ${remoteApp1} --host=${hostApp} --port=${port2} --style=css --no-interactive` + `generate @nrwl/angular:remote ${remoteApp1} --host=${hostApp} --port=${remotePort} --style=css --no-interactive` ); - // generate a shared lib + // check default generated host is built successfully + const buildOutput = runCLI(`build ${hostApp}`); + expect(buildOutput).toContain('Successfully ran target build'); + + // generate a shared lib with a seconary entry point runCLI( `generate @nrwl/angular:library ${sharedLib} --buildable --no-interactive` ); runCLI( `generate @nrwl/angular:library-secondary-entry-point --library=${sharedLib} --name=${secondaryEntry} --no-interactive` ); - - // update the files to use shared library + // update host & remote files to use shared library updateFile( `apps/${hostApp}/src/app/app.module.ts`, `import { NgModule } from '@angular/core'; @@ -123,169 +120,79 @@ describe('Angular Projects', () => { ` ); - let process: ChildProcess; - - try { - process = await runCommandUntil( - `serve ${hostApp} --dev-remotes=${remoteApp1}`, - (output) => { - return ( - output.includes(`listening on localhost:${port2}`) && - output.includes(`listening on localhost:${port1}`) - ); - } - ); - } catch (err) { - console.error(err); - } + const process = await runCommandUntil( + `serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`, + (output) => + output.includes(`listening on localhost:${remotePort}`) && + output.includes(`listening on localhost:${hostPort}`) + ); // port and process cleanup - try { - if (process && process.pid) { - await promisifiedTreeKill(process.pid, 'SIGKILL'); - } - await killPort(port1); - await killPort(port2); - } catch (err) { - expect(err).toBeFalsy(); - } + await killProcessAndPorts(process.pid, hostPort, remotePort); }, 300000); - it('should build the host app successfully', async () => { - // ARRANGE - const hostApp = uniq('app'); - const remoteApp1 = uniq('remote'); - - // generate host app - runCLI(`generate @nrwl/angular:host ${hostApp} --no-interactive`); + it('should convert apps to MF successfully', async () => { + const app1 = uniq('app1'); + const app2 = uniq('app2'); + const app1Port = 4400; + const app2Port = 4401; - // generate remote apps + // generate apps runCLI( - `generate @nrwl/angular:remote ${remoteApp1} --host=${hostApp} --no-interactive` + `generate @nrwl/angular:application ${app1} --routing --no-interactive` ); + runCLI(`generate @nrwl/angular:application ${app2} --no-interactive`); - // ACT - const buildOutput = runCLI(`build ${hostApp}`); - - // ASSERT - expect(buildOutput).toContain('Successfully ran target build'); - }, 300000); - - it('should serve a ssr remote app successfully', async () => { - // ARRANGE - const remoteApp1 = uniq('remote'); - // generate remote apps + // convert apps runCLI( - `generate @nrwl/angular:remote ${remoteApp1} --ssr --no-interactive` + `generate @nrwl/angular:setup-mf ${app1} --mfType=host --port=${app1Port} --no-interactive` + ); + runCLI( + `generate @nrwl/angular:setup-mf ${app2} --mfType=remote --host=${app1} --port=${app2Port} --no-interactive` ); - const port = 4301; - - let process = await runCommandUntil( - `serve-ssr ${remoteApp1} --port=${port}`, - (output) => { - return ( - output.includes(`Browser application bundle generation complete.`) && - output.includes(`Server application bundle generation complete.`) && - output.includes( - `Angular Universal Live Development Server is listening` - ) - ); - } + const process = await runCommandUntil( + `serve ${app1} --dev-remotes=${app2}`, + (output) => + output.includes(`listening on localhost:${app1Port}`) && + output.includes(`listening on localhost:${app2Port}`) ); // port and process cleanup - try { - if (process && process.pid) { - await promisifiedTreeKill(process.pid, 'SIGKILL'); - } - await killPort(port); - } catch (err) { - expect(err).toBeFalsy(); - } - }, 10_000_000); + await killProcessAndPorts(process.pid, app1Port, app2Port); + }, 20_000_000); // TODO(colum): enable when this issue is resolved https://github.com/module-federation/universe/issues/604 - xit('should scaffold a ssr MF setup successfully', async () => { - // ARRANGE - const remoteApp1 = uniq('remote1'); - const remoteApp2 = uniq('remote2'); - const hostApp = uniq('host1'); + xit('should scaffold MF + SSR setup successfully', async () => { + const host = uniq('host'); + const remote1 = uniq('remote1'); + const remote2 = uniq('remote2'); + // generate remote apps runCLI( - `generate @nrwl/angular:host ${hostApp} --ssr --remotes=${remoteApp1},${remoteApp2} --no-interactive` + `generate @nrwl/angular:host ${host} --ssr --remotes=${remote1},${remote2} --no-interactive` ); // ports - const remoteApp1Port = - readProjectConfig(remoteApp1).targets.serve.options.port; - const remoteApp2Port = - readProjectConfig(remoteApp2).targets.serve.options.port; - - const port = 4401; - - let process = await runCommandUntil( - `serve-ssr ${hostApp} --port=${port}`, - (output) => { - return ( - output.includes( - `Node Express server listening on http://localhost:${remoteApp1Port}` - ) && - output.includes( - `Node Express server listening on http://localhost:${remoteApp2Port}` - ) && - output.includes( - `Angular Universal Live Development Server is listening` - ) - ); - } + const hostPort = 4500; + const remote1Port = readProjectConfig(remote1).targets.serve.options.port; + const remote2Port = readProjectConfig(remote2).targets.serve.options.port; + + const process = await runCommandUntil( + `serve-ssr ${host} --port=${hostPort}`, + (output) => + output.includes( + `Node Express server listening on http://localhost:${remote1Port}` + ) && + output.includes( + `Node Express server listening on http://localhost:${remote2Port}` + ) && + output.includes( + `Angular Universal Live Development Server is listening` + ) ); // port and process cleanup - try { - if (process && process.pid) { - await promisifiedTreeKill(process.pid, 'SIGKILL'); - } - await killPort(port); - await killPort(remoteApp1Port); - await killPort(remoteApp2Port); - } catch (err) { - expect(err).toBeFalsy(); - } + await killProcessAndPorts(process.pid, hostPort, remote1Port, remote2Port); }, 20_000_000); - - it('Custom Webpack Config for SSR - should serve the app correctly', async () => { - // ARRANGE - const ssrApp = uniq('app'); - - runCLI(`generate @nrwl/angular:app ${ssrApp} --no-interactive`); - runCLI(`generate @nrwl/angular:setup-ssr ${ssrApp} --no-interactive`); - - updateProjectConfig(ssrApp, (project) => { - project.targets.server.executor = '@nrwl/angular:webpack-server'; - return project; - }); - - const port = 4501; - - // ACT - let process = await runCommandUntil( - `serve-ssr ${ssrApp} --port=${port}`, - (output) => { - return output.includes( - `Angular Universal Live Development Server is listening on http://localhost:${port}` - ); - } - ); - - // port and process cleanup - try { - if (process && process.pid) { - await promisifiedTreeKill(process.pid, 'SIGKILL'); - } - await killPort(port); - } catch (err) { - expect(err).toBeFalsy(); - } - }, 300000); }); diff --git a/e2e/angular-core/src/ng-add.test.ts b/e2e/angular-core/src/ng-add.test.ts index 239045c712900..f466585ec7dc0 100644 --- a/e2e/angular-core/src/ng-add.test.ts +++ b/e2e/angular-core/src/ng-add.test.ts @@ -64,11 +64,10 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { } beforeEach(() => { - project = uniq('proj'); packageManager = getSelectedPackageManager(); // TODO: solve issues with pnpm and remove this fallback packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; - runNgNew(project, packageManager); + project = runNgNew(packageManager); }); afterEach(() => { @@ -436,55 +435,12 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { ); }); - it('should support a workspace with multiple libraries', () => { - // add some libraries - const lib1 = uniq('lib1'); - const lib2 = uniq('lib2'); - runCommand(`ng g @schematics/angular:library ${lib1}`); - runCommand(`ng g @schematics/angular:library ${lib2}`); - - runNgAdd('@nrwl/angular', '--npm-scope projscope'); - - // check angular.json does not exist - checkFilesDoNotExist('angular.json'); - - // check building lib1 - let output = runCLI(`build ${lib1}`); - expect(output).toContain(`> nx run ${lib1}:build:production`); - expect(output).toContain( - `Successfully ran target build for project ${lib1}` - ); - checkFilesExist(`dist/${lib1}/package.json`); - - output = runCLI(`build ${lib1}`); - expect(output).toContain( - `> nx run ${lib1}:build:production [local cache]` - ); - expect(output).toContain( - `Successfully ran target build for project ${lib1}` - ); - - // check building lib2 - output = runCLI(`build ${lib2}`); - expect(output).toContain(`> nx run ${lib2}:build:production`); - expect(output).toContain( - `Successfully ran target build for project ${lib2}` - ); - checkFilesExist(`dist/${lib2}/package.json`); - - output = runCLI(`build ${lib2}`); - expect(output).toContain( - `> nx run ${lib2}:build:production [local cache]` - ); - expect(output).toContain( - `Successfully ran target build for project ${lib2}` - ); - }); - - it('should support a workspace with multiple applications', () => { - // add another app + it('should support a workspace with multiple projects', () => { + // add other projects const app1 = uniq('app1'); + const lib1 = uniq('lib1'); runCommand(`ng g @schematics/angular:application ${app1}`); + runCommand(`ng g @schematics/angular:library ${lib1}`); runNgAdd('@nrwl/angular', '--npm-scope projscope'); @@ -526,5 +482,21 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { expect(output).toContain( `Successfully ran target build for project ${app1}` ); + + // check building lib1 + output = runCLI(`build ${lib1}`); + expect(output).toContain(`> nx run ${lib1}:build:production`); + expect(output).toContain( + `Successfully ran target build for project ${lib1}` + ); + checkFilesExist(`dist/${lib1}/package.json`); + + output = runCLI(`build ${lib1}`); + expect(output).toContain( + `> nx run ${lib1}:build:production [local cache]` + ); + expect(output).toContain( + `Successfully ran target build for project ${lib1}` + ); }); }); diff --git a/e2e/angular-core/src/ng-cli.test.ts b/e2e/angular-core/src/ng-cli.test.ts deleted file mode 100644 index 7ece5d77376a7..0000000000000 --- a/e2e/angular-core/src/ng-cli.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as isCI from 'is-ci'; -import { - checkFilesExist, - getSelectedPackageManager, - packageInstall, - readJson, - runCommand, - runNgNew, - tmpProjPath, - uniq, - updateFile, -} from '@nrwl/e2e/utils'; -import { PackageManager } from 'nx/src/utils/package-manager'; -import { removeSync } from 'fs-extra'; - -describe('using Nx executors and generators with Angular CLI', () => { - let project: string; - let packageManager: PackageManager; - - beforeEach(() => { - project = uniq('proj'); - packageManager = getSelectedPackageManager(); - runNgNew(project, packageManager); - }); - - afterEach(() => { - if (isCI) { - try { - removeSync(tmpProjPath()); - } catch (e) {} - } - }); - - it('should convert Nx executors into Angular CLI compatible builders', () => { - packageInstall('@nrwl/angular'); - const angularJson = readJson('angular.json'); - angularJson.projects[project].architect.build.builder = - '@nrwl/angular:webpack-browser'; - updateFile('angular.json', JSON.stringify(angularJson, null, 2)); - - runCommand(`npx ng build ${project} --configuration=development`); - - checkFilesExist(`dist/${project}/main.js`); - }); -}); diff --git a/e2e/angular-core/src/projects.test.ts b/e2e/angular-core/src/projects.test.ts index 1ded053b620cf..349d1110d57ec 100644 --- a/e2e/angular-core/src/projects.test.ts +++ b/e2e/angular-core/src/projects.test.ts @@ -1,11 +1,13 @@ +import { names } from '@nrwl/devkit'; import { checkFilesExist, cleanupProject, getSize, killPorts, + killProcessAndPorts, newProject, - promisifiedTreeKill, readFile, + removeFile, runCLI, runCommandUntil, runCypressTests, @@ -14,95 +16,151 @@ import { updateFile, updateProjectConfig, } from '@nrwl/e2e/utils'; - -import { names } from '@nrwl/devkit'; +import { normalize } from 'path'; describe('Angular Projects', () => { let proj: string; + const app1 = uniq('app1'); + const lib1 = uniq('lib1'); + let app1DefaultModule: string; + let app1DefaultComponentTemplate: string; - beforeAll(() => (proj = newProject())); - afterAll(() => cleanupProject()); - - it('should generate an app, a lib, link them, build, serve and test both correctly', async () => { - const myapp = uniq('myapp'); - const myapp2 = uniq('myapp2'); - const mylib = uniq('mylib'); + beforeAll(() => { + proj = newProject(); + runCLI(`generate @nrwl/angular:app ${app1} --no-interactive`); runCLI( - `generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive` + `generate @nrwl/angular:lib ${lib1} --add-module-spec --no-interactive` ); - runCLI( - `generate @nrwl/angular:app ${myapp2} --standalone=true --directory=myDir --no-interactive` + app1DefaultModule = readFile(`apps/${app1}/src/app/app.module.ts`); + app1DefaultComponentTemplate = readFile( + `apps/${app1}/src/app/app.component.html` ); + }); + + afterEach(() => { + updateFile(`apps/${app1}/src/app/app.module.ts`, app1DefaultModule); + updateFile( + `apps/${app1}/src/app/app.component.html`, + app1DefaultComponentTemplate + ); + }); + + afterAll(() => cleanupProject()); + + it('should successfully generate apps and libs and work correctly', async () => { + const standaloneApp = uniq('standalone-app'); runCLI( - `generate @nrwl/angular:lib ${mylib} --directory=myDir --add-module-spec --no-interactive` + `generate @nrwl/angular:app ${standaloneApp} --directory=myDir --standalone=true --no-interactive` ); updateFile( - `apps/my-dir/${myapp}/src/app/app.module.ts`, + `apps/${app1}/src/app/app.module.ts`, + ` + import { NgModule } from '@angular/core'; + import { BrowserModule } from '@angular/platform-browser'; + import { ${names(lib1).className}Module } from '@${proj}/${lib1}'; + import { AppComponent } from './app.component'; + import { NxWelcomeComponent } from './nx-welcome.component'; + + @NgModule({ + imports: [BrowserModule, ${names(lib1).className}Module], + declarations: [AppComponent, NxWelcomeComponent], + bootstrap: [AppComponent] + }) + export class AppModule {} ` - import { NgModule } from '@angular/core'; - import { BrowserModule } from '@angular/platform-browser'; - import { MyDir${ - names(mylib).className - }Module } from '@${proj}/my-dir/${mylib}'; - import { AppComponent } from './app.component'; - import { NxWelcomeComponent } from './nx-welcome.component'; - - @NgModule({ - imports: [BrowserModule, MyDir${names(mylib).className}Module], - declarations: [AppComponent, NxWelcomeComponent], - bootstrap: [AppComponent] - }) - export class AppModule {} - ` ); + + // check build runCLI( - `run-many --target build --projects=my-dir-${myapp},my-dir-${myapp2} --parallel --prod --output-hashing none` + `run-many --target build --projects=${app1},my-dir-${standaloneApp} --parallel --prod --output-hashing none` ); - - checkFilesExist(`dist/apps/my-dir/${myapp}/main.js`); - + checkFilesExist(`dist/apps/${app1}/main.js`); + checkFilesExist(`dist/apps/my-dir/${standaloneApp}/main.js`); // This is a loose requirement because there are a lot of // influences external from this project that affect this. - const es2015BundleSize = getSize( - tmpProjPath(`dist/apps/my-dir/${myapp}/main.js`) - ); + const es2015BundleSize = getSize(tmpProjPath(`dist/apps/${app1}/main.js`)); console.log( `The current es2015 bundle size is ${es2015BundleSize / 1000} KB` ); expect(es2015BundleSize).toBeLessThanOrEqual(160000); + // check unit tests runCLI( - `run-many --target test --projects=my-dir-${myapp},my-dir-${mylib} --parallel` + `run-many --target test --projects=${app1},my-dir-${standaloneApp},${lib1} --parallel` ); + // check e2e tests if (runCypressTests()) { - const e2eResults = runCLI(`e2e my-dir-${myapp}-e2e --no-watch`); + const e2eResults = runCLI(`e2e ${app1}-e2e --no-watch`); expect(e2eResults).toContain('All specs passed!'); expect(await killPorts()).toBeTruthy(); } + const appPort = 4207; const process = await runCommandUntil( - `serve my-dir-${myapp} -- --port=4207`, + `serve ${app1} -- --port=${appPort}`, (output) => output.includes(`listening on localhost:4207`) ); // port and process cleanup - try { - await promisifiedTreeKill(process.pid, 'SIGKILL'); - await killPorts(4207); - } catch (err) { - expect(err).toBeFalsy(); - } + await killProcessAndPorts(process.pid, appPort); + }, 1000000); + + it('should lint correctly with eslint and handle external HTML files and inline templates', async () => { + // check apps and lib pass linting for initial generated code + runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`); + + // External HTML template file + const templateWhichFailsBananaInBoxLintCheck = `
`; + updateFile( + `apps/${app1}/src/app/app.component.html`, + templateWhichFailsBananaInBoxLintCheck + ); + // Inline template within component.ts file + const wrappedAsInlineTemplate = ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'inline-template-component', + template: \` + ${templateWhichFailsBananaInBoxLintCheck} + \`, + }) + export class InlineTemplateComponent {} + `; + updateFile( + `apps/${app1}/src/app/inline-template.component.ts`, + wrappedAsInlineTemplate + ); + + const appLintStdOut = runCLI(`lint ${app1}`, { + silenceError: true, + }); + expect(appLintStdOut).toContain( + normalize(`apps/${app1}/src/app/app.component.html`) + ); + expect(appLintStdOut).toContain(`1:6`); + expect(appLintStdOut).toContain(`Invalid binding syntax`); + expect(appLintStdOut).toContain( + normalize(`apps/${app1}/src/app/inline-template.component.ts`) + ); + expect(appLintStdOut).toContain(`5:19`); + expect(appLintStdOut).toContain( + `The selector should start with one of these prefixes` + ); + expect(appLintStdOut).toContain(`7:16`); + expect(appLintStdOut).toContain(`Invalid binding syntax`); + + // cleanup added component + removeFile(`apps/${app1}/src/app/inline-template.component.ts`); }, 1000000); it('should build the dependent buildable lib and its child lib, as well as the app', async () => { // ARRANGE - const app = uniq('app'); const buildableLib = uniq('buildlib1'); const buildableChildLib = uniq('buildlib2'); - runCLI(`generate @nrwl/angular:app ${app} --style=css --no-interactive`); runCLI( `generate @nrwl/angular:library ${buildableLib} --buildable=true --no-interactive` ); @@ -112,7 +170,7 @@ describe('Angular Projects', () => { // update the app module to include a ref to the buildable lib updateFile( - `apps/${app}/src/app/app.module.ts`, + `apps/${app1}/src/app/app.module.ts`, ` import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @@ -152,7 +210,7 @@ describe('Angular Projects', () => { ); // update the angular.json - updateProjectConfig(app, (config) => { + updateProjectConfig(app1, (config) => { config.targets.build.executor = '@nrwl/angular:webpack-browser'; config.targets.build.options = { ...config.targets.build.options, @@ -162,17 +220,17 @@ describe('Angular Projects', () => { }); // ACT - const libOutput = runCLI(`build ${app} --configuration=development`); + const libOutput = runCLI(`build ${app1} --configuration=development`); // ASSERT expect(libOutput).toContain( `Building entry point '@${proj}/${buildableLib}'` ); - expect(libOutput).toContain(`nx run ${app}:build:development`); + expect(libOutput).toContain(`nx run ${app1}:build:development`); // to proof it has been built from source the "main.js" should actually contain // the path to dist - const mainBundle = readFile(`dist/apps/${app}/main.js`); + const mainBundle = readFile(`dist/apps/${app1}/main.js`); expect(mainBundle).toContain(`dist/libs/${buildableLib}`); }); diff --git a/e2e/make-angular-cli-faster/src/make-angular-cli-faster.test.ts b/e2e/make-angular-cli-faster/src/make-angular-cli-faster.test.ts index 75035d78bd6b0..fb0c892b20d4b 100644 --- a/e2e/make-angular-cli-faster/src/make-angular-cli-faster.test.ts +++ b/e2e/make-angular-cli-faster/src/make-angular-cli-faster.test.ts @@ -3,7 +3,6 @@ import { getSelectedPackageManager, runNgNew, tmpProjPath, - uniq, } from '../../utils'; import { PackageManager } from 'nx/src/utils/package-manager'; import { execSync } from 'child_process'; @@ -13,10 +12,10 @@ describe('make-angular-cli-faster', () => { let packageManager: PackageManager; beforeEach(() => { - project = uniq('proj'); packageManager = getSelectedPackageManager(); // TODO: solve issues with pnpm and remove this fallback packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; + project = runNgNew(packageManager); }); afterEach(() => { @@ -25,9 +24,6 @@ describe('make-angular-cli-faster', () => { // TODO(colum): skip until we can investigate why it is installing incorrect version xit('should successfully install make-angular-cli-faster with nx cloud', () => { - // ARRANGE - runNgNew(project, packageManager); - expect(() => execSync( `NPM_CONFIG_REGISTRY=https://registry.npmjs.org npx --yes make-angular-cli-faster@latest --useNxCloud=true`, diff --git a/e2e/nx-init/src/nx-init-angular.test.ts b/e2e/nx-init/src/nx-init-angular.test.ts index e7257a74ba68f..1ae516a92ddc4 100644 --- a/e2e/nx-init/src/nx-init-angular.test.ts +++ b/e2e/nx-init/src/nx-init-angular.test.ts @@ -9,7 +9,6 @@ import { runCLI, runCommand, runNgNew, - uniq, } from '../../utils'; describe('nx init (Angular CLI)', () => { @@ -18,11 +17,11 @@ describe('nx init (Angular CLI)', () => { let pmc: ReturnType; beforeEach(() => { - project = uniq('proj'); packageManager = getSelectedPackageManager(); // TODO: solve issues with pnpm and remove this fallback packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; pmc = getPackageManagerCommand({ packageManager }); + project = runNgNew(packageManager); }); afterEach(() => { @@ -30,8 +29,6 @@ describe('nx init (Angular CLI)', () => { }); it('should successfully convert an Angular CLI workspace to an Nx workspace', () => { - runNgNew(project, packageManager); - const output = runCommand( `${pmc.runUninstalledPackage} nx@${getPublishedVersion()} init -y` ); diff --git a/e2e/utils/create-project-utils.ts b/e2e/utils/create-project-utils.ts index ab3ef7114912e..add78ac4917a2 100644 --- a/e2e/utils/create-project-utils.ts +++ b/e2e/utils/create-project-utils.ts @@ -2,6 +2,7 @@ import { copySync, ensureDirSync, moveSync, removeSync } from 'fs-extra'; import { createFile, directoryExists, + tmpBackupNgCliProjPath, tmpBackupProjPath, updateFile, updateJson, @@ -9,10 +10,10 @@ import { import { e2eCwd, getLatestLernaVersion, - getNpmMajorVersion, getPublishedVersion, getSelectedPackageManager, isVerbose, + isVerboseE2ERun, } from './get-env-info'; import * as isCI from 'is-ci'; @@ -28,6 +29,8 @@ import { runCommand, } from './command-utils'; import { output } from '@nrwl/devkit'; +import { readFileSync } from 'fs'; +import { join } from 'path'; let projName: string; @@ -311,24 +314,51 @@ export function packageInstall( } } +/** + * Creates a new Angular CLI workspace or restores a cached one if exists. + * @returns the workspace name + */ export function runNgNew( - projectName: string, packageManager = getSelectedPackageManager(), angularCliVersion = defaultAngularCliVersion ): string { - projName = projectName; + const pmc = getPackageManagerCommand({ packageManager }); - const npmMajorVersion = getNpmMajorVersion(); - const command = `npx ${ - +npmMajorVersion >= 7 ? '--yes' : '' - } @angular/cli@${angularCliVersion} new ${projectName} --package-manager=${packageManager}`; + if (directoryExists(tmpBackupNgCliProjPath())) { + const angularJson = JSON.parse( + readFileSync(join(tmpBackupNgCliProjPath(), 'angular.json'), 'utf-8') + ); + // the name of the workspace matches the name of the generated default app, + // we need to reuse the same name that's cached in order to avoid issues + // with tests relying on a different name + projName = Object.keys(angularJson.projects)[0]; + copySync(tmpBackupNgCliProjPath(), tmpProjPath()); + + if (isVerboseE2ERun()) { + logInfo( + `NX`, + `E2E restored an Angular CLI project from cache: ${tmpProjPath()}` + ); + } - return execSync(command, { + return projName; + } + + projName = uniq('ng-proj'); + const command = `${pmc.runUninstalledPackage} @angular/cli@${angularCliVersion} new ${projName} --package-manager=${packageManager}`; + execSync(command, { cwd: e2eCwd, stdio: isVerbose() ? 'inherit' : 'pipe', env: process.env, encoding: 'utf-8', }); + copySync(tmpProjPath(), tmpBackupNgCliProjPath()); + + if (isVerboseE2ERun()) { + logInfo(`NX`, `E2E created an Angular CLI project: ${tmpProjPath()}`); + } + + return projName; } export function newLernaWorkspace({ diff --git a/e2e/utils/file-utils.ts b/e2e/utils/file-utils.ts index 9110a2abbdba9..92859c8aeb71b 100644 --- a/e2e/utils/file-utils.ts +++ b/e2e/utils/file-utils.ts @@ -119,3 +119,9 @@ export function getSize(filePath: string): number { export function tmpBackupProjPath(path?: string) { return path ? `${e2eCwd}/proj-backup/${path}` : `${e2eCwd}/proj-backup`; } + +export function tmpBackupNgCliProjPath(path?: string) { + return path + ? `${e2eCwd}/ng-cli-proj-backup/${path}` + : `${e2eCwd}/ng-cli-proj-backup`; +} diff --git a/e2e/utils/process-utils.ts b/e2e/utils/process-utils.ts index 672d9d459822b..e17997c0a5173 100644 --- a/e2e/utils/process-utils.ts +++ b/e2e/utils/process-utils.ts @@ -39,3 +39,19 @@ export async function killPorts(port?: number): Promise { ? await killPort(port) : (await killPort(3333)) && (await killPort(4200)); } + +export async function killProcessAndPorts( + pid: number | undefined, + ...ports: number[] +): Promise { + try { + if (pid) { + await promisifiedTreeKill(pid, 'SIGKILL'); + } + for (const port of ports) { + await killPort(port); + } + } catch (err) { + expect(err).toBeFalsy(); + } +}