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,27 +1,50 @@
import { import {
checkFilesExist,
cleanupProject, cleanupProject,
expectTestsPass,
newProject, newProject,
removeFile,
runCLI, runCLI,
runCLIAsync,
uniq, uniq,
updateFile, updateFile,
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
describe('Angular Config', () => { describe('angular.json v1 config', () => {
beforeAll(() => newProject()); 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()); afterAll(() => cleanupProject());
it('should upgrade the config correctly', async () => { it('should support projects in angular.json v1 config', async () => {
const myapp = uniq('myapp'); expect(runCLI(`build ${app1}`)).toContain('Successfully ran target build');
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`); expect(runCLI(`test ${app1} --no-watch`)).toContain(
'Successfully ran target test'
);
}, 1000000);
// update the angular.json, first reset to v1 config it('should generate new app with project.json and keep the existing in angular.json', async () => {
updateFile(`angular.json`, angularV1Json(myapp)); // create new app
const app2 = uniq('app2');
runCLI(`generate @nrwl/angular:app ${app2} --no-interactive`);
const myapp2 = uniq('myapp'); // should generate project.json for new projects
runCLI(`generate @nrwl/angular:app ${myapp2} --no-interactive`); checkFilesExist(`apps/${app2}/project.json`);
expectTestsPass(await runCLIAsync(`test ${myapp2} --no-watch`)); // 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); }, 1000000);
}); });
@ -32,7 +55,7 @@ const angularV1Json = (appName: string) => `{
"projectType": "application", "projectType": "application",
"root": "apps/${appName}", "root": "apps/${appName}",
"sourceRoot": "apps/${appName}/src", "sourceRoot": "apps/${appName}/src",
"prefix": "v1anuglar", "prefix": "v1angular",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
@ -41,7 +64,7 @@ const angularV1Json = (appName: string) => `{
"outputPath": "dist/apps/${appName}", "outputPath": "dist/apps/${appName}",
"index": "apps/${appName}/src/index.html", "index": "apps/${appName}/src/index.html",
"main": "apps/${appName}/src/main.ts", "main": "apps/${appName}/src/main.ts",
"polyfills": "apps/${appName}/src/polyfills.ts", "polyfills": ["zone.js"],
"tsConfig": "apps/${appName}/tsconfig.app.json", "tsConfig": "apps/${appName}/tsconfig.app.json",
"assets": ["apps/${appName}/src/favicon.ico", "apps/${appName}/src/assets"], "assets": ["apps/${appName}/src/favicon.ico", "apps/${appName}/src/assets"],
"styles": ["apps/${appName}/src/styles.css"], "styles": ["apps/${appName}/src/styles.css"],
@ -61,12 +84,6 @@ const angularV1Json = (appName: string) => `{
"maximumError": "4kb" "maximumError": "4kb"
} }
], ],
"fileReplacements": [
{
"replace": "apps/${appName}/src/environments/environment.ts",
"with": "apps/${appName}/src/environments/environment.prod.ts"
}
],
"outputHashing": "all" "outputHashing": "all"
}, },
"development": { "development": {
@ -127,7 +144,8 @@ const angularV1Json = (appName: string) => `{
"builder": "@nrwl/cypress:cypress", "builder": "@nrwl/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/${appName}-e2e/cypress.json", "cypressConfig": "apps/${appName}-e2e/cypress.json",
"devServerTarget": "${appName}:serve:development" "devServerTarget": "${appName}:serve:development",
"testingType": "e2e"
}, },
"configurations": { "configurations": {
"production": { "production": {
@ -147,5 +165,5 @@ const angularV1Json = (appName: string) => `{
"implicitDependencies": ["${appName}"] "implicitDependencies": ["${appName}"]
} }
} }
} }
`; `;

View File

@ -1,61 +1,58 @@
import { names } from '@nrwl/devkit';
import { import {
cleanupProject, cleanupProject,
killPort, killProcessAndPorts,
newProject, newProject,
promisifiedTreeKill,
readProjectConfig, readProjectConfig,
runCLI, runCLI,
runCommandUntil, runCommandUntil,
uniq, uniq,
updateFile, updateFile,
updateProjectConfig,
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
import { ChildProcess } from 'child_process';
import { names } from '@nrwl/devkit'; describe('Angular Module Federation', () => {
describe('Angular Projects', () => {
let proj: string; let proj: string;
let oldValue; let oldVerboseLoggingValue: string;
beforeAll(() => { beforeAll(() => {
proj = newProject(); proj = newProject();
oldValue = process.env.NX_E2E_VERBOSE_LOGGING; oldVerboseLoggingValue = process.env.NX_E2E_VERBOSE_LOGGING;
process.env.NX_E2E_VERBOSE_LOGGING = 'true'; process.env.NX_E2E_VERBOSE_LOGGING = 'true';
}); });
afterAll(() => { afterAll(() => {
cleanupProject(); 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 () => { it('should generate valid host and remote apps', async () => {
// ACT + ASSERT
const port1 = 4200;
const port2 = 4206;
const hostApp = uniq('app'); const hostApp = uniq('app');
const remoteApp1 = uniq('remote'); const remoteApp1 = uniq('remote');
const sharedLib = uniq('shared-lib'); const sharedLib = uniq('shared-lib');
const secondaryEntry = uniq('secondary'); const secondaryEntry = uniq('secondary');
const hostPort = 4300;
const remotePort = 4301;
// generate host app // generate host app
runCLI( runCLI(
`generate @nrwl/angular:host ${hostApp} --style=css --no-interactive` `generate @nrwl/angular:host ${hostApp} --style=css --no-interactive`
); );
// generate remote app
// generate remote apps
runCLI( 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( runCLI(
`generate @nrwl/angular:library ${sharedLib} --buildable --no-interactive` `generate @nrwl/angular:library ${sharedLib} --buildable --no-interactive`
); );
runCLI( runCLI(
`generate @nrwl/angular:library-secondary-entry-point --library=${sharedLib} --name=${secondaryEntry} --no-interactive` `generate @nrwl/angular:library-secondary-entry-point --library=${sharedLib} --name=${secondaryEntry} --no-interactive`
); );
// update host & remote files to use shared library
// update the files to use shared library
updateFile( updateFile(
`apps/${hostApp}/src/app/app.module.ts`, `apps/${hostApp}/src/app/app.module.ts`,
`import { NgModule } from '@angular/core'; `import { NgModule } from '@angular/core';
@ -123,169 +120,79 @@ describe('Angular Projects', () => {
` `
); );
let process: ChildProcess; const process = await runCommandUntil(
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`,
try { (output) =>
process = await runCommandUntil( output.includes(`listening on localhost:${remotePort}`) &&
`serve ${hostApp} --dev-remotes=${remoteApp1}`, output.includes(`listening on localhost:${hostPort}`)
(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`
)
);
}
); );
// port and process cleanup // port and process cleanup
try { await killProcessAndPorts(process.pid, hostPort, remotePort);
if (process && process.pid) { }, 300000);
await promisifiedTreeKill(process.pid, 'SIGKILL');
} it('should convert apps to MF successfully', async () => {
await killPort(port); const app1 = uniq('app1');
} catch (err) { const app2 = uniq('app2');
expect(err).toBeFalsy(); const app1Port = 4400;
} const app2Port = 4401;
}, 10_000_000);
// 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 // 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 () => { xit('should scaffold MF + SSR setup successfully', async () => {
// ARRANGE const host = uniq('host');
const remoteApp1 = uniq('remote1'); const remote1 = uniq('remote1');
const remoteApp2 = uniq('remote2'); const remote2 = uniq('remote2');
const hostApp = uniq('host1');
// generate remote apps // generate remote apps
runCLI( runCLI(
`generate @nrwl/angular:host ${hostApp} --ssr --remotes=${remoteApp1},${remoteApp2} --no-interactive` `generate @nrwl/angular:host ${host} --ssr --remotes=${remote1},${remote2} --no-interactive`
); );
// ports // ports
const remoteApp1Port = const hostPort = 4500;
readProjectConfig(remoteApp1).targets.serve.options.port; const remote1Port = readProjectConfig(remote1).targets.serve.options.port;
const remoteApp2Port = const remote2Port = readProjectConfig(remote2).targets.serve.options.port;
readProjectConfig(remoteApp2).targets.serve.options.port;
const port = 4401; const process = await runCommandUntil(
`serve-ssr ${host} --port=${hostPort}`,
let process = await runCommandUntil( (output) =>
`serve-ssr ${hostApp} --port=${port}`,
(output) => {
return (
output.includes( output.includes(
`Node Express server listening on http://localhost:${remoteApp1Port}` `Node Express server listening on http://localhost:${remote1Port}`
) && ) &&
output.includes( output.includes(
`Node Express server listening on http://localhost:${remoteApp2Port}` `Node Express server listening on http://localhost:${remote2Port}`
) && ) &&
output.includes( output.includes(
`Angular Universal Live Development Server is listening` `Angular Universal Live Development Server is listening`
) )
); );
}
);
// port and process cleanup // port and process cleanup
try { await killProcessAndPorts(process.pid, hostPort, remote1Port, remote2Port);
if (process && process.pid) {
await promisifiedTreeKill(process.pid, 'SIGKILL');
}
await killPort(port);
await killPort(remoteApp1Port);
await killPort(remoteApp2Port);
} catch (err) {
expect(err).toBeFalsy();
}
}, 20_000_000); }, 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(() => { beforeEach(() => {
project = uniq('proj');
packageManager = getSelectedPackageManager(); packageManager = getSelectedPackageManager();
// TODO: solve issues with pnpm and remove this fallback // TODO: solve issues with pnpm and remove this fallback
packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager;
runNgNew(project, packageManager); project = runNgNew(packageManager);
}); });
afterEach(() => { afterEach(() => {
@ -436,55 +435,12 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
); );
}); });
it('should support a workspace with multiple libraries', () => { it('should support a workspace with multiple projects', () => {
// add some libraries // add other projects
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
const app1 = uniq('app1'); const app1 = uniq('app1');
const lib1 = uniq('lib1');
runCommand(`ng g @schematics/angular:application ${app1}`); runCommand(`ng g @schematics/angular:application ${app1}`);
runCommand(`ng g @schematics/angular:library ${lib1}`);
runNgAdd('@nrwl/angular', '--npm-scope projscope'); runNgAdd('@nrwl/angular', '--npm-scope projscope');
@ -526,5 +482,21 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(output).toContain( expect(output).toContain(
`Successfully ran target build for project ${app1}` `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 { import {
checkFilesExist, checkFilesExist,
cleanupProject, cleanupProject,
getSize, getSize,
killPorts, killPorts,
killProcessAndPorts,
newProject, newProject,
promisifiedTreeKill,
readFile, readFile,
removeFile,
runCLI, runCLI,
runCommandUntil, runCommandUntil,
runCypressTests, runCypressTests,
@ -14,95 +16,151 @@ import {
updateFile, updateFile,
updateProjectConfig, updateProjectConfig,
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
import { normalize } from 'path';
import { names } from '@nrwl/devkit';
describe('Angular Projects', () => { describe('Angular Projects', () => {
let proj: string; 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()); afterAll(() => cleanupProject());
it('should generate an app, a lib, link them, build, serve and test both correctly', async () => { it('should successfully generate apps and libs and work correctly', async () => {
const myapp = uniq('myapp'); const standaloneApp = uniq('standalone-app');
const myapp2 = uniq('myapp2');
const mylib = uniq('mylib');
runCLI( runCLI(
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive` `generate @nrwl/angular:app ${standaloneApp} --directory=myDir --standalone=true --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`
); );
updateFile( updateFile(
`apps/my-dir/${myapp}/src/app/app.module.ts`, `apps/${app1}/src/app/app.module.ts`,
` `
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { MyDir${ import { ${names(lib1).className}Module } from '@${proj}/${lib1}';
names(mylib).className
}Module } from '@${proj}/my-dir/${mylib}';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { NxWelcomeComponent } from './nx-welcome.component'; import { NxWelcomeComponent } from './nx-welcome.component';
@NgModule({ @NgModule({
imports: [BrowserModule, MyDir${names(mylib).className}Module], imports: [BrowserModule, ${names(lib1).className}Module],
declarations: [AppComponent, NxWelcomeComponent], declarations: [AppComponent, NxWelcomeComponent],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule {}
` `
); );
// check build
runCLI( 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/${app1}/main.js`);
checkFilesExist(`dist/apps/my-dir/${myapp}/main.js`); checkFilesExist(`dist/apps/my-dir/${standaloneApp}/main.js`);
// This is a loose requirement because there are a lot of // This is a loose requirement because there are a lot of
// influences external from this project that affect this. // influences external from this project that affect this.
const es2015BundleSize = getSize( const es2015BundleSize = getSize(tmpProjPath(`dist/apps/${app1}/main.js`));
tmpProjPath(`dist/apps/my-dir/${myapp}/main.js`)
);
console.log( console.log(
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB` `The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
); );
expect(es2015BundleSize).toBeLessThanOrEqual(160000); expect(es2015BundleSize).toBeLessThanOrEqual(160000);
// check unit tests
runCLI( 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()) { 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(e2eResults).toContain('All specs passed!');
expect(await killPorts()).toBeTruthy(); expect(await killPorts()).toBeTruthy();
} }
const appPort = 4207;
const process = await runCommandUntil( const process = await runCommandUntil(
`serve my-dir-${myapp} -- --port=4207`, `serve ${app1} -- --port=${appPort}`,
(output) => output.includes(`listening on localhost:4207`) (output) => output.includes(`listening on localhost:4207`)
); );
// port and process cleanup // port and process cleanup
try { await killProcessAndPorts(process.pid, appPort);
await promisifiedTreeKill(process.pid, 'SIGKILL'); }, 1000000);
await killPorts(4207);
} catch (err) { it('should lint correctly with eslint and handle external HTML files and inline templates', async () => {
expect(err).toBeFalsy(); // 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); }, 1000000);
it('should build the dependent buildable lib and its child lib, as well as the app', async () => { it('should build the dependent buildable lib and its child lib, as well as the app', async () => {
// ARRANGE // ARRANGE
const app = uniq('app');
const buildableLib = uniq('buildlib1'); const buildableLib = uniq('buildlib1');
const buildableChildLib = uniq('buildlib2'); const buildableChildLib = uniq('buildlib2');
runCLI(`generate @nrwl/angular:app ${app} --style=css --no-interactive`);
runCLI( runCLI(
`generate @nrwl/angular:library ${buildableLib} --buildable=true --no-interactive` `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 // update the app module to include a ref to the buildable lib
updateFile( updateFile(
`apps/${app}/src/app/app.module.ts`, `apps/${app1}/src/app/app.module.ts`,
` `
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
@ -152,7 +210,7 @@ describe('Angular Projects', () => {
); );
// update the angular.json // update the angular.json
updateProjectConfig(app, (config) => { updateProjectConfig(app1, (config) => {
config.targets.build.executor = '@nrwl/angular:webpack-browser'; config.targets.build.executor = '@nrwl/angular:webpack-browser';
config.targets.build.options = { config.targets.build.options = {
...config.targets.build.options, ...config.targets.build.options,
@ -162,17 +220,17 @@ describe('Angular Projects', () => {
}); });
// ACT // ACT
const libOutput = runCLI(`build ${app} --configuration=development`); const libOutput = runCLI(`build ${app1} --configuration=development`);
// ASSERT // ASSERT
expect(libOutput).toContain( expect(libOutput).toContain(
`Building entry point '@${proj}/${buildableLib}'` `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 // to proof it has been built from source the "main.js" should actually contain
// the path to dist // 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}`); expect(mainBundle).toContain(`dist/libs/${buildableLib}`);
}); });

View File

@ -3,7 +3,6 @@ import {
getSelectedPackageManager, getSelectedPackageManager,
runNgNew, runNgNew,
tmpProjPath, tmpProjPath,
uniq,
} from '../../utils'; } from '../../utils';
import { PackageManager } from 'nx/src/utils/package-manager'; import { PackageManager } from 'nx/src/utils/package-manager';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
@ -13,10 +12,10 @@ describe('make-angular-cli-faster', () => {
let packageManager: PackageManager; let packageManager: PackageManager;
beforeEach(() => { beforeEach(() => {
project = uniq('proj');
packageManager = getSelectedPackageManager(); packageManager = getSelectedPackageManager();
// TODO: solve issues with pnpm and remove this fallback // TODO: solve issues with pnpm and remove this fallback
packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager;
project = runNgNew(packageManager);
}); });
afterEach(() => { afterEach(() => {
@ -25,9 +24,6 @@ describe('make-angular-cli-faster', () => {
// TODO(colum): skip until we can investigate why it is installing incorrect version // TODO(colum): skip until we can investigate why it is installing incorrect version
xit('should successfully install make-angular-cli-faster with nx cloud', () => { xit('should successfully install make-angular-cli-faster with nx cloud', () => {
// ARRANGE
runNgNew(project, packageManager);
expect(() => expect(() =>
execSync( execSync(
`NPM_CONFIG_REGISTRY=https://registry.npmjs.org npx --yes make-angular-cli-faster@latest --useNxCloud=true`, `NPM_CONFIG_REGISTRY=https://registry.npmjs.org npx --yes make-angular-cli-faster@latest --useNxCloud=true`,

View File

@ -9,7 +9,6 @@ import {
runCLI, runCLI,
runCommand, runCommand,
runNgNew, runNgNew,
uniq,
} from '../../utils'; } from '../../utils';
describe('nx init (Angular CLI)', () => { describe('nx init (Angular CLI)', () => {
@ -18,11 +17,11 @@ describe('nx init (Angular CLI)', () => {
let pmc: ReturnType<typeof getPackageManagerCommand>; let pmc: ReturnType<typeof getPackageManagerCommand>;
beforeEach(() => { beforeEach(() => {
project = uniq('proj');
packageManager = getSelectedPackageManager(); packageManager = getSelectedPackageManager();
// TODO: solve issues with pnpm and remove this fallback // TODO: solve issues with pnpm and remove this fallback
packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager;
pmc = getPackageManagerCommand({ packageManager }); pmc = getPackageManagerCommand({ packageManager });
project = runNgNew(packageManager);
}); });
afterEach(() => { afterEach(() => {
@ -30,8 +29,6 @@ describe('nx init (Angular CLI)', () => {
}); });
it('should successfully convert an Angular CLI workspace to an Nx workspace', () => { it('should successfully convert an Angular CLI workspace to an Nx workspace', () => {
runNgNew(project, packageManager);
const output = runCommand( const output = runCommand(
`${pmc.runUninstalledPackage} nx@${getPublishedVersion()} init -y` `${pmc.runUninstalledPackage} nx@${getPublishedVersion()} init -y`
); );

View File

@ -2,6 +2,7 @@ import { copySync, ensureDirSync, moveSync, removeSync } from 'fs-extra';
import { import {
createFile, createFile,
directoryExists, directoryExists,
tmpBackupNgCliProjPath,
tmpBackupProjPath, tmpBackupProjPath,
updateFile, updateFile,
updateJson, updateJson,
@ -9,10 +10,10 @@ import {
import { import {
e2eCwd, e2eCwd,
getLatestLernaVersion, getLatestLernaVersion,
getNpmMajorVersion,
getPublishedVersion, getPublishedVersion,
getSelectedPackageManager, getSelectedPackageManager,
isVerbose, isVerbose,
isVerboseE2ERun,
} from './get-env-info'; } from './get-env-info';
import * as isCI from 'is-ci'; import * as isCI from 'is-ci';
@ -28,6 +29,8 @@ import {
runCommand, runCommand,
} from './command-utils'; } from './command-utils';
import { output } from '@nrwl/devkit'; import { output } from '@nrwl/devkit';
import { readFileSync } from 'fs';
import { join } from 'path';
let projName: string; 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( export function runNgNew(
projectName: string,
packageManager = getSelectedPackageManager(), packageManager = getSelectedPackageManager(),
angularCliVersion = defaultAngularCliVersion angularCliVersion = defaultAngularCliVersion
): string { ): string {
projName = projectName; const pmc = getPackageManagerCommand({ packageManager });
const npmMajorVersion = getNpmMajorVersion(); if (directoryExists(tmpBackupNgCliProjPath())) {
const command = `npx ${ const angularJson = JSON.parse(
+npmMajorVersion >= 7 ? '--yes' : '' readFileSync(join(tmpBackupNgCliProjPath(), 'angular.json'), 'utf-8')
} @angular/cli@${angularCliVersion} new ${projectName} --package-manager=${packageManager}`; );
// 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, cwd: e2eCwd,
stdio: isVerbose() ? 'inherit' : 'pipe', stdio: isVerbose() ? 'inherit' : 'pipe',
env: process.env, env: process.env,
encoding: 'utf-8', encoding: 'utf-8',
}); });
copySync(tmpProjPath(), tmpBackupNgCliProjPath());
if (isVerboseE2ERun()) {
logInfo(`NX`, `E2E created an Angular CLI project: ${tmpProjPath()}`);
}
return projName;
} }
export function newLernaWorkspace({ export function newLernaWorkspace({

View File

@ -119,3 +119,9 @@ export function getSize(filePath: string): number {
export function tmpBackupProjPath(path?: string) { export function tmpBackupProjPath(path?: string) {
return path ? `${e2eCwd}/proj-backup/${path}` : `${e2eCwd}/proj-backup`; 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(port)
: (await killPort(3333)) && (await killPort(4200)); : (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();
}
}