Skip to content

feat(@schematics/angular): change layout of e2e files #13780

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

Merged
merged 4 commits into from
Mar 4, 2019
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
},
"exclude": [
"test.ts",
"**/*.spec.ts"
"**/*.spec.ts",
"e2e/**"
]<% if (enableIvy) { %>,
"angularCompilerOptions": {
"enableIvy": true
Expand Down
2 changes: 0 additions & 2 deletions packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,8 @@ export default function (options: ApplicationOptions): Rule {
const tsLintRoot = appDir;

const e2eOptions: E2eOptions = {
name: `${options.name}-e2e`,
relatedAppName: options.name,
rootSelector: appRootSelector,
projectRoot: newProjectRoot ? `${newProjectRoot}/${options.name}-e2e` : 'e2e',
};

const styleExt = styleToFileExtention(options.style);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ exports.config = {
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
Expand Down
13 changes: 0 additions & 13 deletions packages/schematics/angular/e2e/files/tsconfig.e2e.json.template

This file was deleted.

13 changes: 13 additions & 0 deletions packages/schematics/angular/e2e/files/tsconfig.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
"compilerOptions": {
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
133 changes: 43 additions & 90 deletions packages/schematics/angular/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { strings, tags } from '@angular-devkit/core';
import { strings } from '@angular-devkit/core';
import {
Rule,
SchematicContext,
Expand All @@ -20,120 +20,73 @@ import {
} from '@angular-devkit/schematics';
import { getWorkspace, updateWorkspace } from '../utility/config';
import { getProject } from '../utility/project';
import {
Builders,
ProjectType,
WorkspaceProject,
WorkspaceSchema,
} from '../utility/workspace-models';
import { Builders, WorkspaceSchema } from '../utility/workspace-models';
import { Schema as E2eOptions } from './schema';

function addAppToWorkspaceFile(options: E2eOptions, workspace: WorkspaceSchema): Rule {
return (host: Tree, context: SchematicContext) => {
let projectRoot = options.projectRoot !== undefined
? options.projectRoot
: `${workspace.newProjectRoot}/${options.name}`;
function getE2eRoot(projectRoot: string): string {
const root = projectRoot.split('/').filter(x => x).join('/');

if (projectRoot !== '' && !projectRoot.endsWith('/')) {
projectRoot += '/';
}
return root ? root + '/e2e' : 'e2e';
}

if (getProject(workspace, options.name)) {
throw new SchematicsException(`Project name "${options.name}" already exists.`);
}
function AddBuilderToWorkspace(options: E2eOptions, workspace: WorkspaceSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const appProject = options.relatedAppName;
const project = getProject(workspace, appProject);
const architect = project.architect;

const project: WorkspaceProject = {
root: projectRoot,
projectType: ProjectType.Application,
prefix: '',
architect: {
e2e: {
builder: Builders.Protractor,
options: {
protractorConfig: `${projectRoot}protractor.conf.js`,
devServerTarget: `${options.relatedAppName}:serve`,
},
configurations: {
production: {
devServerTarget: `${options.relatedAppName}:serve:production`,
},
},
const projectRoot = getE2eRoot(project.root);

if (architect) {
architect.e2e = {
builder: Builders.Protractor,
options: {
protractorConfig: `${projectRoot}/protractor.conf.js`,
devServerTarget: `${options.relatedAppName}:serve`,
},
lint: {
builder: Builders.TsLint,
options: {
tsConfig: `${projectRoot}tsconfig.e2e.json`,
exclude: [
'**/node_modules/**',
],
configurations: {
production: {
devServerTarget: `${options.relatedAppName}:serve:production`,
},
},
},
};

workspace.projects[options.name] = project;

return updateWorkspace(workspace);
};
}
const projectNameRegexp = /^[a-zA-Z][.0-9a-zA-Z]*(-[.0-9a-zA-Z]*)*$/;
const unsupportedProjectNames = ['test', 'ember', 'ember-cli', 'vendor', 'app'];
};

function getRegExpFailPosition(str: string): number | null {
const parts = str.indexOf('-') >= 0 ? str.split('-') : [str];
const matched: string[] = [];
const lintConfig = architect.lint;
if (lintConfig) {
lintConfig.options.tsConfig =
lintConfig.options.tsConfig.concat(`${projectRoot}/tsconfig.json`);
}

parts.forEach(part => {
if (part.match(projectNameRegexp)) {
matched.push(part);
workspace.projects[options.relatedAppName] = project;
}
});

const compare = matched.join('-');

return (str !== compare) ? compare.length : null;
}

function validateProjectName(projectName: string) {
const errorIndex = getRegExpFailPosition(projectName);
if (errorIndex !== null) {
const firstMessage = tags.oneLine`
Project name "${projectName}" is not valid. New project names must
start with a letter, and must contain only alphanumeric characters or dashes.
When adding a dash the segment after the dash must also start with a letter.
`;
const msg = tags.stripIndent`
${firstMessage}
${projectName}
${Array(errorIndex + 1).join(' ') + '^'}
`;
throw new SchematicsException(msg);
} else if (unsupportedProjectNames.indexOf(projectName) !== -1) {
throw new SchematicsException(`Project name "${projectName}" is not a supported name.`);
}

return updateWorkspace(workspace);
};
}

export default function (options: E2eOptions): Rule {
return (host: Tree) => {
validateProjectName(options.name);

const appProject = options.relatedAppName;
const workspace = getWorkspace(host);
const appDir = options.projectRoot !== undefined
? options.projectRoot
: `${workspace.newProjectRoot}/${options.name}`;
const project = getProject(workspace, appProject);

if (!project) {
throw new SchematicsException(`Project name "${appProject}" doesn't not exist.`);
}

const root = getE2eRoot(project.root);
const relativePathToWorkspaceRoot = root.split('/').map(() => '..').join('/');

return chain([
addAppToWorkspaceFile(options, workspace),
AddBuilderToWorkspace(options, workspace),
mergeWith(
apply(url('./files'), [
applyTemplates({
utils: strings,
...options,
'dot': '.',
appDir,
relativePathToWorkspaceRoot,
}),
move(appDir),
move(root),
])),
]);
};
Expand Down
76 changes: 38 additions & 38 deletions packages/schematics/angular/e2e/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { Schema as ApplicationOptions } from '../application/schema';
import { Schema as WorkspaceOptions } from '../workspace/schema';
import { Schema as E2eOptions } from './schema';

// tslint:disable:max-line-length
describe('Application Schematic', () => {
const schematicRunner = new SchematicTestRunner(
'@schematics/angular',
Expand All @@ -23,81 +23,81 @@ describe('Application Schematic', () => {
};

const defaultOptions: E2eOptions = {
relatedAppName: 'foo',
};

const defaultAppOptions: ApplicationOptions = {
name: 'foo',
relatedAppName: 'app',
inlineStyle: true,
inlineTemplate: true,
routing: false,
skipPackageJson: false,
minimal: true,
};

let workspaceTree: UnitTestTree;
let applicationTree: UnitTestTree;

beforeEach(() => {
workspaceTree = schematicRunner.runSchematic('workspace', workspaceOptions);
const workspaceTree = schematicRunner.runSchematic('workspace', workspaceOptions);
applicationTree = schematicRunner.runSchematic('application', defaultAppOptions, workspaceTree);
});

it('should create all files of an e2e application', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
it('should create all files of e2e in an application', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, applicationTree);
const files = tree.files;
expect(files).toEqual(jasmine.arrayContaining([
'/projects/foo/protractor.conf.js',
'/projects/foo/tsconfig.e2e.json',
'/projects/foo/src/app.e2e-spec.ts',
'/projects/foo/src/app.po.ts',
'/projects/foo/e2e/protractor.conf.js',
'/projects/foo/e2e/tsconfig.json',
'/projects/foo/e2e/src/app.e2e-spec.ts',
'/projects/foo/e2e/src/app.po.ts',
]));
});

it('should create all files of an e2e application', () => {
const options = {...defaultOptions, projectRoot: 'e2e'};
const tree = schematicRunner.runSchematic('e2e', options, workspaceTree);
const files = tree.files;
expect(files).not.toContain('/projects/foo/protractor.conf.js');
expect(files).toContain('/e2e/protractor.conf.js');
});

it('should set the rootSelector in the app.po.ts', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
const content = tree.readContent('/projects/foo/src/app.po.ts');
const tree = schematicRunner.runSchematic('e2e', defaultOptions, applicationTree);
const content = tree.readContent('/projects/foo/e2e/src/app.po.ts');
expect(content).toMatch(/app\-root/);
});

it('should set the rootSelector in the app.po.ts from the option', () => {
const options = {...defaultOptions, rootSelector: 't-a-c-o'};
const tree = schematicRunner.runSchematic('e2e', options, workspaceTree);
const content = tree.readContent('/projects/foo/src/app.po.ts');
const tree = schematicRunner.runSchematic('e2e', options, applicationTree);
const content = tree.readContent('/projects/foo/e2e/src/app.po.ts');
expect(content).toMatch(/t\-a\-c\-o/);
});

it('should set the rootSelector in the app.po.ts from the option with emoji', () => {
const options = {...defaultOptions, rootSelector: '🌮-🌯'};
const tree = schematicRunner.runSchematic('e2e', options, workspaceTree);
const content = tree.readContent('/projects/foo/src/app.po.ts');
const tree = schematicRunner.runSchematic('e2e', options, applicationTree);
const content = tree.readContent('/projects/foo/e2e/src/app.po.ts');
expect(content).toMatch(/🌮-🌯/);
});

describe('workspace config', () => {
it('should create the e2e app', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
const workspace = JSON.parse(tree.readContent('/angular.json'));
expect(workspace.projects.foo).toBeDefined();
});

it('should set 2 targets for the app', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
it('should add e2e targets for the app', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, applicationTree);
const workspace = JSON.parse(tree.readContent('/angular.json'));
const targets = workspace.projects.foo.architect;
expect(Object.keys(targets)).toEqual(['e2e', 'lint']);
expect(targets.e2e).toBeDefined();
});

it('should set the e2e options', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
const tree = schematicRunner.runSchematic('e2e', defaultOptions, applicationTree);
const workspace = JSON.parse(tree.readContent('/angular.json'));
const e2eOptions = workspace.projects.foo.architect.e2e.options;
expect(e2eOptions.protractorConfig).toEqual('projects/foo/protractor.conf.js');
expect(e2eOptions.devServerTarget).toEqual('app:serve');
expect(e2eOptions.protractorConfig).toEqual('projects/foo/e2e/protractor.conf.js');
expect(e2eOptions.devServerTarget).toEqual('foo:serve');
});

it('should set the lint options', () => {
const tree = schematicRunner.runSchematic('e2e', defaultOptions, workspaceTree);
const tree = schematicRunner.runSchematic('e2e', defaultOptions, applicationTree);
const workspace = JSON.parse(tree.readContent('/angular.json'));
const lintOptions = workspace.projects.foo.architect.lint.options;
expect(lintOptions.tsConfig).toEqual('projects/foo/tsconfig.e2e.json');
expect(lintOptions.tsConfig).toEqual([
'projects/foo/tsconfig.app.json',
'projects/foo/tsconfig.spec.json',
'projects/foo/e2e/tsconfig.json',
]);
});
});
});
15 changes: 0 additions & 15 deletions packages/schematics/angular/e2e/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,6 @@
"description": "Generates a new, generic end-to-end test definition for the given or default project.",
"long-description": "e2e-long.md",
"properties": {
"projectRoot": {
"description": "The root folder for the new test app.",
"type": "string",
"visible": false
},
"name": {
"description": "The name of the new e2e app.",
"type": "string",
"format": "html-selector",
"$default": {
"$source": "argv",
"index": 0
}
},
"rootSelector": {
"description": "The HTML selector for the root component of the test app.",
"type": "string",
Expand All @@ -31,7 +17,6 @@
}
},
"required": [
"name",
"relatedAppName"
]
}
Loading