Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(schematics): add an option to nest e2e projects inside of newly generated apps #770

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions packages/schematics/src/collection/application/application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,125 @@ describe('app', () => {
});
});

describe('not nested, with nested e2e', () => {
it('should update angular.json', () => {
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', nestE2e: true },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');

expect(angularJson.projects['my-app'].root).toEqual('apps/my-app/');
expect(angularJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app/e2e/'
);
});

it('should update nx.json', () => {
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', tags: 'one,two' },
appTree
);
const nxJson = readJsonInTree(tree, '/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
projects: {
'my-app': {
tags: ['one', 'two']
},
'my-app-e2e': {
tags: []
}
}
});
});

it('should generate files', () => {
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', nestE2e: true },
appTree
);
expect(tree.exists(`apps/my-app/karma.conf.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 tsconfigApp = JSON.parse(
stripJsonComments(getFileContent(tree, 'apps/my-app/tsconfig.app.json'))
);
expect(tsconfigApp.compilerOptions.outDir).toEqual(
'../../dist/out-tsc/apps/my-app'
);
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/src/app.po.ts')).toBeTruthy();

// check e2e spec file patching was successful
const e2eSpec = getFileContent(
tree,
'apps/my-app/e2e/src/app.e2e-spec.ts'
);
expect(e2eSpec.indexOf('<any>')).toBeGreaterThan(-1);
expect(e2eSpec.indexOf('jasmine')).toBeGreaterThan(-1);

const tsconfigE2E = JSON.parse(
stripJsonComments(
getFileContent(tree, 'apps/my-app/e2e/tsconfig.e2e.json')
)
);
expect(tsconfigE2E.compilerOptions.outDir).toEqual(
'../../../dist/out-tsc/apps/my-app-e2e'
);
expect(tsconfigE2E.extends).toEqual('../../../tsconfig.json');
});

it('should default the prefix to npmScope', () => {
const noPrefix = schematicRunner.runSchematic(
'app',
{ name: 'myApp', nestE2e: true },
appTree
);
const withPrefix = schematicRunner.runSchematic(
'app',
{ name: 'myApp', prefix: 'custom', nestE2e: true },
appTree
);

// Testing without prefix

let appE2eSpec = noPrefix
.read('apps/my-app/e2e/src/app.e2e-spec.ts')
.toString();
let angularJson = JSON.parse(noPrefix.read('angular.json').toString());
let myAppPrefix = angularJson.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();
angularJson = JSON.parse(withPrefix.read('angular.json').toString());
myAppPrefix = angularJson.projects['my-app'].prefix;

expect(myAppPrefix).toEqual('custom');
expect(appE2eSpec).toContain('Welcome to my-app!');
});
});

describe('nested', () => {
it('should update angular.json', () => {
const tree = schematicRunner.runSchematic(
Expand Down Expand Up @@ -214,6 +333,99 @@ describe('app', () => {
});
});

describe('nested, with nested e2e', () => {
it('should update angular.json', () => {
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', nestE2e: true },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');

expect(angularJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app/'
);
expect(angularJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app/e2e/'
);
});

it('should update nx.json', () => {
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', tags: 'one,two', nestE2e: true },
appTree
);
const nxJson = readJsonInTree(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', () => {
const hasJsonValue = ({ path, expectedValue, lookupFn }) => {
const content = getFileContent(tree, path);
const config = JSON.parse(stripJsonComments(content));

expect(lookupFn(config)).toEqual(expectedValue);
};
const tree = schematicRunner.runSchematic(
'app',
{ name: 'myApp', directory: 'myDir', nestE2e: true },
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/karma.conf.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/src/app.po.ts'
].forEach(path => {
expect(tree.exists(path)).toBeTruthy();
});

// check e2e spec file patching was successful
const e2eSpec = getFileContent(
tree,
'apps/my-dir/my-app/e2e/src/app.e2e-spec.ts'
);
expect(e2eSpec.indexOf('<any>')).toBeGreaterThan(-1);
expect(e2eSpec.indexOf('jasmine')).toBeGreaterThan(-1);

// Make sure these have properties
[
{
path: 'apps/my-dir/my-app/tsconfig.app.json',
lookupFn: json => json.compilerOptions.outDir,
expectedValue: '../../../dist/out-tsc/apps/my-dir/my-app'
},
{
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);
});
});

it('should import NgModule', () => {
const tree = schematicRunner.runSchematic(
'app',
Expand Down
22 changes: 19 additions & 3 deletions packages/schematics/src/collection/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,21 @@ function updateE2eProject(options: NormalizedSchema): Rule {
content.replace('Welcome to app!', `Welcome to ${options.prefix}!`)
);

// also patching the spec file for nested e2e projects.
if (options.nestE2e) {
// fix for jasmine types error 'string not assignable'
host.overwrite(spec, content.replace('expect', 'expect<any>'));
// tsconfig typeRoots aren't respected
// workaround: add "import {} from 'jasmine'"
const specFile = ts.createSourceFile(
spec,
content,
ts.ScriptTarget.Latest,
true
);
insert(host, spec, [insertImport(specFile, spec, '', 'jasmine')]);
}

return chain([
updateJsonInTree(getWorkspacePath(host), json => {
const project = json.projects[options.e2eProjectName];
Expand All @@ -269,9 +284,9 @@ function updateE2eProject(options: NormalizedSchema): Rule {
extends: `${offsetFromRoot(options.e2eProjectRoot)}tsconfig.json`,
compilerOptions: {
...json.compilerOptions,
outDir: `${offsetFromRoot(options.e2eProjectRoot)}dist/out-tsc/${
outDir: `${offsetFromRoot(
options.e2eProjectRoot
}`
)}dist/out-tsc/${options.e2eProjectRoot.slice(0, -4) + '-e2e'}`
}
};
})
Expand Down Expand Up @@ -329,7 +344,8 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const e2eProjectName = `${appProjectName}-e2e`;

const appProjectRoot = `apps/${appDirectory}`;
const e2eProjectRoot = `apps/${appDirectory}-e2e`;
let e2eProjectRoot = `apps/${appDirectory}-e2e`;
if (options.nestE2e) e2eProjectRoot = `apps/${appDirectory}/e2e`;

const parsedTags = options.tags
? options.tags.split(',').map(s => s.trim())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export interface Schema {
directory?: string;
tags?: string;
unitTestRunner: UnitTestRunner;
nestE2e?: boolean;
}
6 changes: 6 additions & 0 deletions packages/schematics/src/collection/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests",
"default": "karma"
},
"nestE2e": {
"type": "boolean",
"description":
"Specifies if the e2e project will be nested in the app directory",
"default": false
}
},
"required": []
Expand Down