chore(repo): split angular e2e tests into core and extensions (#6470)
This commit is contained in:
parent
f3b5ffc165
commit
1ca34f9669
3
.github/workflows/e2e-matrix.yml
vendored
3
.github/workflows/e2e-matrix.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
- yarn
|
- yarn
|
||||||
- pnpm
|
- pnpm
|
||||||
packages:
|
packages:
|
||||||
- e2e-angular
|
- e2e-angular-core,e2e-angular-extensions
|
||||||
- e2e-cli,e2e-nx-plugin,e2e-jest,e2e-linter
|
- e2e-cli,e2e-nx-plugin,e2e-jest,e2e-linter
|
||||||
- e2e-cypress
|
- e2e-cypress
|
||||||
- e2e-gatsby,e2e-react
|
- e2e-gatsby,e2e-react
|
||||||
@ -104,7 +104,6 @@ jobs:
|
|||||||
NODE_OPTIONS: --max_old_space_size=8192
|
NODE_OPTIONS: --max_old_space_size=8192
|
||||||
SELECTED_PM: ${{ matrix.package_manager }}
|
SELECTED_PM: ${{ matrix.package_manager }}
|
||||||
YARN_REGISTRY: http://localhost:4872
|
YARN_REGISTRY: http://localhost:4872
|
||||||
SELECTED_CLI: ${{ matrix.packages == 'e2e-angular' && 'angular' || 'nx' }}
|
|
||||||
|
|
||||||
- name: Setup tmate session
|
- name: Setup tmate session
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled && failure() }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled && failure() }}
|
||||||
|
|||||||
@ -6,5 +6,5 @@ module.exports = {
|
|||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||||
maxWorkers: 1,
|
maxWorkers: 1,
|
||||||
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
|
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
|
||||||
displayName: 'e2e-angular',
|
displayName: 'e2e-angular-core',
|
||||||
};
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"root": "e2e/angular",
|
"root": "e2e/angular-core",
|
||||||
"sourceRoot": "e2e/angular",
|
"sourceRoot": "e2e/angular-core",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"targets": {
|
"targets": {
|
||||||
"e2e": {
|
"e2e": {
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"command": "yarn e2e-build-package-publish"
|
"command": "yarn e2e-build-package-publish"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "nx run-e2e-tests e2e-angular"
|
"command": "nx run-e2e-tests e2e-angular-core"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parallel": false
|
"parallel": false
|
||||||
@ -23,11 +23,11 @@
|
|||||||
"run-e2e-tests": {
|
"run-e2e-tests": {
|
||||||
"executor": "@nrwl/jest:jest",
|
"executor": "@nrwl/jest:jest",
|
||||||
"options": {
|
"options": {
|
||||||
"jestConfig": "e2e/angular/jest.config.js",
|
"jestConfig": "e2e/angular-core/jest.config.js",
|
||||||
"passWithNoTests": true,
|
"passWithNoTests": true,
|
||||||
"runInBand": true
|
"runInBand": true
|
||||||
},
|
},
|
||||||
"outputs": ["coverage/e2e/angular"]
|
"outputs": ["coverage/e2e/angular-core"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"implicitDependencies": ["angular"]
|
"implicitDependencies": ["angular"]
|
||||||
118
e2e/angular-core/src/angular-core.test.ts
Normal file
118
e2e/angular-core/src/angular-core.test.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
expectTestsPass,
|
||||||
|
getSelectedPackageManager,
|
||||||
|
getSize,
|
||||||
|
killPorts,
|
||||||
|
newProject,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
tmpProjPath,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
runCypressTests,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
|
import { names } from '@nrwl/devkit';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('core', () => {
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
beforeEach(() => (proj = newProject()));
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
// TODO: npm build is failing for Angular because of webpack 4
|
||||||
|
// remove this condition once `node` is migrated to webpack 5
|
||||||
|
if (getSelectedPackageManager() !== 'npm') {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:lib ${mylib} --directory=myDir --add-module-spec --no-interactive`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`apps/my-dir/${myapp}/src/app/app.module.ts`,
|
||||||
|
`
|
||||||
|
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';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [BrowserModule, MyDir${names(mylib).className}Module],
|
||||||
|
declarations: [AppComponent],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
runCLI(`build my-dir-${myapp} --prod --output-hashing none`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/apps/my-dir/${myapp}/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`)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
|
||||||
|
);
|
||||||
|
expect(es2015BundleSize).toBeLessThanOrEqual(160000);
|
||||||
|
|
||||||
|
// running tests for the app
|
||||||
|
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
||||||
|
|
||||||
|
// running tests for the lib
|
||||||
|
expectTestsPass(await runCLIAsync(`test my-dir-${mylib} --no-watch`));
|
||||||
|
|
||||||
|
if (runCypressTests()) {
|
||||||
|
const e2eResults = runCLI(
|
||||||
|
`e2e my-dir-${myapp}-e2e --headless --no-watch`
|
||||||
|
);
|
||||||
|
expect(e2eResults).toContain('All specs passed!');
|
||||||
|
expect(await killPorts()).toBeTruthy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000000);
|
||||||
|
|
||||||
|
it('should support building in parallel', () => {
|
||||||
|
// TODO: npm build is failing for Angular because of webpack 4
|
||||||
|
// remove this condition once `node` is migrated to webpack 5
|
||||||
|
if (getSelectedPackageManager() !== 'npm') {
|
||||||
|
if (getSelectedPackageManager() === 'pnpm') {
|
||||||
|
// TODO: This tests fails with pnpm but we should still enable this for other package managers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
const myapp2 = uniq('myapp');
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp}`);
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp2}`);
|
||||||
|
|
||||||
|
runCLI('run-many --target build --all --parallel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support Ivy', async () => {
|
||||||
|
// TODO: npm build is failing for Angular because of webpack 4
|
||||||
|
// remove this condition once `node` is migrated to webpack 5
|
||||||
|
if (getSelectedPackageManager() !== 'npm') {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:app ${myapp} --directory=myDir --routing --enable-ivy`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI(`build my-dir-${myapp} --aot`);
|
||||||
|
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
||||||
|
}
|
||||||
|
}, 1000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
70
e2e/angular-core/src/angular-linting.test.ts
Normal file
70
e2e/angular-core/src/angular-linting.test.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
newProject,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('linting', () => {
|
||||||
|
beforeEach(() => newProject());
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
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`);
|
||||||
|
expect(runCLI(`lint ${myapp}`)).toContain('All files pass linting.');
|
||||||
|
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
runCLI(`generate @nrwl/angular:lib ${mylib} --linter=eslint`);
|
||||||
|
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`);
|
||||||
|
|
||||||
|
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(
|
||||||
|
`The selector should start with one of these prefixes`
|
||||||
|
);
|
||||||
|
expect(appLintStdOut).toContain(`7:18`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
48
e2e/angular-core/src/config-compat.test.ts
Normal file
48
e2e/angular-core/src/config-compat.test.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
process.env.SELECTED_CLI = 'angular';
|
||||||
|
|
||||||
|
import {
|
||||||
|
expectTestsPass,
|
||||||
|
newProject,
|
||||||
|
readJson,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('config compat', () => {
|
||||||
|
beforeEach(() => newProject());
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
||||||
|
|
||||||
|
// update the angular.json
|
||||||
|
const workspaceJson = readJson(`angular.json`);
|
||||||
|
workspaceJson.version = 2;
|
||||||
|
workspaceJson.projects[myapp].targets = updateConfig(
|
||||||
|
workspaceJson.projects[myapp].architect
|
||||||
|
);
|
||||||
|
workspaceJson.generators = workspaceJson.schematics;
|
||||||
|
delete workspaceJson.schematics;
|
||||||
|
updateFile('angular.json', JSON.stringify(workspaceJson, null, 2));
|
||||||
|
|
||||||
|
const myapp2 = uniq('myapp');
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp2} --no-interactive`);
|
||||||
|
expectTestsPass(await runCLIAsync(`test ${myapp2} --no-watch`));
|
||||||
|
}, 1000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateConfig(targets: any) {
|
||||||
|
const res = {};
|
||||||
|
Object.entries(targets).forEach(([name, t]: any) => {
|
||||||
|
t.executor = t.builder;
|
||||||
|
delete t.builder;
|
||||||
|
res[name] = t;
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
366
e2e/angular-core/src/ng-add.test.ts
Normal file
366
e2e/angular-core/src/ng-add.test.ts
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
process.env.SELECTED_CLI = 'angular';
|
||||||
|
|
||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
readJson,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
runCommand,
|
||||||
|
runNgAdd,
|
||||||
|
runNgNew,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
|
// TODO: Check why generated angular app is different
|
||||||
|
xdescribe('Angular Package', () => {
|
||||||
|
describe('convert to Nx workspace', () => {
|
||||||
|
let proj;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
removeProject({ onlyOnCI: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a workspace', () => {
|
||||||
|
proj = uniq('proj');
|
||||||
|
runNgNew(proj);
|
||||||
|
|
||||||
|
// update package.json
|
||||||
|
const packageJson = readJson('package.json');
|
||||||
|
packageJson.description = 'some description';
|
||||||
|
updateFile('package.json', JSON.stringify(packageJson, null, 2));
|
||||||
|
// confirm that @nrwl and @ngrx dependencies do not exist yet
|
||||||
|
expect(packageJson.devDependencies['@nrwl/workspace']).not.toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/store']).not.toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/effects']).not.toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/router-store']).not.toBeDefined();
|
||||||
|
expect(
|
||||||
|
packageJson.devDependencies['@ngrx/store-devtools']
|
||||||
|
).not.toBeDefined();
|
||||||
|
|
||||||
|
// update tsconfig.json
|
||||||
|
const tsconfigJson = readJson('tsconfig.json');
|
||||||
|
tsconfigJson.compilerOptions.paths = { a: ['b'] };
|
||||||
|
updateFile('tsconfig.json', JSON.stringify(tsconfigJson, null, 2));
|
||||||
|
|
||||||
|
updateFile('src/scripts.ts', '');
|
||||||
|
|
||||||
|
// update angular-cli.json
|
||||||
|
const angularCLIJson = readJson('angular.json');
|
||||||
|
angularCLIJson.projects[proj].architect.build.options.scripts =
|
||||||
|
angularCLIJson.projects[proj].architect.test.options.scripts = [
|
||||||
|
'src/scripts.ts',
|
||||||
|
];
|
||||||
|
angularCLIJson.projects[proj].architect.test.options.styles = [
|
||||||
|
'src/styles.css',
|
||||||
|
];
|
||||||
|
updateFile('angular.json', JSON.stringify(angularCLIJson, null, 2));
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
runNgAdd('--npm-scope projscope');
|
||||||
|
|
||||||
|
// check that prettier config exits and that files have been moved!
|
||||||
|
checkFilesExist(
|
||||||
|
'.vscode/extensions.json',
|
||||||
|
'.prettierrc',
|
||||||
|
`apps/${proj}/src/main.ts`,
|
||||||
|
`apps/${proj}/src/app/app.module.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readJson('.vscode/extensions.json').recommendations).toEqual([
|
||||||
|
'nrwl.angular-console',
|
||||||
|
'angular.ng-template',
|
||||||
|
'dbaeumer.vscode-eslint',
|
||||||
|
'esbenp.prettier-vscode',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// check that package.json got merged
|
||||||
|
const updatedPackageJson = readJson('package.json');
|
||||||
|
expect(updatedPackageJson.description).toEqual('some description');
|
||||||
|
expect(updatedPackageJson.scripts).toEqual({
|
||||||
|
ng: 'nx',
|
||||||
|
nx: 'nx',
|
||||||
|
start: 'ng serve',
|
||||||
|
build: 'ng build',
|
||||||
|
test: 'ng test',
|
||||||
|
lint: 'nx workspace-lint && ng lint',
|
||||||
|
e2e: 'ng e2e',
|
||||||
|
'affected:apps': 'nx affected:apps',
|
||||||
|
'affected:libs': 'nx affected:libs',
|
||||||
|
'affected:build': 'nx affected:build',
|
||||||
|
'affected:e2e': 'nx affected:e2e',
|
||||||
|
'affected:test': 'nx affected:test',
|
||||||
|
'affected:lint': 'nx affected:lint',
|
||||||
|
'affected:dep-graph': 'nx affected:dep-graph',
|
||||||
|
affected: 'nx affected',
|
||||||
|
format: 'nx format:write',
|
||||||
|
'format:write': 'nx format:write',
|
||||||
|
'format:check': 'nx format:check',
|
||||||
|
update: 'ng update @nrwl/workspace',
|
||||||
|
'update:check': 'ng update',
|
||||||
|
postinstall: 'node ./decorate-angular-cli.js',
|
||||||
|
'dep-graph': 'nx dep-graph',
|
||||||
|
'workspace-generator': 'nx workspace-generator',
|
||||||
|
help: 'nx help',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
updatedPackageJson.devDependencies['@nrwl/workspace']
|
||||||
|
).toBeDefined();
|
||||||
|
expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined();
|
||||||
|
|
||||||
|
const nxJson = readJson('nx.json');
|
||||||
|
expect(nxJson).toEqual({
|
||||||
|
npmScope: 'projscope',
|
||||||
|
implicitDependencies: {
|
||||||
|
'angular.json': '*',
|
||||||
|
'package.json': '*',
|
||||||
|
'tslint.json': '*',
|
||||||
|
'.eslintrc.json': '*',
|
||||||
|
'tsconfig.base.json': '*',
|
||||||
|
'nx.json': '*',
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
[`${proj}`]: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
[`${proj}-e2e`]: {
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if angular-cli.json get merged
|
||||||
|
const updatedAngularCLIJson = readJson('angular.json');
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].root).toEqual(`apps/${proj}`);
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].sourceRoot).toEqual(
|
||||||
|
`apps/${proj}/src`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].architect.build).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:browser',
|
||||||
|
options: {
|
||||||
|
aot: true,
|
||||||
|
outputPath: `dist/apps/${proj}`,
|
||||||
|
index: `apps/${proj}/src/index.html`,
|
||||||
|
main: `apps/${proj}/src/main.ts`,
|
||||||
|
polyfills: `apps/${proj}/src/polyfills.ts`,
|
||||||
|
tsConfig: `apps/${proj}/tsconfig.app.json`,
|
||||||
|
assets: [`apps/${proj}/src/favicon.ico`, `apps/${proj}/src/assets`],
|
||||||
|
styles: [`apps/${proj}/src/styles.css`],
|
||||||
|
scripts: [`apps/${proj}/src/scripts.ts`],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
fileReplacements: [
|
||||||
|
{
|
||||||
|
replace: `apps/${proj}/src/environments/environment.ts`,
|
||||||
|
with: `apps/${proj}/src/environments/environment.prod.ts`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
budgets: [
|
||||||
|
{
|
||||||
|
maximumError: '5mb',
|
||||||
|
maximumWarning: '2mb',
|
||||||
|
type: 'initial',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maximumError: '10kb',
|
||||||
|
maximumWarning: '6kb',
|
||||||
|
type: 'anyComponentStyle',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
optimization: true,
|
||||||
|
outputHashing: 'all',
|
||||||
|
sourceMap: false,
|
||||||
|
extractCss: true,
|
||||||
|
namedChunks: false,
|
||||||
|
extractLicenses: true,
|
||||||
|
vendorChunk: false,
|
||||||
|
buildOptimizer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].architect.serve).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:dev-server',
|
||||||
|
options: {
|
||||||
|
browserTarget: 'proj:build',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
browserTarget: 'proj:build:production',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].architect.test).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:karma',
|
||||||
|
options: {
|
||||||
|
main: `apps/${proj}/src/test.ts`,
|
||||||
|
polyfills: `apps/${proj}/src/polyfills.ts`,
|
||||||
|
tsConfig: `apps/${proj}/tsconfig.spec.json`,
|
||||||
|
karmaConfig: `apps/${proj}/karma.conf.js`,
|
||||||
|
styles: [`apps/${proj}/src/styles.css`],
|
||||||
|
scripts: [`apps/${proj}/src/scripts.ts`],
|
||||||
|
assets: [`apps/${proj}/src/favicon.ico`, `apps/${proj}/src/assets`],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].architect.lint).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
tsConfig: [
|
||||||
|
`apps/${proj}/tsconfig.app.json`,
|
||||||
|
`apps/${proj}/tsconfig.spec.json`,
|
||||||
|
],
|
||||||
|
exclude: ['**/node_modules/**'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedAngularCLIJson.projects[`${proj}-e2e`].root).toEqual(
|
||||||
|
`apps/${proj}-e2e`
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
updatedAngularCLIJson.projects[`${proj}-e2e`].architect.e2e
|
||||||
|
).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:protractor',
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
devServerTarget: `${proj}:serve:production`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
protractorConfig: `apps/${proj}-e2e/protractor.conf.js`,
|
||||||
|
devServerTarget: `${proj}:serve`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
updatedAngularCLIJson.projects[`${proj}-e2e`].architect.lint
|
||||||
|
).toEqual({
|
||||||
|
builder: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
tsConfig: `apps/${proj}-e2e/tsconfig.json`,
|
||||||
|
exclude: ['**/node_modules/**'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedTslint = readJson('tslint.json');
|
||||||
|
expect(updatedTslint.rules['nx-enforce-module-boundaries']).toEqual([
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
allow: [],
|
||||||
|
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
runCLI('build --prod --outputHashing none');
|
||||||
|
checkFilesExist(`dist/apps/${proj}/main-es2015.js`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a workspace and not change dependencies, devDependencies, or vscode extensions if they already exist', () => {
|
||||||
|
// create a new AngularCLI app
|
||||||
|
proj = uniq('proj');
|
||||||
|
runNgNew(proj);
|
||||||
|
const schematicsVersion = '12.0.0';
|
||||||
|
const ngrxVersion = '12.0.0';
|
||||||
|
// update package.json
|
||||||
|
const existingPackageJson = readJson('package.json');
|
||||||
|
existingPackageJson.devDependencies['@nrwl/workspace'] =
|
||||||
|
schematicsVersion;
|
||||||
|
existingPackageJson.dependencies['@ngrx/store'] = ngrxVersion;
|
||||||
|
existingPackageJson.dependencies['@ngrx/effects'] = ngrxVersion;
|
||||||
|
existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion;
|
||||||
|
existingPackageJson.devDependencies['@ngrx/store-devtools'] = ngrxVersion;
|
||||||
|
updateFile('package.json', JSON.stringify(existingPackageJson, null, 2));
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
'.vscode/extensions.json',
|
||||||
|
JSON.stringify({
|
||||||
|
recommendations: ['eamodio.gitlens', 'angular.ng-template'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// run the command
|
||||||
|
runNgAdd('--npm-scope projscope --skip-install');
|
||||||
|
|
||||||
|
// check that dependencies and devDependencies remained the same
|
||||||
|
const packageJson = readJson('package.json');
|
||||||
|
expect(packageJson.devDependencies['@nrwl/workspace']).toEqual(
|
||||||
|
schematicsVersion
|
||||||
|
);
|
||||||
|
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
|
||||||
|
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
|
||||||
|
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
|
||||||
|
ngrxVersion
|
||||||
|
);
|
||||||
|
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
|
||||||
|
ngrxVersion
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readJson('.vscode/extensions.json').recommendations).toEqual([
|
||||||
|
'eamodio.gitlens',
|
||||||
|
'angular.ng-template',
|
||||||
|
'nrwl.angular-console',
|
||||||
|
'dbaeumer.vscode-eslint',
|
||||||
|
'esbenp.prettier-vscode',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle different types of errors', () => {
|
||||||
|
// create a new AngularCLI app
|
||||||
|
proj = uniq('proj');
|
||||||
|
runNgNew(proj);
|
||||||
|
|
||||||
|
// Only remove e2e directory
|
||||||
|
runCommand('mv e2e e2e-bak');
|
||||||
|
try {
|
||||||
|
runNgAdd('--npm-scope projscope --skip-install');
|
||||||
|
fail('Did not handle not having a e2e directory');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.stderr.toString()).toContain(
|
||||||
|
'Your workspace could not be converted into an Nx Workspace because of the above error.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put e2e back
|
||||||
|
runCommand('mv e2e-bak e2e');
|
||||||
|
|
||||||
|
// Remove package.json
|
||||||
|
runCommand('mv package.json package.json.bak');
|
||||||
|
try {
|
||||||
|
runNgAdd('--npm-scope projscope --skip-install');
|
||||||
|
fail('Did not handle not having a package.json');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.stderr.toString()).toContain(
|
||||||
|
'Your workspace could not be converted into an Nx Workspace because of the above error.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put package.json back
|
||||||
|
runCommand('mv package.json.bak package.json');
|
||||||
|
|
||||||
|
// Remove src
|
||||||
|
runCommand('mv src src-bak');
|
||||||
|
try {
|
||||||
|
runNgAdd('--npm-scope projscope --skip-install');
|
||||||
|
fail('Did not handle not having a src directory');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.stderr.toString()).toContain('Path: src does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put src back
|
||||||
|
runCommand('mv src-bak src');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support preserveAngularCLILayout', () => {
|
||||||
|
proj = uniq('proj');
|
||||||
|
runNgNew(proj);
|
||||||
|
runNgAdd('--preserveAngularCLILayout');
|
||||||
|
|
||||||
|
const updatedAngularCLIJson = readJson('angular.json');
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].root).toEqual('');
|
||||||
|
expect(updatedAngularCLIJson.projects[proj].sourceRoot).toEqual('src');
|
||||||
|
|
||||||
|
const output = runCLI('build');
|
||||||
|
expect(output).toContain(`> ng run ${proj}:build`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
10
e2e/angular-extensions/jest.config.js
Normal file
10
e2e/angular-extensions/jest.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||||
|
maxWorkers: 1,
|
||||||
|
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
|
||||||
|
displayName: 'e2e-angular-extensions',
|
||||||
|
};
|
||||||
34
e2e/angular-extensions/project.json
Normal file
34
e2e/angular-extensions/project.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"root": "e2e/angular-extensions",
|
||||||
|
"sourceRoot": "e2e/angular-extensions",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {
|
||||||
|
"executor": "@nrwl/workspace:run-commands",
|
||||||
|
"options": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "yarn e2e-start-local-registry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "yarn e2e-build-package-publish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx run-e2e-tests e2e-angular-extensions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parallel": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"run-e2e-tests": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "e2e/angular-extensions/jest.config.js",
|
||||||
|
"passWithNoTests": true,
|
||||||
|
"runInBand": true
|
||||||
|
},
|
||||||
|
"outputs": ["coverage/e2e/angular-extensions"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implicitDependencies": ["angular"]
|
||||||
|
}
|
||||||
88
e2e/angular-extensions/src/angular-app.test.ts
Normal file
88
e2e/angular-extensions/src/angular-app.test.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
process.env.SELECTED_CLI = 'angular';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getSelectedPackageManager,
|
||||||
|
newProject,
|
||||||
|
readFile,
|
||||||
|
readJson,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
import { names } from '@nrwl/devkit';
|
||||||
|
|
||||||
|
// TODO: Check why this fails on yarn and npm
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('app builder', () => {
|
||||||
|
let app;
|
||||||
|
let buildableLib;
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
// This fails with pnpm due to incompatibilities with ngcc.
|
||||||
|
// Since this suite has a single test, we wrap everything to avoid the hooks to run and
|
||||||
|
// waste time.
|
||||||
|
if (getSelectedPackageManager() !== 'pnpm') {
|
||||||
|
beforeEach(() => {
|
||||||
|
app = uniq('app');
|
||||||
|
buildableLib = uniq('buildlib1');
|
||||||
|
|
||||||
|
proj = newProject();
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:app ${app} --style=css --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${buildableLib} --buildable=true --no-interactive`
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the app module to include a ref to the buildable lib
|
||||||
|
updateFile(
|
||||||
|
`apps/${app}/src/app/app.module.ts`,
|
||||||
|
`
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import {${
|
||||||
|
names(buildableLib).className
|
||||||
|
}Module} from '@${proj}/${buildableLib}';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent],
|
||||||
|
imports: [BrowserModule, ${names(buildableLib).className}Module],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the angular.json
|
||||||
|
const workspaceJson = readJson(`angular.json`);
|
||||||
|
workspaceJson.projects[app].architect.build.builder =
|
||||||
|
'@nrwl/angular:webpack-browser';
|
||||||
|
updateFile('angular.json', JSON.stringify(workspaceJson, null, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('should build the dependent buildable lib as well as the app', () => {
|
||||||
|
const libOutput = runCLI(
|
||||||
|
`build ${app} --with-deps --configuration=development`
|
||||||
|
);
|
||||||
|
expect(libOutput).toContain(
|
||||||
|
`Building entry point '@${proj}/${buildableLib}'`
|
||||||
|
);
|
||||||
|
expect(libOutput).toContain(`nx run ${app}: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`);
|
||||||
|
expect(mainBundle).toContain(`dist/libs/${buildableLib}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
it('Skip tests with pnpm', () => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
178
e2e/angular-extensions/src/angular-library.test.ts
Normal file
178
e2e/angular-extensions/src/angular-library.test.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
process.env.SELECTED_CLI = 'angular';
|
||||||
|
|
||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
getSelectedPackageManager,
|
||||||
|
newProject,
|
||||||
|
readJson,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
import { names } from '@nrwl/devkit';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
['publishable', 'buildable'].forEach((testConfig) => {
|
||||||
|
describe(`library builder - ${testConfig}`, () => {
|
||||||
|
/**
|
||||||
|
* Graph:
|
||||||
|
*
|
||||||
|
* childLib
|
||||||
|
* /
|
||||||
|
* parentLib =>
|
||||||
|
* \
|
||||||
|
* \
|
||||||
|
* childLib2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
let parentLib: string;
|
||||||
|
let childLib: string;
|
||||||
|
let childLib2: string;
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parentLib = uniq('parentlib');
|
||||||
|
childLib = uniq('childlib');
|
||||||
|
childLib2 = uniq('childlib2');
|
||||||
|
|
||||||
|
proj = newProject();
|
||||||
|
|
||||||
|
if (testConfig === 'buildable') {
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${parentLib} --buildable=true --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${childLib} --buildable=true --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${childLib2} --buildable=true --no-interactive`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${parentLib} --publishable=true --importPath=@${proj}/${parentLib} --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${childLib} --publishable=true --importPath=@${proj}/${childLib} --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:library ${childLib2} --publishable=true --importPath=@${proj}/${childLib2} --no-interactive`
|
||||||
|
);
|
||||||
|
|
||||||
|
// create secondary entrypoint
|
||||||
|
updateFile(
|
||||||
|
`libs/${childLib}/sub/package.json`,
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ngPackage": {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
updateFile(
|
||||||
|
`libs/${childLib}/sub/src/lib/sub.module.ts`,
|
||||||
|
`
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
@NgModule({ imports: [CommonModule] })
|
||||||
|
export class SubModule {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`libs/${childLib}/sub/src/public_api.ts`,
|
||||||
|
`export * from './lib/sub.module';`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`libs/${childLib}/sub/src/index.ts`,
|
||||||
|
`export * from './public_api';`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(`tsconfig.base.json`, (s) => {
|
||||||
|
return s.replace(
|
||||||
|
`"@${proj}/${childLib}": ["libs/${childLib}/src/index.ts"],`,
|
||||||
|
`"@${proj}/${childLib}": ["libs/${childLib}/src/index.ts"],
|
||||||
|
"@${proj}/${childLib}/sub": ["libs/${childLib}/sub/src/index.ts"],
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create dependencies by importing
|
||||||
|
const createDep = (parent, children: string[]) => {
|
||||||
|
let moduleContent = `
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
${children
|
||||||
|
.map(
|
||||||
|
(entry) =>
|
||||||
|
`import { ${
|
||||||
|
names(entry).className
|
||||||
|
}Module } from '@${proj}/${entry}';`
|
||||||
|
)
|
||||||
|
.join('\n')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (testConfig === 'publishable') {
|
||||||
|
moduleContent += `
|
||||||
|
import { SubModule } from '@${proj}/${childLib}/sub';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, ${children
|
||||||
|
.map((entry) => `${names(entry).className}Module`)
|
||||||
|
.join(',')}, SubModule]
|
||||||
|
})
|
||||||
|
export class ${names(parent).className}Module {}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`libs/${parent}/src/lib/${parent}.module.ts`,
|
||||||
|
moduleContent
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
createDep(parentLib, [childLib, childLib2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('empty test to make jest happy', () => {});
|
||||||
|
|
||||||
|
// These fail with pnpm due to incompatibilities with ngcc for buildable libraries.
|
||||||
|
if (
|
||||||
|
getSelectedPackageManager() !== 'pnpm' ||
|
||||||
|
testConfig === 'publishable'
|
||||||
|
) {
|
||||||
|
it('should build the library when it does not have any deps', () => {
|
||||||
|
runCLI(`build ${childLib}`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/libs/${childLib}/package.json`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly add references to any dependency into the parent package.json', () => {
|
||||||
|
runCLI(`build ${childLib}`);
|
||||||
|
runCLI(`build ${childLib2}`);
|
||||||
|
runCLI(`build ${parentLib}`);
|
||||||
|
|
||||||
|
checkFilesExist(
|
||||||
|
`dist/libs/${childLib}/package.json`,
|
||||||
|
`dist/libs/${childLib2}/package.json`,
|
||||||
|
`dist/libs/${parentLib}/package.json`
|
||||||
|
);
|
||||||
|
|
||||||
|
const jsonFile = readJson(`dist/libs/${parentLib}/package.json`);
|
||||||
|
|
||||||
|
expect(jsonFile.dependencies['tslib']).toMatch(/\^2\.\d+\.\d+/); // match any ^2.x.x
|
||||||
|
expect(
|
||||||
|
jsonFile.peerDependencies[`@${proj}/${childLib}`]
|
||||||
|
).toBeDefined();
|
||||||
|
expect(
|
||||||
|
jsonFile.peerDependencies[`@${proj}/${childLib2}`]
|
||||||
|
).toBeDefined();
|
||||||
|
expect(jsonFile.peerDependencies['@angular/common']).toBeDefined();
|
||||||
|
expect(jsonFile.peerDependencies['@angular/core']).toBeDefined();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
50
e2e/angular-extensions/src/angular-router-config.test.ts
Normal file
50
e2e/angular-extensions/src/angular-router-config.test.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
expectTestsPass,
|
||||||
|
getSelectedPackageManager,
|
||||||
|
newProject,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
uniq,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('router config', () => {
|
||||||
|
beforeEach(() => newProject());
|
||||||
|
afterEach(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('should support router config generation (lazy)', async () => {
|
||||||
|
if (getSelectedPackageManager() !== 'npm') {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --lazy --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI(`build my-dir-${myapp} --aot`);
|
||||||
|
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
||||||
|
}
|
||||||
|
}, 1000000);
|
||||||
|
|
||||||
|
it('should support router config generation (eager)', async () => {
|
||||||
|
// TODO: npm build is failing for Angular because of webpack 4
|
||||||
|
// remove this condition once `node` is migrated to webpack 5
|
||||||
|
if (getSelectedPackageManager() !== 'npm') {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`
|
||||||
|
);
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI(`build my-dir-${myapp} --aot`);
|
||||||
|
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
||||||
|
}
|
||||||
|
}, 1000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
72
e2e/angular-extensions/src/ngrx.test.ts
Normal file
72
e2e/angular-extensions/src/ngrx.test.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
expectTestsPass,
|
||||||
|
newProject,
|
||||||
|
readJson,
|
||||||
|
removeProject,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
uniq,
|
||||||
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
|
describe('Angular Package', () => {
|
||||||
|
describe('ngrx', () => {
|
||||||
|
beforeEach(() => newProject());
|
||||||
|
afterAll(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
||||||
|
|
||||||
|
// Generate root ngrx state management
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root --minimal=false --syntax=classes --useDataPersistence=true`
|
||||||
|
);
|
||||||
|
const packageJson = readJson('package.json');
|
||||||
|
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
|
||||||
|
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
|
||||||
|
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
// Generate feature library and ngrx state within that library
|
||||||
|
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade --syntax=classes`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
|
||||||
|
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
|
||||||
|
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
|
||||||
|
}, 1000000);
|
||||||
|
|
||||||
|
it('should work with creators', async () => {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
runCLI(`generate @nrwl/angular:app ${myapp} --routing --no-interactive`);
|
||||||
|
|
||||||
|
// Generate root ngrx state management
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root`
|
||||||
|
);
|
||||||
|
const packageJson = readJson('package.json');
|
||||||
|
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
|
||||||
|
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
|
||||||
|
expect(packageJson.devDependencies['@ngrx/schematics']).toBeDefined();
|
||||||
|
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
|
||||||
|
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
// Generate feature library and ngrx state within that library
|
||||||
|
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
|
||||||
|
|
||||||
|
const flags = `--facade --barrels`;
|
||||||
|
runCLI(
|
||||||
|
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts ${flags}`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
|
||||||
|
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
|
||||||
|
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
|
||||||
|
}, 1000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -14,64 +14,65 @@ import {
|
|||||||
} from '@nrwl/e2e/utils';
|
} from '@nrwl/e2e/utils';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
|
|
||||||
describe('Storybook schematics', () => {
|
describe('Angular Package', () => {
|
||||||
let proj: string;
|
describe('storybook schematics', () => {
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
beforeEach(() => (proj = newProject()));
|
beforeEach(() => (proj = newProject()));
|
||||||
afterAll(() => removeProject({ onlyOnCI: true }));
|
afterAll(() => removeProject({ onlyOnCI: true }));
|
||||||
|
|
||||||
it('should not overwrite global storybook config files', () => {
|
it('should not overwrite global storybook config files', () => {
|
||||||
const angularStorybookLib = uniq('test-ui-lib-angular');
|
const angularStorybookLib = uniq('test-ui-lib-angular');
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:lib ${angularStorybookLib} --no-interactive`
|
`generate @nrwl/angular:lib ${angularStorybookLib} --no-interactive`
|
||||||
);
|
);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
checkFilesExist(`.storybook/main.js`);
|
checkFilesExist(`.storybook/main.js`);
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(`.storybook/main.js`),
|
tmpProjPath(`.storybook/main.js`),
|
||||||
`
|
`
|
||||||
module.exports = {
|
module.exports = {
|
||||||
stories: [],
|
stories: [],
|
||||||
addons: ['@storybook/addon-essentials'],
|
addons: ['@storybook/addon-essentials'],
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('hi there');
|
console.log('hi there');
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate another lib with storybook config
|
// generate another lib with storybook config
|
||||||
const anotherAngularStorybookLib = uniq('test-ui-lib-angular2');
|
const anotherAngularStorybookLib = uniq('test-ui-lib-angular2');
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:lib ${anotherAngularStorybookLib} --no-interactive`
|
`generate @nrwl/angular:lib ${anotherAngularStorybookLib} --no-interactive`
|
||||||
);
|
);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:storybook-configuration ${anotherAngularStorybookLib} --generateStories --no-interactive`
|
`generate @nrwl/angular:storybook-configuration ${anotherAngularStorybookLib} --generateStories --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(readFile(`.storybook/main.js`)).toContain(
|
expect(readFile(`.storybook/main.js`)).toContain(
|
||||||
`console.log('hi there');`
|
`console.log('hi there');`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('build storybook', () => {
|
describe('build storybook', () => {
|
||||||
it('should execute e2e tests using Cypress running against Storybook', async () => {
|
it('should execute e2e tests using Cypress running against Storybook', async () => {
|
||||||
if (isNotWindows()) {
|
if (isNotWindows()) {
|
||||||
const myapp = uniq('myapp');
|
const myapp = uniq('myapp');
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
||||||
|
|
||||||
const myAngularLib = uniq('test-ui-lib');
|
const myAngularLib = uniq('test-ui-lib');
|
||||||
createTestUILib(myAngularLib);
|
createTestUILib(myAngularLib);
|
||||||
const myReactLib = uniq('test-ui-lib-react');
|
const myReactLib = uniq('test-ui-lib-react');
|
||||||
runCLI(`generate @nrwl/react:lib ${myReactLib} --no-interactive`);
|
runCLI(`generate @nrwl/react:lib ${myReactLib} --no-interactive`);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/react:component Button --project=${myReactLib} --no-interactive`
|
`generate @nrwl/react:component Button --project=${myReactLib} --no-interactive`
|
||||||
);
|
);
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(`libs/${myReactLib}/src/lib/button.tsx`),
|
tmpProjPath(`libs/${myReactLib}/src/lib/button.tsx`),
|
||||||
`
|
`
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import './button.css';
|
import './button.css';
|
||||||
@ -95,41 +96,41 @@ describe('Storybook schematics', () => {
|
|||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(`libs/${myReactLib}/src/lib/button.stories.tsx`),
|
tmpProjPath(`libs/${myReactLib}/src/lib/button.stories.tsx`),
|
||||||
`
|
`
|
||||||
import { Story, Meta } from '@storybook/react';
|
import { Story, Meta } from '@storybook/react';
|
||||||
import { Button, ButtonProps } from './button';
|
import { Button, ButtonProps } from './button';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
component: Button,
|
component: Button,
|
||||||
title: 'Button',
|
title: 'Button',
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
const Template: Story<ButtonProps> = (args) => <Button {...args} />;
|
const Template: Story<ButtonProps> = (args) => <Button {...args} />;
|
||||||
|
|
||||||
export const Primary = Template.bind({});
|
export const Primary = Template.bind({});
|
||||||
Primary.args = {
|
Primary.args = {
|
||||||
text: 'Click me',
|
text: 'Click me',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
style: 'default',
|
style: 'default',
|
||||||
};
|
};
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:storybook-configuration ${myAngularLib} --configureCypress --generateStories --generateCypressSpecs --no-interactive`
|
`generate @nrwl/angular:storybook-configuration ${myAngularLib} --configureCypress --generateStories --generateCypressSpecs --no-interactive`
|
||||||
);
|
);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:stories ${myAngularLib} --generateCypressSpecs --no-interactive`
|
`generate @nrwl/angular:stories ${myAngularLib} --generateCypressSpecs --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(
|
tmpProjPath(
|
||||||
`apps/${myAngularLib}-e2e/src/integration/test-button/test-button.component.spec.ts`
|
`apps/${myAngularLib}-e2e/src/integration/test-button/test-button.component.spec.ts`
|
||||||
),
|
),
|
||||||
`
|
`
|
||||||
describe('${myAngularLib}', () => {
|
describe('${myAngularLib}', () => {
|
||||||
|
|
||||||
it('should render the component', () => {
|
it('should render the component', () => {
|
||||||
@ -147,21 +148,23 @@ describe('Storybook schematics', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/react:storybook-configuration ${myReactLib} --configureCypress --no-interactive`
|
`generate @nrwl/react:storybook-configuration ${myReactLib} --configureCypress --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
// The following line (mkdirSync...) is not needed,
|
// The following line (mkdirSync...) is not needed,
|
||||||
// since the above schematic creates this directory.
|
// since the above schematic creates this directory.
|
||||||
// So, if we leave it there, there's an error saying the directory exists.
|
// So, if we leave it there, there's an error saying the directory exists.
|
||||||
// I am not sure how it worked as it was :/
|
// I am not sure how it worked as it was :/
|
||||||
|
|
||||||
// mkdirSync(tmpProjPath(`apps/${myReactLib}-e2e/src/integration`));
|
// mkdirSync(tmpProjPath(`apps/${myReactLib}-e2e/src/integration`));
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(`apps/${myReactLib}-e2e/src/integration/button.spec.ts`),
|
tmpProjPath(
|
||||||
`
|
`apps/${myReactLib}-e2e/src/integration/button.spec.ts`
|
||||||
|
),
|
||||||
|
`
|
||||||
describe('react-ui', () => {
|
describe('react-ui', () => {
|
||||||
it('should render the component', () => {
|
it('should render the component', () => {
|
||||||
cy.visit(
|
cy.visit(
|
||||||
@ -178,68 +181,68 @@ describe('Storybook schematics', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
);
|
|
||||||
|
|
||||||
if (runCypressTests()) {
|
|
||||||
const e2eResults = runCLI(
|
|
||||||
`e2e ${myAngularLib}-e2e --headless --no-watch`
|
|
||||||
);
|
);
|
||||||
expect(e2eResults).toContain('All specs passed!');
|
|
||||||
expect(await killPorts()).toBeTruthy();
|
if (runCypressTests()) {
|
||||||
|
const e2eResults = runCLI(
|
||||||
|
`e2e ${myAngularLib}-e2e --headless --no-watch`
|
||||||
|
);
|
||||||
|
expect(e2eResults).toContain('All specs passed!');
|
||||||
|
expect(await killPorts()).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
runCLI(`run ${myAngularLib}:build-storybook`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/storybook/${myAngularLib}/index.html`);
|
||||||
|
expect(
|
||||||
|
readFile(`dist/storybook/${myAngularLib}/index.html`)
|
||||||
|
).toContain(`<title>Storybook</title>`);
|
||||||
}
|
}
|
||||||
|
}, 1000000);
|
||||||
|
|
||||||
runCLI(`run ${myAngularLib}:build-storybook`);
|
xit('should build an Angular based storybook', () => {
|
||||||
|
const angularStorybookLib = uniq('test-ui-lib');
|
||||||
checkFilesExist(`dist/storybook/${myAngularLib}/index.html`);
|
createTestUILib(angularStorybookLib);
|
||||||
expect(readFile(`dist/storybook/${myAngularLib}/index.html`)).toContain(
|
runCLI(
|
||||||
`<title>Storybook</title>`
|
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
xit('should build an Angular based storybook', () => {
|
// build Angular lib
|
||||||
const angularStorybookLib = uniq('test-ui-lib');
|
runCLI(`run ${angularStorybookLib}:build-storybook`);
|
||||||
createTestUILib(angularStorybookLib);
|
checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`);
|
||||||
runCLI(
|
expect(
|
||||||
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
readFile(`dist/storybook/${angularStorybookLib}/index.html`)
|
||||||
);
|
).toContain(`<title>Storybook</title>`);
|
||||||
|
}, 1000000);
|
||||||
|
|
||||||
// build Angular lib
|
xit('should build an Angular based storybook that references another lib', () => {
|
||||||
runCLI(`run ${angularStorybookLib}:build-storybook`);
|
const angularStorybookLib = uniq('test-ui-lib');
|
||||||
checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`);
|
createTestUILib(angularStorybookLib);
|
||||||
expect(
|
runCLI(
|
||||||
readFile(`dist/storybook/${angularStorybookLib}/index.html`)
|
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
||||||
).toContain(`<title>Storybook</title>`);
|
);
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
xit('should build an Angular based storybook that references another lib', () => {
|
// create another lib with a component
|
||||||
const angularStorybookLib = uniq('test-ui-lib');
|
const anotherTestLib = uniq('test-another-lib');
|
||||||
createTestUILib(angularStorybookLib);
|
runCLI(`g @nrwl/angular:library ${anotherTestLib} --no-interactive`);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nrwl/angular:storybook-configuration ${angularStorybookLib} --generateStories --no-interactive`
|
`g @nrwl/angular:component my-test-cmp --project=${anotherTestLib} --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
// create another lib with a component
|
// update index.ts and export it
|
||||||
const anotherTestLib = uniq('test-another-lib');
|
writeFileSync(
|
||||||
runCLI(`g @nrwl/angular:library ${anotherTestLib} --no-interactive`);
|
tmpProjPath(`libs/${anotherTestLib}/src/index.ts`),
|
||||||
runCLI(
|
`
|
||||||
`g @nrwl/angular:component my-test-cmp --project=${anotherTestLib} --no-interactive`
|
|
||||||
);
|
|
||||||
|
|
||||||
// update index.ts and export it
|
|
||||||
writeFileSync(
|
|
||||||
tmpProjPath(`libs/${anotherTestLib}/src/index.ts`),
|
|
||||||
`
|
|
||||||
export * from './lib/my-test-cmp/my-test-cmp.component';
|
export * from './lib/my-test-cmp/my-test-cmp.component';
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
// create a story in the first lib to reference the cmp from the 2nd lib
|
// create a story in the first lib to reference the cmp from the 2nd lib
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
tmpProjPath(
|
tmpProjPath(
|
||||||
`libs/${angularStorybookLib}/src/lib/myteststory.stories.ts`
|
`libs/${angularStorybookLib}/src/lib/myteststory.stories.ts`
|
||||||
),
|
),
|
||||||
`
|
`
|
||||||
import { moduleMetadata, Story, Meta } from '@storybook/angular';
|
import { moduleMetadata, Story, Meta } from '@storybook/angular';
|
||||||
import { MyTestCmpComponent } from '@${proj}/${anotherTestLib}';
|
import { MyTestCmpComponent } from '@${proj}/${anotherTestLib}';
|
||||||
|
|
||||||
@ -263,15 +266,16 @@ describe('Storybook schematics', () => {
|
|||||||
Primary.args = {
|
Primary.args = {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
// build Angular lib
|
// build Angular lib
|
||||||
runCLI(`run ${angularStorybookLib}:build-storybook`);
|
runCLI(`run ${angularStorybookLib}:build-storybook`);
|
||||||
checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`);
|
checkFilesExist(`dist/storybook/${angularStorybookLib}/index.html`);
|
||||||
expect(
|
expect(
|
||||||
readFile(`dist/storybook/${angularStorybookLib}/index.html`)
|
readFile(`dist/storybook/${angularStorybookLib}/index.html`)
|
||||||
).toContain(`<title>Storybook</title>`);
|
).toContain(`<title>Storybook</title>`);
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
13
e2e/angular-extensions/tsconfig.json
Normal file
13
e2e/angular-extensions/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node", "jest"]
|
||||||
|
},
|
||||||
|
"include": [],
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
e2e/angular-extensions/tsconfig.spec.json
Normal file
16
e2e/angular-extensions/tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,82 +0,0 @@
|
|||||||
process.env.SELECTED_CLI = 'angular';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getSelectedPackageManager,
|
|
||||||
newProject,
|
|
||||||
readFile,
|
|
||||||
readJson,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
uniq,
|
|
||||||
updateFile,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
import { names } from '@nrwl/devkit';
|
|
||||||
|
|
||||||
// TODO: Check why this fails on yarn and npm
|
|
||||||
xdescribe('Angular Nrwl app builder', () => {
|
|
||||||
let app;
|
|
||||||
let buildableLib;
|
|
||||||
let proj: string;
|
|
||||||
|
|
||||||
// This fails with pnpm due to incompatibilities with ngcc.
|
|
||||||
// Since this suite has a single test, we wrap everything to avoid the hooks to run and
|
|
||||||
// waste time.
|
|
||||||
if (getSelectedPackageManager() !== 'pnpm') {
|
|
||||||
beforeEach(() => {
|
|
||||||
app = uniq('app');
|
|
||||||
buildableLib = uniq('buildlib1');
|
|
||||||
|
|
||||||
proj = newProject();
|
|
||||||
|
|
||||||
runCLI(`generate @nrwl/angular:app ${app} --style=css --no-interactive`);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${buildableLib} --buildable=true --no-interactive`
|
|
||||||
);
|
|
||||||
|
|
||||||
// update the app module to include a ref to the buildable lib
|
|
||||||
updateFile(
|
|
||||||
`apps/${app}/src/app/app.module.ts`,
|
|
||||||
`
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import {${
|
|
||||||
names(buildableLib).className
|
|
||||||
}Module} from '@${proj}/${buildableLib}';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppComponent],
|
|
||||||
imports: [BrowserModule, ${names(buildableLib).className}Module],
|
|
||||||
providers: [],
|
|
||||||
bootstrap: [AppComponent],
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
// update the angular.json
|
|
||||||
const workspaceJson = readJson(`angular.json`);
|
|
||||||
workspaceJson.projects[app].architect.build.builder =
|
|
||||||
'@nrwl/angular:webpack-browser';
|
|
||||||
updateFile('angular.json', JSON.stringify(workspaceJson, null, 2));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => removeProject({ onlyOnCI: true }));
|
|
||||||
|
|
||||||
it('should build the dependent buildable lib as well as the app', () => {
|
|
||||||
const libOutput = runCLI(`build ${app} --with-deps`);
|
|
||||||
expect(libOutput).toContain(
|
|
||||||
`Building entry point '@${proj}/${buildableLib}'`
|
|
||||||
);
|
|
||||||
expect(libOutput).toContain(`nx run ${app}:build`);
|
|
||||||
|
|
||||||
// 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`);
|
|
||||||
expect(mainBundle).toContain(`dist/libs/${buildableLib}`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
it('Skip tests with pnpm', () => {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import {
|
|
||||||
checkFilesExist,
|
|
||||||
expectTestsPass,
|
|
||||||
getSelectedPackageManager,
|
|
||||||
getSize,
|
|
||||||
killPorts,
|
|
||||||
newProject,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
runCLIAsync,
|
|
||||||
tmpProjPath,
|
|
||||||
uniq,
|
|
||||||
updateFile,
|
|
||||||
runCypressTests,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
|
|
||||||
import { names } from '@nrwl/devkit';
|
|
||||||
|
|
||||||
describe('Angular Package', () => {
|
|
||||||
let proj: string;
|
|
||||||
|
|
||||||
beforeEach(() => (proj = newProject()));
|
|
||||||
afterEach(() => removeProject({ onlyOnCI: true }));
|
|
||||||
|
|
||||||
it('should work', async () => {
|
|
||||||
// TODO: npm build is failing for Angular because of webpack 4
|
|
||||||
// remove this condition once `node` is migrated to webpack 5
|
|
||||||
if (getSelectedPackageManager() !== 'npm') {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive`
|
|
||||||
);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:lib ${mylib} --directory=myDir --add-module-spec --no-interactive`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`apps/my-dir/${myapp}/src/app/app.module.ts`,
|
|
||||||
`
|
|
||||||
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';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [BrowserModule, MyDir${names(mylib).className}Module],
|
|
||||||
declarations: [AppComponent],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
runCLI(`build my-dir-${myapp} --prod --output-hashing none`);
|
|
||||||
|
|
||||||
checkFilesExist(`dist/apps/my-dir/${myapp}/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`)
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
|
|
||||||
);
|
|
||||||
expect(es2015BundleSize).toBeLessThanOrEqual(160000);
|
|
||||||
|
|
||||||
// running tests for the app
|
|
||||||
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
|
||||||
|
|
||||||
// running tests for the lib
|
|
||||||
expectTestsPass(await runCLIAsync(`test my-dir-${mylib} --no-watch`));
|
|
||||||
|
|
||||||
if (runCypressTests()) {
|
|
||||||
const e2eResults = runCLI(
|
|
||||||
`e2e my-dir-${myapp}-e2e --headless --no-watch`
|
|
||||||
);
|
|
||||||
expect(e2eResults).toContain('All specs passed!');
|
|
||||||
expect(await killPorts()).toBeTruthy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should support router config generation (lazy)', async () => {
|
|
||||||
if (getSelectedPackageManager() !== 'npm') {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --lazy --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(`build my-dir-${myapp} --aot`);
|
|
||||||
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
|
||||||
}
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should support router config generation (eager)', async () => {
|
|
||||||
// TODO: npm build is failing for Angular because of webpack 4
|
|
||||||
// remove this condition once `node` is migrated to webpack 5
|
|
||||||
if (getSelectedPackageManager() !== 'npm') {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(`build my-dir-${myapp} --aot`);
|
|
||||||
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
|
||||||
}
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should support Ivy', async () => {
|
|
||||||
// TODO: npm build is failing for Angular because of webpack 4
|
|
||||||
// remove this condition once `node` is migrated to webpack 5
|
|
||||||
if (getSelectedPackageManager() !== 'npm') {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:app ${myapp} --directory=myDir --routing --enable-ivy`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(`build my-dir-${myapp} --aot`);
|
|
||||||
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
|
||||||
}
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should support building in parallel', () => {
|
|
||||||
// TODO: npm build is failing for Angular because of webpack 4
|
|
||||||
// remove this condition once `node` is migrated to webpack 5
|
|
||||||
if (getSelectedPackageManager() !== 'npm') {
|
|
||||||
if (getSelectedPackageManager() === 'pnpm') {
|
|
||||||
// TODO: This tests fails with pnpm but we should still enable this for other package managers
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
const myapp2 = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp}`);
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp2}`);
|
|
||||||
|
|
||||||
runCLI('run-many --target build --all --parallel');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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`);
|
|
||||||
expect(runCLI(`lint ${myapp}`)).toContain('All files pass linting.');
|
|
||||||
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
runCLI(`generate @nrwl/angular:lib ${mylib} --linter=eslint`);
|
|
||||||
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`);
|
|
||||||
|
|
||||||
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(
|
|
||||||
`The selector should start with one of these prefixes`
|
|
||||||
);
|
|
||||||
expect(appLintStdOut).toContain(`7:18`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
process.env.SELECTED_CLI = 'angular';
|
|
||||||
|
|
||||||
import {
|
|
||||||
checkFilesExist,
|
|
||||||
getSelectedPackageManager,
|
|
||||||
newProject,
|
|
||||||
readJson,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
uniq,
|
|
||||||
updateFile,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
import { names } from '@nrwl/devkit';
|
|
||||||
|
|
||||||
['publishable', 'buildable'].forEach((testConfig) => {
|
|
||||||
describe('Build Angular library', () => {
|
|
||||||
/**
|
|
||||||
* Graph:
|
|
||||||
*
|
|
||||||
* childLib
|
|
||||||
* /
|
|
||||||
* parentLib =>
|
|
||||||
* \
|
|
||||||
* \
|
|
||||||
* childLib2
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
let parentLib: string;
|
|
||||||
let childLib: string;
|
|
||||||
let childLib2: string;
|
|
||||||
let proj: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
parentLib = uniq('parentlib');
|
|
||||||
childLib = uniq('childlib');
|
|
||||||
childLib2 = uniq('childlib2');
|
|
||||||
|
|
||||||
proj = newProject();
|
|
||||||
|
|
||||||
if (testConfig === 'buildable') {
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${parentLib} --buildable=true --no-interactive`
|
|
||||||
);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${childLib} --buildable=true --no-interactive`
|
|
||||||
);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${childLib2} --buildable=true --no-interactive`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${parentLib} --publishable=true --importPath=@${proj}/${parentLib} --no-interactive`
|
|
||||||
);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${childLib} --publishable=true --importPath=@${proj}/${childLib} --no-interactive`
|
|
||||||
);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:library ${childLib2} --publishable=true --importPath=@${proj}/${childLib2} --no-interactive`
|
|
||||||
);
|
|
||||||
|
|
||||||
// create secondary entrypoint
|
|
||||||
updateFile(
|
|
||||||
`libs/${childLib}/sub/package.json`,
|
|
||||||
`
|
|
||||||
{
|
|
||||||
"ngPackage": {}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
updateFile(
|
|
||||||
`libs/${childLib}/sub/src/lib/sub.module.ts`,
|
|
||||||
`
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
@NgModule({ imports: [CommonModule] })
|
|
||||||
export class SubModule {}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`libs/${childLib}/sub/src/public_api.ts`,
|
|
||||||
`export * from './lib/sub.module';`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`libs/${childLib}/sub/src/index.ts`,
|
|
||||||
`export * from './public_api';`
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(`tsconfig.base.json`, (s) => {
|
|
||||||
return s.replace(
|
|
||||||
`"@${proj}/${childLib}": ["libs/${childLib}/src/index.ts"],`,
|
|
||||||
`"@${proj}/${childLib}": ["libs/${childLib}/src/index.ts"],
|
|
||||||
"@${proj}/${childLib}/sub": ["libs/${childLib}/sub/src/index.ts"],
|
|
||||||
`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// create dependencies by importing
|
|
||||||
const createDep = (parent, children: string[]) => {
|
|
||||||
let moduleContent = `
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
${children
|
|
||||||
.map(
|
|
||||||
(entry) =>
|
|
||||||
`import { ${
|
|
||||||
names(entry).className
|
|
||||||
}Module } from '@${proj}/${entry}';`
|
|
||||||
)
|
|
||||||
.join('\n')}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (testConfig === 'publishable') {
|
|
||||||
moduleContent += `
|
|
||||||
import { SubModule } from '@${proj}/${childLib}/sub';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule, ${children
|
|
||||||
.map((entry) => `${names(entry).className}Module`)
|
|
||||||
.join(',')}, SubModule]
|
|
||||||
})
|
|
||||||
export class ${names(parent).className}Module {}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFile(`libs/${parent}/src/lib/${parent}.module.ts`, moduleContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
createDep(parentLib, [childLib, childLib2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => removeProject({ onlyOnCI: true }));
|
|
||||||
|
|
||||||
it('empty test to make jest happy', () => {});
|
|
||||||
|
|
||||||
// These fail with pnpm due to incompatibilities with ngcc for buildable libraries.
|
|
||||||
if (
|
|
||||||
getSelectedPackageManager() !== 'pnpm' ||
|
|
||||||
testConfig === 'publishable'
|
|
||||||
) {
|
|
||||||
it('should build the library when it does not have any deps', () => {
|
|
||||||
runCLI(`build ${childLib}`);
|
|
||||||
|
|
||||||
checkFilesExist(`dist/libs/${childLib}/package.json`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly add references to any dependency into the parent package.json', () => {
|
|
||||||
runCLI(`build ${childLib}`);
|
|
||||||
runCLI(`build ${childLib2}`);
|
|
||||||
runCLI(`build ${parentLib}`);
|
|
||||||
|
|
||||||
checkFilesExist(
|
|
||||||
`dist/libs/${childLib}/package.json`,
|
|
||||||
`dist/libs/${childLib2}/package.json`,
|
|
||||||
`dist/libs/${parentLib}/package.json`
|
|
||||||
);
|
|
||||||
|
|
||||||
const jsonFile = readJson(`dist/libs/${parentLib}/package.json`);
|
|
||||||
|
|
||||||
expect(jsonFile.dependencies['tslib']).toMatch(/\^2\.\d+\.\d+/); // match any ^2.x.x
|
|
||||||
expect(jsonFile.peerDependencies[`@${proj}/${childLib}`]).toBeDefined();
|
|
||||||
expect(
|
|
||||||
jsonFile.peerDependencies[`@${proj}/${childLib2}`]
|
|
||||||
).toBeDefined();
|
|
||||||
expect(jsonFile.peerDependencies['@angular/common']).toBeDefined();
|
|
||||||
expect(jsonFile.peerDependencies['@angular/core']).toBeDefined();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
process.env.SELECTED_CLI = 'angular';
|
|
||||||
|
|
||||||
import {
|
|
||||||
expectTestsPass,
|
|
||||||
newProject,
|
|
||||||
readJson,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
runCLIAsync,
|
|
||||||
uniq,
|
|
||||||
updateFile,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
|
|
||||||
describe('new config format', () => {
|
|
||||||
beforeEach(() => newProject());
|
|
||||||
afterEach(() => removeProject({ onlyOnCI: true }));
|
|
||||||
|
|
||||||
it('should work', async () => {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
|
||||||
|
|
||||||
// update the angular.json
|
|
||||||
const workspaceJson = readJson(`angular.json`);
|
|
||||||
workspaceJson.version = 2;
|
|
||||||
workspaceJson.projects[myapp].targets = updateConfig(
|
|
||||||
workspaceJson.projects[myapp].architect
|
|
||||||
);
|
|
||||||
workspaceJson.generators = workspaceJson.schematics;
|
|
||||||
delete workspaceJson.schematics;
|
|
||||||
updateFile('angular.json', JSON.stringify(workspaceJson, null, 2));
|
|
||||||
|
|
||||||
const myapp2 = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp2} --no-interactive`);
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${myapp2} --no-watch`));
|
|
||||||
}, 1000000);
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateConfig(targets: any) {
|
|
||||||
const res = {};
|
|
||||||
Object.entries(targets).forEach(([name, t]: any) => {
|
|
||||||
t.executor = t.builder;
|
|
||||||
delete t.builder;
|
|
||||||
res[name] = t;
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
@ -1,359 +0,0 @@
|
|||||||
process.env.SELECTED_CLI = 'angular';
|
|
||||||
|
|
||||||
import {
|
|
||||||
checkFilesExist,
|
|
||||||
readJson,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
runCommand,
|
|
||||||
runNgAdd,
|
|
||||||
runNgNew,
|
|
||||||
uniq,
|
|
||||||
updateFile,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
|
|
||||||
// TODO: Check why generated angular app is different
|
|
||||||
xdescribe('Nrwl Convert to Nx Workspace', () => {
|
|
||||||
let proj;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeProject({ onlyOnCI: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate a workspace', () => {
|
|
||||||
proj = uniq('proj');
|
|
||||||
runNgNew(proj);
|
|
||||||
|
|
||||||
// update package.json
|
|
||||||
const packageJson = readJson('package.json');
|
|
||||||
packageJson.description = 'some description';
|
|
||||||
updateFile('package.json', JSON.stringify(packageJson, null, 2));
|
|
||||||
// confirm that @nrwl and @ngrx dependencies do not exist yet
|
|
||||||
expect(packageJson.devDependencies['@nrwl/workspace']).not.toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/store']).not.toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/effects']).not.toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/router-store']).not.toBeDefined();
|
|
||||||
expect(
|
|
||||||
packageJson.devDependencies['@ngrx/store-devtools']
|
|
||||||
).not.toBeDefined();
|
|
||||||
|
|
||||||
// update tsconfig.json
|
|
||||||
const tsconfigJson = readJson('tsconfig.json');
|
|
||||||
tsconfigJson.compilerOptions.paths = { a: ['b'] };
|
|
||||||
updateFile('tsconfig.json', JSON.stringify(tsconfigJson, null, 2));
|
|
||||||
|
|
||||||
updateFile('src/scripts.ts', '');
|
|
||||||
|
|
||||||
// update angular-cli.json
|
|
||||||
const angularCLIJson = readJson('angular.json');
|
|
||||||
angularCLIJson.projects[proj].architect.build.options.scripts =
|
|
||||||
angularCLIJson.projects[proj].architect.test.options.scripts = [
|
|
||||||
'src/scripts.ts',
|
|
||||||
];
|
|
||||||
angularCLIJson.projects[proj].architect.test.options.styles = [
|
|
||||||
'src/styles.css',
|
|
||||||
];
|
|
||||||
updateFile('angular.json', JSON.stringify(angularCLIJson, null, 2));
|
|
||||||
|
|
||||||
// run the command
|
|
||||||
runNgAdd('--npm-scope projscope');
|
|
||||||
|
|
||||||
// check that prettier config exits and that files have been moved!
|
|
||||||
checkFilesExist(
|
|
||||||
'.vscode/extensions.json',
|
|
||||||
'.prettierrc',
|
|
||||||
`apps/${proj}/src/main.ts`,
|
|
||||||
`apps/${proj}/src/app/app.module.ts`
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(readJson('.vscode/extensions.json').recommendations).toEqual([
|
|
||||||
'nrwl.angular-console',
|
|
||||||
'angular.ng-template',
|
|
||||||
'dbaeumer.vscode-eslint',
|
|
||||||
'esbenp.prettier-vscode',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// check that package.json got merged
|
|
||||||
const updatedPackageJson = readJson('package.json');
|
|
||||||
expect(updatedPackageJson.description).toEqual('some description');
|
|
||||||
expect(updatedPackageJson.scripts).toEqual({
|
|
||||||
ng: 'nx',
|
|
||||||
nx: 'nx',
|
|
||||||
start: 'ng serve',
|
|
||||||
build: 'ng build',
|
|
||||||
test: 'ng test',
|
|
||||||
lint: 'nx workspace-lint && ng lint',
|
|
||||||
e2e: 'ng e2e',
|
|
||||||
'affected:apps': 'nx affected:apps',
|
|
||||||
'affected:libs': 'nx affected:libs',
|
|
||||||
'affected:build': 'nx affected:build',
|
|
||||||
'affected:e2e': 'nx affected:e2e',
|
|
||||||
'affected:test': 'nx affected:test',
|
|
||||||
'affected:lint': 'nx affected:lint',
|
|
||||||
'affected:dep-graph': 'nx affected:dep-graph',
|
|
||||||
affected: 'nx affected',
|
|
||||||
format: 'nx format:write',
|
|
||||||
'format:write': 'nx format:write',
|
|
||||||
'format:check': 'nx format:check',
|
|
||||||
update: 'ng update @nrwl/workspace',
|
|
||||||
'update:check': 'ng update',
|
|
||||||
postinstall: 'node ./decorate-angular-cli.js',
|
|
||||||
'dep-graph': 'nx dep-graph',
|
|
||||||
'workspace-generator': 'nx workspace-generator',
|
|
||||||
help: 'nx help',
|
|
||||||
});
|
|
||||||
expect(updatedPackageJson.devDependencies['@nrwl/workspace']).toBeDefined();
|
|
||||||
expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined();
|
|
||||||
|
|
||||||
const nxJson = readJson('nx.json');
|
|
||||||
expect(nxJson).toEqual({
|
|
||||||
npmScope: 'projscope',
|
|
||||||
implicitDependencies: {
|
|
||||||
'angular.json': '*',
|
|
||||||
'package.json': '*',
|
|
||||||
'tslint.json': '*',
|
|
||||||
'.eslintrc.json': '*',
|
|
||||||
'tsconfig.base.json': '*',
|
|
||||||
'nx.json': '*',
|
|
||||||
},
|
|
||||||
projects: {
|
|
||||||
[`${proj}`]: {
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
[`${proj}-e2e`]: {
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// check if angular-cli.json get merged
|
|
||||||
const updatedAngularCLIJson = readJson('angular.json');
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].root).toEqual(`apps/${proj}`);
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].sourceRoot).toEqual(
|
|
||||||
`apps/${proj}/src`
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].architect.build).toEqual({
|
|
||||||
builder: '@angular-devkit/build-angular:browser',
|
|
||||||
options: {
|
|
||||||
aot: true,
|
|
||||||
outputPath: `dist/apps/${proj}`,
|
|
||||||
index: `apps/${proj}/src/index.html`,
|
|
||||||
main: `apps/${proj}/src/main.ts`,
|
|
||||||
polyfills: `apps/${proj}/src/polyfills.ts`,
|
|
||||||
tsConfig: `apps/${proj}/tsconfig.app.json`,
|
|
||||||
assets: [`apps/${proj}/src/favicon.ico`, `apps/${proj}/src/assets`],
|
|
||||||
styles: [`apps/${proj}/src/styles.css`],
|
|
||||||
scripts: [`apps/${proj}/src/scripts.ts`],
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
fileReplacements: [
|
|
||||||
{
|
|
||||||
replace: `apps/${proj}/src/environments/environment.ts`,
|
|
||||||
with: `apps/${proj}/src/environments/environment.prod.ts`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
budgets: [
|
|
||||||
{
|
|
||||||
maximumError: '5mb',
|
|
||||||
maximumWarning: '2mb',
|
|
||||||
type: 'initial',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maximumError: '10kb',
|
|
||||||
maximumWarning: '6kb',
|
|
||||||
type: 'anyComponentStyle',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
optimization: true,
|
|
||||||
outputHashing: 'all',
|
|
||||||
sourceMap: false,
|
|
||||||
extractCss: true,
|
|
||||||
namedChunks: false,
|
|
||||||
extractLicenses: true,
|
|
||||||
vendorChunk: false,
|
|
||||||
buildOptimizer: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].architect.serve).toEqual({
|
|
||||||
builder: '@angular-devkit/build-angular:dev-server',
|
|
||||||
options: {
|
|
||||||
browserTarget: 'proj:build',
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
browserTarget: 'proj:build:production',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].architect.test).toEqual({
|
|
||||||
builder: '@angular-devkit/build-angular:karma',
|
|
||||||
options: {
|
|
||||||
main: `apps/${proj}/src/test.ts`,
|
|
||||||
polyfills: `apps/${proj}/src/polyfills.ts`,
|
|
||||||
tsConfig: `apps/${proj}/tsconfig.spec.json`,
|
|
||||||
karmaConfig: `apps/${proj}/karma.conf.js`,
|
|
||||||
styles: [`apps/${proj}/src/styles.css`],
|
|
||||||
scripts: [`apps/${proj}/src/scripts.ts`],
|
|
||||||
assets: [`apps/${proj}/src/favicon.ico`, `apps/${proj}/src/assets`],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].architect.lint).toEqual({
|
|
||||||
builder: '@angular-devkit/build-angular:tslint',
|
|
||||||
options: {
|
|
||||||
tsConfig: [
|
|
||||||
`apps/${proj}/tsconfig.app.json`,
|
|
||||||
`apps/${proj}/tsconfig.spec.json`,
|
|
||||||
],
|
|
||||||
exclude: ['**/node_modules/**'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updatedAngularCLIJson.projects[`${proj}-e2e`].root).toEqual(
|
|
||||||
`apps/${proj}-e2e`
|
|
||||||
);
|
|
||||||
expect(updatedAngularCLIJson.projects[`${proj}-e2e`].architect.e2e).toEqual(
|
|
||||||
{
|
|
||||||
builder: '@angular-devkit/build-angular:protractor',
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
devServerTarget: `${proj}:serve:production`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
protractorConfig: `apps/${proj}-e2e/protractor.conf.js`,
|
|
||||||
devServerTarget: `${proj}:serve`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
updatedAngularCLIJson.projects[`${proj}-e2e`].architect.lint
|
|
||||||
).toEqual({
|
|
||||||
builder: '@angular-devkit/build-angular:tslint',
|
|
||||||
options: {
|
|
||||||
tsConfig: `apps/${proj}-e2e/tsconfig.json`,
|
|
||||||
exclude: ['**/node_modules/**'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedTslint = readJson('tslint.json');
|
|
||||||
expect(updatedTslint.rules['nx-enforce-module-boundaries']).toEqual([
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
allow: [],
|
|
||||||
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
runCLI('build --prod --outputHashing none');
|
|
||||||
checkFilesExist(`dist/apps/${proj}/main-es2015.js`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate a workspace and not change dependencies, devDependencies, or vscode extensions if they already exist', () => {
|
|
||||||
// create a new AngularCLI app
|
|
||||||
proj = uniq('proj');
|
|
||||||
runNgNew(proj);
|
|
||||||
const schematicsVersion = '12.0.0';
|
|
||||||
const ngrxVersion = '12.0.0';
|
|
||||||
// update package.json
|
|
||||||
const existingPackageJson = readJson('package.json');
|
|
||||||
existingPackageJson.devDependencies['@nrwl/workspace'] = schematicsVersion;
|
|
||||||
existingPackageJson.dependencies['@ngrx/store'] = ngrxVersion;
|
|
||||||
existingPackageJson.dependencies['@ngrx/effects'] = ngrxVersion;
|
|
||||||
existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion;
|
|
||||||
existingPackageJson.devDependencies['@ngrx/store-devtools'] = ngrxVersion;
|
|
||||||
updateFile('package.json', JSON.stringify(existingPackageJson, null, 2));
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
'.vscode/extensions.json',
|
|
||||||
JSON.stringify({
|
|
||||||
recommendations: ['eamodio.gitlens', 'angular.ng-template'],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// run the command
|
|
||||||
runNgAdd('--npm-scope projscope --skip-install');
|
|
||||||
|
|
||||||
// check that dependencies and devDependencies remained the same
|
|
||||||
const packageJson = readJson('package.json');
|
|
||||||
expect(packageJson.devDependencies['@nrwl/workspace']).toEqual(
|
|
||||||
schematicsVersion
|
|
||||||
);
|
|
||||||
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
|
|
||||||
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
|
|
||||||
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(ngrxVersion);
|
|
||||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
|
|
||||||
ngrxVersion
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(readJson('.vscode/extensions.json').recommendations).toEqual([
|
|
||||||
'eamodio.gitlens',
|
|
||||||
'angular.ng-template',
|
|
||||||
'nrwl.angular-console',
|
|
||||||
'dbaeumer.vscode-eslint',
|
|
||||||
'esbenp.prettier-vscode',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle different types of errors', () => {
|
|
||||||
// create a new AngularCLI app
|
|
||||||
proj = uniq('proj');
|
|
||||||
runNgNew(proj);
|
|
||||||
|
|
||||||
// Only remove e2e directory
|
|
||||||
runCommand('mv e2e e2e-bak');
|
|
||||||
try {
|
|
||||||
runNgAdd('--npm-scope projscope --skip-install');
|
|
||||||
fail('Did not handle not having a e2e directory');
|
|
||||||
} catch (e) {
|
|
||||||
expect(e.stderr.toString()).toContain(
|
|
||||||
'Your workspace could not be converted into an Nx Workspace because of the above error.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put e2e back
|
|
||||||
runCommand('mv e2e-bak e2e');
|
|
||||||
|
|
||||||
// Remove package.json
|
|
||||||
runCommand('mv package.json package.json.bak');
|
|
||||||
try {
|
|
||||||
runNgAdd('--npm-scope projscope --skip-install');
|
|
||||||
fail('Did not handle not having a package.json');
|
|
||||||
} catch (e) {
|
|
||||||
expect(e.stderr.toString()).toContain(
|
|
||||||
'Your workspace could not be converted into an Nx Workspace because of the above error.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put package.json back
|
|
||||||
runCommand('mv package.json.bak package.json');
|
|
||||||
|
|
||||||
// Remove src
|
|
||||||
runCommand('mv src src-bak');
|
|
||||||
try {
|
|
||||||
runNgAdd('--npm-scope projscope --skip-install');
|
|
||||||
fail('Did not handle not having a src directory');
|
|
||||||
} catch (e) {
|
|
||||||
expect(e.stderr.toString()).toContain('Path: src does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put src back
|
|
||||||
runCommand('mv src-bak src');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support preserveAngularCLILayout', () => {
|
|
||||||
proj = uniq('proj');
|
|
||||||
runNgNew(proj);
|
|
||||||
runNgAdd('--preserveAngularCLILayout');
|
|
||||||
|
|
||||||
const updatedAngularCLIJson = readJson('angular.json');
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].root).toEqual('');
|
|
||||||
expect(updatedAngularCLIJson.projects[proj].sourceRoot).toEqual('src');
|
|
||||||
|
|
||||||
const output = runCLI('build');
|
|
||||||
expect(output).toContain(`> ng run ${proj}:build`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import {
|
|
||||||
expectTestsPass,
|
|
||||||
newProject,
|
|
||||||
readJson,
|
|
||||||
removeProject,
|
|
||||||
runCLI,
|
|
||||||
runCLIAsync,
|
|
||||||
uniq,
|
|
||||||
} from '@nrwl/e2e/utils';
|
|
||||||
|
|
||||||
// TODO: Uncomment when the node is migrated to webpack 5
|
|
||||||
xdescribe('ngrx', () => {
|
|
||||||
beforeEach(() => newProject());
|
|
||||||
afterAll(() => removeProject({ onlyOnCI: true }));
|
|
||||||
|
|
||||||
it('should work', async () => {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
|
||||||
|
|
||||||
// Generate root ngrx state management
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root --minimal=false --syntax=classes --useDataPersistence=true`
|
|
||||||
);
|
|
||||||
const packageJson = readJson('package.json');
|
|
||||||
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
|
|
||||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
|
|
||||||
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
// Generate feature library and ngrx state within that library
|
|
||||||
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade --syntax=classes`
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should work with creators', async () => {
|
|
||||||
const myapp = uniq('myapp');
|
|
||||||
runCLI(`generate @nrwl/angular:app ${myapp} --routing --no-interactive`);
|
|
||||||
|
|
||||||
// Generate root ngrx state management
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root`
|
|
||||||
);
|
|
||||||
const packageJson = readJson('package.json');
|
|
||||||
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
|
|
||||||
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
|
|
||||||
expect(packageJson.devDependencies['@ngrx/schematics']).toBeDefined();
|
|
||||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
|
|
||||||
|
|
||||||
const mylib = uniq('mylib');
|
|
||||||
// Generate feature library and ngrx state within that library
|
|
||||||
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
|
|
||||||
|
|
||||||
const flags = `--facade --barrels`;
|
|
||||||
runCLI(
|
|
||||||
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts ${flags}`
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
|
|
||||||
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
|
|
||||||
}, 1000000);
|
|
||||||
});
|
|
||||||
@ -20,7 +20,8 @@
|
|||||||
"dep-graph-dep-graph-e2e": "dep-graph/dep-graph-e2e",
|
"dep-graph-dep-graph-e2e": "dep-graph/dep-graph-e2e",
|
||||||
"devkit": "packages/devkit",
|
"devkit": "packages/devkit",
|
||||||
"docs": "docs",
|
"docs": "docs",
|
||||||
"e2e-angular": "e2e/angular",
|
"e2e-angular-core": "e2e/angular-core",
|
||||||
|
"e2e-angular-extensions": "e2e/angular-extensions",
|
||||||
"e2e-cli": "e2e/cli",
|
"e2e-cli": "e2e/cli",
|
||||||
"e2e-cypress": "e2e/cypress",
|
"e2e-cypress": "e2e/cypress",
|
||||||
"e2e-gatsby": "e2e/gatsby",
|
"e2e-gatsby": "e2e/gatsby",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user