cleanup(angular): consolidate and improve e2e-angular-core tests (#15726)

This commit is contained in:
Leosvel Pérez Espinosa 2023-03-28 17:53:33 +01:00 committed by GitHub
parent db20f655d9
commit 9dbc90d45e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 410 additions and 534 deletions

View File

@ -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 = `<div ([foo])="bar"></div>`;
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`);
});
});
});

View File

@ -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"
}
},
"tags": []
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"apps/${appName}/src/**/*.ts",
"apps/${appName}/src/**/*.html"
]
}
},
"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}"]
}
}
`;
}
`;

View File

@ -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);
}
// 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();
}
}, 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`);
// generate remote apps
runCLI(
`generate @nrwl/angular:remote ${remoteApp1} --host=${hostApp} --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
runCLI(
`generate @nrwl/angular:remote ${remoteApp1} --ssr --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 ${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(port);
} catch (err) {
expect(err).toBeFalsy();
}
}, 10_000_000);
await killProcessAndPorts(process.pid, hostPort, remotePort);
}, 300000);
it('should convert apps to MF successfully', async () => {
const app1 = uniq('app1');
const app2 = uniq('app2');
const app1Port = 4400;
const app2Port = 4401;
// generate apps
runCLI(
`generate @nrwl/angular:application ${app1} --routing --no-interactive`
);
runCLI(`generate @nrwl/angular:application ${app2} --no-interactive`);
// convert apps
runCLI(
`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 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
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 hostPort = 4500;
const remote1Port = readProjectConfig(remote1).targets.serve.options.port;
const remote2Port = readProjectConfig(remote2).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 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);
});

View File

@ -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}`
);
});
});

View File

@ -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`);
});
});

View File

@ -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();
runCLI(`generate @nrwl/angular:app ${app1} --no-interactive`);
runCLI(
`generate @nrwl/angular:lib ${lib1} --add-module-spec --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
);
});
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');
it('should successfully generate apps and libs and work correctly', async () => {
const standaloneApp = uniq('standalone-app');
runCLI(
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive`
);
runCLI(
`generate @nrwl/angular:app ${myapp2} --standalone=true --directory=myDir --no-interactive`
);
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 = `<div ([foo])="bar"></div>`;
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}`);
});

View File

@ -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`,

View File

@ -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<typeof getPackageManagerCommand>;
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`
);

View File

@ -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());
return execSync(command, {
if (isVerboseE2ERun()) {
logInfo(
`NX`,
`E2E restored an Angular CLI project from cache: ${tmpProjPath()}`
);
}
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({

View File

@ -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`;
}

View File

@ -39,3 +39,19 @@ export async function killPorts(port?: number): Promise<boolean> {
? await killPort(port)
: (await killPort(3333)) && (await killPort(4200));
}
export async function killProcessAndPorts(
pid: number | undefined,
...ports: number[]
): Promise<void> {
try {
if (pid) {
await promisifiedTreeKill(pid, 'SIGKILL');
}
for (const port of ports) {
await killPort(port);
}
} catch (err) {
expect(err).toBeFalsy();
}
}