Dan Smith f095647013 fix(angular): allow creating an app named 'app' or lib named 'lib'
Currently when trying to create an app named 'app' or a lib named 'lib', the schematics inject the
'projectType' as 'apps/application' or lib as 'libs/library', breaking the Angular build.

fix #1844
2019-11-11 13:17:40 -05:00

544 lines
18 KiB
TypeScript

import { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace, getFileContent } from '@nrwl/workspace/testing';
import * as stripJsonComments from 'strip-json-comments';
import { readJsonInTree, updateJsonInTree, NxJson } from '@nrwl/workspace';
import { runSchematic, callRule } from '../../utils/testing';
describe('app', () => {
let appTree: Tree;
beforeEach(() => {
appTree = Tree.empty();
appTree = createEmptyWorkspace(appTree);
});
describe('not nested', () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e'
);
expect(
workspaceJson.projects['my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app/**']);
expect(
workspaceJson.projects['my-app-e2e'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app-e2e/**']);
});
it('should remove the e2e target on the application', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-app'].architect.e2e).not.toBeDefined();
});
it('should update nx.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', tags: 'one,two' },
appTree
);
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
projects: {
'my-app': {
tags: ['one', 'two']
},
'my-app-e2e': {
tags: []
}
}
});
});
it('should generate files', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
expect(tree.exists(`apps/my-app/jest.config.js`)).toBeTruthy();
expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.module.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.component.ts')).toBeTruthy();
expect(
getFileContent(tree, 'apps/my-app/src/app/app.module.ts')
).toContain('class AppModule');
const tsconfig = readJsonInTree(tree, 'apps/my-app/tsconfig.json');
expect(tsconfig.extends).toEqual('../../tsconfig.json');
expect(tsconfig.compilerOptions.types).toContain('jest');
const tsconfigApp = JSON.parse(
stripJsonComments(getFileContent(tree, 'apps/my-app/tsconfig.app.json'))
);
expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc');
expect(tsconfigApp.extends).toEqual('./tsconfig.json');
const tslintJson = JSON.parse(
stripJsonComments(getFileContent(tree, 'apps/my-app/tslint.json'))
);
expect(tslintJson.extends).toEqual('../../tslint.json');
expect(tree.exists('apps/my-app-e2e/cypress.json')).toBeTruthy();
const tsconfigE2E = JSON.parse(
stripJsonComments(
getFileContent(tree, 'apps/my-app-e2e/tsconfig.e2e.json')
)
);
expect(tsconfigE2E.extends).toEqual('./tsconfig.json');
});
it('should default the prefix to npmScope', async () => {
const noPrefix = await runSchematic(
'app',
{ name: 'myApp', e2eTestRunner: 'protractor' },
appTree
);
const withPrefix = await runSchematic(
'app',
{ name: 'myApp', prefix: 'custom', e2eTestRunner: 'protractor' },
appTree
);
// Testing without prefix
let appE2eSpec = noPrefix
.read('apps/my-app-e2e/src/app.e2e-spec.ts')
.toString();
let workspaceJson = JSON.parse(
noPrefix.read('workspace.json').toString()
);
let myAppPrefix = workspaceJson.projects['my-app'].prefix;
expect(myAppPrefix).toEqual('proj');
expect(appE2eSpec).toContain('Welcome to my-app!');
// Testing WITH prefix
appE2eSpec = withPrefix
.read('apps/my-app-e2e/src/app.e2e-spec.ts')
.toString();
workspaceJson = JSON.parse(withPrefix.read('workspace.json').toString());
myAppPrefix = workspaceJson.projects['my-app'].prefix;
expect(myAppPrefix).toEqual('custom');
expect(appE2eSpec).toContain('Welcome to my-app!');
});
xit('should work if the new project root is changed', async () => {
appTree = await callRule(
updateJsonInTree('/workspace.json', json => ({
...json,
newProjectRoot: 'newProjectRoot'
})),
appTree
);
const result = await runSchematic('app', { name: 'myApp' }, appTree);
expect(result.exists('apps/my-app/src/main.ts')).toEqual(true);
expect(result.exists('apps/my-app-e2e/protractor.conf.js')).toEqual(true);
});
it('should set projectType to application', async () => {
const tree = await runSchematic('app', { name: 'app' }, appTree);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['app'].projectType).toEqual('application');
});
});
describe('nested', () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app'
);
expect(workspaceJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app-e2e'
);
expect(
workspaceJson.projects['my-dir-my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app/**']);
expect(
workspaceJson.projects['my-dir-my-app-e2e'].architect.lint.options
.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app-e2e/**']);
});
it('should update nx.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', tags: 'one,two' },
appTree
);
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
projects: {
'my-dir-my-app': {
tags: ['one', 'two']
},
'my-dir-my-app-e2e': {
tags: []
}
}
});
});
it('should generate files', async () => {
const hasJsonValue = ({ path, expectedValue, lookupFn }) => {
const content = getFileContent(tree, path);
const config = JSON.parse(stripJsonComments(content));
expect(lookupFn(config)).toEqual(expectedValue);
};
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
const appModulePath = 'apps/my-dir/my-app/src/app/app.module.ts';
expect(getFileContent(tree, appModulePath)).toContain('class AppModule');
// Make sure these exist
[
`apps/my-dir/my-app/jest.config.js`,
'apps/my-dir/my-app/src/main.ts',
'apps/my-dir/my-app/src/app/app.module.ts',
'apps/my-dir/my-app/src/app/app.component.ts',
'apps/my-dir/my-app-e2e/cypress.json'
].forEach(path => {
expect(tree.exists(path)).toBeTruthy();
});
// Make sure these have properties
[
{
path: 'apps/my-dir/my-app/tsconfig.json',
lookupFn: json => json.extends,
expectedValue: '../../../tsconfig.json'
},
{
path: 'apps/my-dir/my-app/tsconfig.app.json',
lookupFn: json => json.compilerOptions.outDir,
expectedValue: '../../../dist/out-tsc'
},
{
path: 'apps/my-dir/my-app-e2e/tsconfig.json',
lookupFn: json => json.extends,
expectedValue: '../../../tsconfig.json'
},
// {
// path: 'apps/my-dir/my-app-e2e/tsconfig.e2e.json',
// lookupFn: json => json.compilerOptions.outDir,
// expectedValue: '../../../dist/out-tsc/apps/my-dir/my-app-e2e'
// },
{
path: 'apps/my-dir/my-app/tslint.json',
lookupFn: json => json.extends,
expectedValue: '../../../tslint.json'
}
].forEach(hasJsonValue);
});
});
describe('routing', () => {
it('should include RouterTestingModule', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', routing: true },
appTree
);
expect(
getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')
).toContain('RouterModule.forRoot');
expect(
getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.spec.ts')
).toContain('imports: [RouterTestingModule]');
});
it('should not modify tests when --skip-tests is set', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', routing: true, skipTests: true },
appTree
);
expect(
tree.exists('apps/my-dir/my-app/src/app/app.component.spec.ts')
).toBeFalsy();
});
});
describe('template generation mode', () => {
it('should create Nx specific `app.component.html` template', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
expect(
getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.html')
).toBeTruthy();
expect(
getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.html')
).toContain('Thank you for using and showing some ♥ for Nx.');
});
it("should update `template`'s property of AppComponent with Nx content", async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', inlineTemplate: true },
appTree
);
expect(
getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.ts')
).toContain('Thank you for using and showing some ♥ for Nx.');
});
it('should update the AppComponent spec to target Nx content', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', inlineTemplate: true },
appTree
);
const testFileContent = getFileContent(
tree,
'apps/my-dir/my-app/src/app/app.component.spec.ts'
);
expect(testFileContent).toContain(`querySelector('h1')`);
expect(testFileContent).toContain('Welcome to my-dir-my-app!');
});
});
describe('--style scss', () => {
it('should generate scss styles', async () => {
const result = await runSchematic(
'app',
{ name: 'myApp', style: 'scss' },
appTree
);
expect(result.exists('apps/my-app/src/app/app.component.scss')).toEqual(
true
);
});
it('should set it as default', async () => {
const result = await runSchematic(
'app',
{ name: 'myApp', style: 'scss' },
appTree
);
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.projects['my-app'].schematics).toEqual({
'@nrwl/angular:component': {
style: 'scss'
}
});
});
});
describe('--unit-test-runner karma', () => {
it('should generate a karma config', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', unitTestRunner: 'karma' },
appTree
);
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeTruthy();
expect(tree.exists('apps/my-app/karma.conf.js')).toBeTruthy();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test.builder).toEqual(
'@angular-devkit/build-angular:karma'
);
expect(
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual([
'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.spec.json'
]);
const tsconfigAppJson = readJsonInTree(
tree,
'apps/my-app/tsconfig.app.json'
);
expect(tsconfigAppJson.exclude).toEqual(['src/test.ts', '**/*.spec.ts']);
expect(tsconfigAppJson.compilerOptions.outDir).toEqual(
'../../dist/out-tsc'
);
});
});
describe('--unit-test-runner none', () => {
it('should not generate test configuration', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', unitTestRunner: 'none' },
appTree
);
expect(tree.exists('apps/my-app/src/test-setup.ts')).toBeFalsy();
expect(tree.exists('apps/my-app/src/test.ts')).toBeFalsy();
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-app/jest.config.js')).toBeFalsy();
expect(tree.exists('apps/my-app/karma.config.js')).toBeFalsy();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect(
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']);
});
});
describe('--e2e-test-runner', () => {
describe('protractor', () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', e2eTestRunner: 'protractor' },
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(
workspaceJson.projects['my-app'].architect.e2e
).not.toBeDefined();
expect(workspaceJson.projects['my-app-e2e']).toEqual({
root: 'apps/my-app-e2e',
projectType: 'application',
architect: {
e2e: {
builder: '@angular-devkit/build-angular:protractor',
options: {
devServerTarget: 'my-app:serve',
protractorConfig: 'apps/my-app-e2e/protractor.conf.js'
},
configurations: {
production: {
devServerTarget: 'my-app:serve:production'
}
}
},
lint: {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: 'apps/my-app-e2e/tsconfig.e2e.json',
exclude: ['**/node_modules/**', '!apps/my-app-e2e/**']
}
}
}
});
});
});
describe('none', () => {
it('should not generate test configuration', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', e2eTestRunner: 'none' },
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app-e2e']).toBeUndefined();
});
});
});
describe('replaceAppNameWithPath', () => {
it('should protect `workspace.json` commands and properties', async () => {
const tree = await runSchematic('app', { name: 'ui' }, appTree);
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['ui']).toBeDefined();
expect(
workspaceJson.projects['ui']['architect']['build']['builder']
).toEqual('@angular-devkit/build-angular:browser');
});
it('should protect `workspace.json` sensible properties value to be renamed', async () => {
const tree = await runSchematic(
'app',
{ name: 'ui', prefix: 'ui' },
appTree
);
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['ui'].prefix).toEqual('ui');
});
});
describe('--backend-project', () => {
describe('with a backend project', () => {
it('should add a proxy.conf.json to app', async () => {
const tree = await runSchematic(
'app',
{ name: 'customer-ui', backendProject: 'customer-api' },
appTree
);
const proxyConfContent = JSON.stringify(
{
'/customer-api': {
target: 'http://localhost:3333',
secure: false
}
},
null,
2
);
expect(tree.exists('apps/customer-ui/proxy.conf.json')).toBeTruthy();
expect(tree.readContent('apps/customer-ui/proxy.conf.json')).toContain(
proxyConfContent
);
});
});
describe('with no backend project', () => {
it('should not generate a proxy.conf.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'customer-ui' },
appTree
);
expect(tree.exists('apps/customer-ui/proxy.conf.json')).toBeFalsy();
});
});
});
describe('--enable-ivy', () => {
it('should not exclude files in the tsconfig.app.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'my-app', enableIvy: true },
appTree
);
expect(tree.readContent('apps/my-app/tsconfig.app.json')).not.toContain(
'exclude'
);
});
it('should only include dts files in the tsconfig.app.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'my-app', enableIvy: true },
appTree
);
expect(tree.readContent('apps/my-app/tsconfig.app.json')).toContain(
`\"include\": [\"src/**/*.d.ts\"]`
);
});
});
});