process.env.SELECTED_CLI = 'angular'; import { checkFilesDoNotExist, checkFilesExist, cleanupProject, getSelectedPackageManager, packageInstall, readJson, runCLI, runCommand, runNgAdd, runNgNew, uniq, updateFile, } from '@nrwl/e2e/utils'; import { PackageManager } from 'nx/src/utils/package-manager'; describe('convert Angular CLI workspace to an Nx workspace', () => { let project: string; let packageManager: PackageManager; // utility to manually add protractor since it's not generated // in the latest Angular CLI versions, but older projects updated // to latest versions might still have it function addProtractor() { updateFile('e2e/protractor.conf.js', 'exports.config = {};'); updateFile( 'e2e/tsconfig.json', JSON.stringify({ extends: '../tsconfig.json' }, null, 2) ); updateFile( 'e2e/src/app.e2e-spec.ts', `describe('app', () => { it('should pass', () => { expect(true).toBe(true); }); });` ); const angularJson = readJson('angular.json'); angularJson.projects[project].architect.e2e = { builder: '@angular-devkit/build-angular:protractor', options: { protractorConfig: 'e2e/protractor.conf.js', devServerTarget: `${project}:serve`, }, configurations: { production: { devServerTarget: `${project}:serve:production` }, }, }; updateFile('angular.json', JSON.stringify(angularJson, null, 2)); } function addCypress9() { runNgAdd('@cypress/schematic', '--e2e-update', '1.7.0'); packageInstall('cypress', null, '^9.0.0'); } function addCypress10() { runNgAdd('@cypress/schematic', '--e2e', 'latest'); } function addEsLint() { runNgAdd('@angular-eslint/schematics', undefined, 'latest'); } beforeEach(() => { project = uniq('proj'); packageManager = getSelectedPackageManager(); // TODO: solve issues with pnpm and remove this fallback packageManager = packageManager === 'pnpm' ? 'yarn' : packageManager; runNgNew(project, packageManager); }); afterEach(() => { cleanupProject(); }); it('should generate a workspace', () => { addProtractor(); // update package.json const packageJson = readJson('package.json'); packageJson.description = 'some description'; updateFile('package.json', JSON.stringify(packageJson, null, 2)); // update tsconfig.json const tsConfig = readJson('tsconfig.json'); tsConfig.compilerOptions.paths = { a: ['b'] }; updateFile('tsconfig.json', JSON.stringify(tsConfig, null, 2)); // add an extra script file updateFile('src/scripts.js', 'const x = 1;'); // update angular.json const angularJson = readJson('angular.json'); angularJson.projects[project].architect.build.options.scripts = angularJson.projects[project].architect.test.options.scripts = [ 'src/scripts.js', ]; angularJson.projects[project].architect.test.options.styles = [ 'src/styles.css', ]; updateFile('angular.json', JSON.stringify(angularJson, null, 2)); // confirm that @nrwl dependencies do not exist yet expect(packageJson.devDependencies['@nrwl/workspace']).not.toBeDefined(); // run ng add runNgAdd('@nrwl/angular', '--npm-scope projscope --default-base main'); // check that prettier config exits and that files have been moved checkFilesExist( '.vscode/extensions.json', '.prettierrc', `apps/${project}/src/main.ts`, `apps/${project}/src/app/app.module.ts` ); // check the right VSCode extensions are recommended expect(readJson('.vscode/extensions.json').recommendations).toEqual([ 'angular.ng-template', 'nrwl.angular-console', 'dbaeumer.vscode-eslint', 'esbenp.prettier-vscode', ]); // check package.json const updatedPackageJson = readJson('package.json'); expect(updatedPackageJson.description).toEqual('some description'); expect(updatedPackageJson.scripts).toEqual({ ng: 'nx', start: 'nx serve', build: 'nx build', watch: 'nx build --watch --configuration development', test: 'nx test', postinstall: 'node ./decorate-angular-cli.js', }); expect(updatedPackageJson.devDependencies['@nrwl/workspace']).toBeDefined(); expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined(); // check nx.json const nxJson = readJson('nx.json'); expect(nxJson).toEqual({ affected: { defaultBase: 'main', }, npmScope: 'projscope', tasksRunnerOptions: { default: { options: { cacheableOperations: ['build', 'test', 'e2e'], }, runner: 'nx/tasks-runners/default', }, }, namedInputs: { default: ['{projectRoot}/**/*', 'sharedGlobals'], production: [ 'default', '!{projectRoot}/tsconfig.spec.json', '!{projectRoot}/**/*.spec.[jt]s', '!{projectRoot}/karma.conf.js', ], sharedGlobals: [], }, targetDefaults: { build: { dependsOn: ['^build'], inputs: ['production', '^production'], }, e2e: { inputs: ['default', '^production'], }, test: { inputs: ['default', '^production', '{workspaceRoot}/karma.conf.js'], }, }, }); // check angular.json does not exist checkFilesDoNotExist('angular.json'); // check project configuration const projectConfig = readJson(`apps/${project}/project.json`); expect(projectConfig.sourceRoot).toEqual(`apps/${project}/src`); expect(projectConfig.targets.build).toStrictEqual({ executor: '@angular-devkit/build-angular:browser', options: { outputPath: `dist/apps/${project}`, index: `apps/${project}/src/index.html`, main: `apps/${project}/src/main.ts`, polyfills: [`zone.js`], tsConfig: `apps/${project}/tsconfig.app.json`, assets: [ `apps/${project}/src/favicon.ico`, `apps/${project}/src/assets`, ], styles: [`apps/${project}/src/styles.css`], scripts: [`apps/${project}/src/scripts.js`], }, configurations: { production: { budgets: [ { type: 'initial', maximumWarning: '500kb', maximumError: '1mb', }, { type: 'anyComponentStyle', maximumWarning: '2kb', maximumError: '4kb', }, ], outputHashing: 'all', }, development: { buildOptimizer: false, optimization: false, vendorChunk: true, extractLicenses: false, sourceMap: true, namedChunks: true, }, }, defaultConfiguration: 'production', }); expect(projectConfig.targets.serve).toEqual({ executor: '@angular-devkit/build-angular:dev-server', configurations: { production: { browserTarget: `${project}:build:production` }, development: { browserTarget: `${project}:build:development` }, }, defaultConfiguration: 'development', }); expect(projectConfig.targets.test).toStrictEqual({ executor: '@angular-devkit/build-angular:karma', options: { polyfills: [`zone.js`, `zone.js/testing`], tsConfig: `apps/${project}/tsconfig.spec.json`, assets: [ `apps/${project}/src/favicon.ico`, `apps/${project}/src/assets`, ], styles: [`apps/${project}/src/styles.css`], scripts: [`apps/${project}/src/scripts.js`], }, }); expect(projectConfig.targets.e2e).toBeUndefined(); // check e2e project config const e2eProjectConfig = readJson(`apps/${project}-e2e/project.json`); expect(e2eProjectConfig.targets.e2e).toEqual({ executor: '@angular-devkit/build-angular:protractor', options: { protractorConfig: `apps/${project}-e2e/protractor.conf.js`, devServerTarget: `${project}:serve`, }, configurations: { production: { devServerTarget: `${project}:serve:production`, }, }, }); runCLI(`build ${project} --configuration production --outputHashing none`); checkFilesExist(`dist/apps/${project}/main.js`); }); it('should handle a workspace with cypress v9', () => { addCypress9(); runNgAdd('@nrwl/angular', '--npm-scope projscope --skip-install'); const e2eProject = `${project}-e2e`; //check e2e project files checkFilesDoNotExist( 'cypress.json', 'cypress/tsconfig.json', 'cypress/integration/spec.ts', 'cypress/plugins/index.ts', 'cypress/support/commands.ts', 'cypress/support/index.ts' ); checkFilesExist( `apps/${e2eProject}/cypress.json`, `apps/${e2eProject}/tsconfig.json`, `apps/${e2eProject}/src/integration/spec.ts`, `apps/${e2eProject}/src/plugins/index.ts`, `apps/${e2eProject}/src/support/commands.ts`, `apps/${e2eProject}/src/support/index.ts` ); const projectConfig = readJson(`apps/${project}/project.json`); expect(projectConfig.targets['cypress-run']).toBeUndefined(); expect(projectConfig.targets['cypress-open']).toBeUndefined(); expect(projectConfig.targets.e2e).toBeUndefined(); // check e2e project config const e2eProjectConfig = readJson(`apps/${project}-e2e/project.json`); expect(e2eProjectConfig.targets['cypress-run']).toEqual({ executor: '@nrwl/cypress:cypress', options: { devServerTarget: `${project}:serve`, cypressConfig: `apps/${e2eProject}/cypress.json`, }, configurations: { production: { devServerTarget: `${project}:serve:production`, }, }, }); expect(e2eProjectConfig.targets['cypress-open']).toEqual({ executor: '@nrwl/cypress:cypress', options: { watch: true, headless: false, cypressConfig: `apps/${e2eProject}/cypress.json`, }, }); expect(e2eProjectConfig.targets.e2e).toEqual({ executor: '@nrwl/cypress:cypress', options: { devServerTarget: `${project}:serve`, watch: true, headless: false, cypressConfig: `apps/${e2eProject}/cypress.json`, }, configurations: { production: { devServerTarget: `${project}:serve:production`, }, }, }); }); it('should handle a workspace with cypress v10', () => { addCypress10(); runNgAdd('@nrwl/angular', '--npm-scope projscope --skip-install'); const e2eProject = `${project}-e2e`; //check e2e project files checkFilesDoNotExist( 'cypress.config.ts', 'cypress/tsconfig.json', 'cypress/e2e/spec.cy.ts', 'cypress/fixtures/example.json', 'cypress/support/commands.ts', 'cypress/support/e2e.ts' ); checkFilesExist( `apps/${e2eProject}/cypress.config.ts`, `apps/${e2eProject}/tsconfig.json`, `apps/${e2eProject}/src/e2e/spec.cy.ts`, `apps/${e2eProject}/src/fixtures/example.json`, `apps/${e2eProject}/src/support/commands.ts`, `apps/${e2eProject}/src/support/e2e.ts` ); const projectConfig = readJson(`apps/${project}/project.json`); expect(projectConfig.targets['cypress-run']).toBeUndefined(); expect(projectConfig.targets['cypress-open']).toBeUndefined(); expect(projectConfig.targets.e2e).toBeUndefined(); // check e2e project config const e2eProjectConfig = readJson(`apps/${project}-e2e/project.json`); expect(e2eProjectConfig.targets['cypress-run']).toEqual({ executor: '@nrwl/cypress:cypress', options: { devServerTarget: `${project}:serve`, cypressConfig: `apps/${e2eProject}/cypress.config.ts`, }, configurations: { production: { devServerTarget: `${project}:serve:production`, }, }, }); expect(e2eProjectConfig.targets['cypress-open']).toEqual({ executor: '@nrwl/cypress:cypress', options: { watch: true, headless: false, cypressConfig: `apps/${e2eProject}/cypress.config.ts`, }, }); expect(e2eProjectConfig.targets.e2e).toEqual({ executor: '@nrwl/cypress:cypress', options: { devServerTarget: `${project}:serve`, watch: true, headless: false, cypressConfig: `apps/${e2eProject}/cypress.config.ts`, }, configurations: { production: { devServerTarget: `${project}:serve:production`, }, }, }); }); // TODO(leo): The current Verdaccio setup fails to resolve older versions // of @nrwl/* packages, the @angular-eslint/builder package depends on an // older version of @nrwl/devkit so we skip this test for now. it.skip('should handle a workspace with ESLint', () => { addEsLint(); runNgAdd('@nrwl/angular', '--npm-scope projscope'); checkFilesExist(`apps/${project}/.eslintrc.json`, `.eslintrc.json`); const projectConfig = readJson(`apps/${project}/project.json`); expect(projectConfig.targets.lint).toStrictEqual({ executor: '@nrwl/linter:eslint', options: { lintFilePatterns: [ `apps/${project}/src/**/*.ts`, `apps/${project}/src/**/*.html`, ], }, }); let output = runCLI(`lint ${project}`); expect(output).toContain(`> nx run ${project}:lint`); expect(output).toContain('All files pass linting.'); expect(output).toContain( `Successfully ran target lint for project ${project}` ); output = runCLI(`lint ${project}`); expect(output).toContain(`> nx run ${project}:lint [local cache]`); expect(output).toContain('All files pass linting.'); expect(output).toContain( `Successfully ran target lint for project ${project}` ); }); 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 const app1 = uniq('app1'); runCommand(`ng g @schematics/angular:application ${app1}`); runNgAdd('@nrwl/angular', '--npm-scope projscope'); // check angular.json does not exist checkFilesDoNotExist('angular.json'); // check building project let output = runCLI(`build ${project} --outputHashing none`); expect(output).toContain( `> nx run ${project}:build:production --outputHashing none` ); expect(output).toContain( `Successfully ran target build for project ${project}` ); checkFilesExist(`dist/apps/${project}/main.js`); output = runCLI(`build ${project} --outputHashing none`); expect(output).toContain( `> nx run ${project}:build:production --outputHashing none [local cache]` ); expect(output).toContain( `Successfully ran target build for project ${project}` ); // check building app1 output = runCLI(`build ${app1} --outputHashing none`); expect(output).toContain( `> nx run ${app1}:build:production --outputHashing none` ); expect(output).toContain( `Successfully ran target build for project ${app1}` ); checkFilesExist(`dist/apps/${app1}/main.js`); output = runCLI(`build ${app1} --outputHashing none`); expect(output).toContain( `> nx run ${app1}:build:production --outputHashing none [local cache]` ); expect(output).toContain( `Successfully ran target build for project ${app1}` ); }); it('should support --preserve-angular-cli-layout', () => { // add another app and a library runCommand(`ng g @schematics/angular:application app2`); runCommand(`ng g @schematics/angular:library lib1`); runNgAdd('@nrwl/angular', '--preserve-angular-cli-layout'); // check project configs const projectJson = readJson('project.json'); expect(projectJson.sourceRoot).toEqual('src'); const app2ProjectJson = readJson('projects/app2/project.json'); expect(app2ProjectJson.sourceRoot).toEqual('projects/app2/src'); const lib1ProjectJson = readJson('projects/lib1/project.json'); expect(lib1ProjectJson.sourceRoot).toEqual('projects/lib1/src'); // check building an app let output = runCLI(`build ${project} --outputHashing none`); expect(output).toContain( `> nx run ${project}:build:production --outputHashing none` ); expect(output).toContain( `Successfully ran target build for project ${project}` ); checkFilesExist(`dist/${project}/main.js`); output = runCLI(`build ${project} --outputHashing none`); expect(output).toContain( `> nx run ${project}:build:production --outputHashing none [local cache]` ); expect(output).toContain( `Successfully ran target build for project ${project}` ); // 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'); }); });