feat: ngmodules and insert components based on the AST (#1616)

This commit is contained in:
Hans 2016-08-09 21:49:57 -07:00 committed by GitHub
parent 4fd8e9c511
commit 5bcb7be917
15 changed files with 559 additions and 214 deletions

View File

@ -120,28 +120,23 @@ module.exports = {
return;
}
var returns = [];
var modulePath = path.resolve(process.env.PWD, this.dynamicPath.appRoot, 'app.module.ts');
var classifiedName =
stringUtils.classify(`${options.entity.name}-${options.originBlueprintName}`);
var importPath = `'./${options.entity.name}/` +
stringUtils.dasherize(`${options.entity.name}.component';`);
const returns = [];
const modulePath = path.join(this.project.root, this.dynamicPath.appRoot, 'app.module.ts');
const className = stringUtils.classify(`${options.entity.name}Component`);
const fileName = stringUtils.dasherize(`${options.entity.name}.component`);
const componentDir = path.relative(this.dynamicPath.appRoot, this.generatePath);
const importPath = componentDir ? `./${componentDir}/${fileName}` : `./${fileName}`;
if (!options.flat) {
returns.push(function() {
return addBarrelRegistration(this, this.generatePath)
});
returns.push(addBarrelRegistration(this, componentDir));
} else {
returns.push(function() {
return addBarrelRegistration(
this,
this.generatePath,
options.entity.name + '.component')
});
returns.push(addBarrelRegistration(this, componentDir, fileName));
}
if (!options['skip-import']) {
returns.push(astUtils.importComponent(modulePath, classifiedName, importPath));
returns.push(
astUtils.addComponentToModule(modulePath, className, importPath)
.then(change => change.apply()));
}
return Promise.all(returns);

View File

@ -54,27 +54,27 @@ module.exports = {
},
afterInstall: function(options) {
var returns = [];
var modulePath = path.resolve(process.env.PWD, this.dynamicPath.appRoot, 'app.module.ts');
var classifiedName =
stringUtils.classify(options.entity.name);
var importPath = '\'./' + stringUtils.dasherize(`${options.entity.name}.directive';`);
if (options.dryRun) {
return;
}
const returns = [];
const modulePath = path.join(this.project.root, this.dynamicPath.appRoot, 'app.module.ts');
const className = stringUtils.classify(`${options.entity.name}`);
const fileName = stringUtils.dasherize(`${options.entity.name}.directive`);
const componentDir = path.relative(this.dynamicPath.appRoot, this.generatePath);
const importPath = componentDir ? `./${componentDir}/${fileName}` : `./${fileName}`;
if (!options.flat) {
returns.push(function() {
return addBarrelRegistration(this, this.generatePath)
});
returns.push(addBarrelRegistration(this, componentDir));
} else {
returns.push(function() {
return addBarrelRegistration(
this,
this.generatePath,
options.entity.name + '.directive')
});
returns.push(addBarrelRegistration(this, componentDir, fileName));
}
if (!options['skip-import']) {
returns.push(astUtils.importComponent(modulePath, classifiedName, importPath));
returns.push(
astUtils.addComponentToModule(modulePath, className, importPath)
.then(change => change.apply()));
}
return Promise.all(returns);

View File

@ -2,8 +2,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ApplicationRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';<% if (isMobile) { %>
import { AppShellModule } from '../app-shell-module';<% } %>
import { AppComponent } from './app.component';
@NgModule({
declarations: [
@ -12,12 +11,12 @@ import { AppShellModule } from '../app-shell-module';<% } %>
imports: [
BrowserModule,
CommonModule,
FormsModule<% if (isMobile) { %>,
AppShellModule<% } %>
FormsModule
],
providers: [],
entryComponents: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
}

View File

@ -12,14 +12,14 @@
},
"private": true,
"dependencies": {
"@angular/common": "github:angular/common-builds",
"@angular/compiler": "github:angular/compiler-builds",
"@angular/core": "github:angular/core-builds",
"@angular/forms": "github:angular/forms-builds",
"@angular/http": "github:angular/http-builds",
"@angular/platform-browser": "github:angular/platform-browser-builds",
"@angular/platform-browser-dynamic": "github:angular/platform-browser-dynamic-builds",
"@angular/router": "github:angular/router-builds",
"@angular/common": "2.0.0-rc.5",
"@angular/compiler": "2.0.0-rc.5",
"@angular/core": "2.0.0-rc.5",
"@angular/forms": "0.3.0",
"@angular/http": "2.0.0-rc.5",
"@angular/platform-browser": "2.0.0-rc.5",
"@angular/platform-browser-dynamic": "2.0.0-rc.5",
"@angular/router": "3.0.0-rc.1",
"core-js": "^2.4.0",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",

View File

@ -52,27 +52,27 @@ module.exports = {
},
afterInstall: function(options) {
var returns = [];
var modulePath = path.resolve(process.env.PWD, this.dynamicPath.appRoot, 'app.module.ts');
var classifiedName =
stringUtils.classify(`${options.entity.name}-${options.originBlueprintName}`);
var importPath = '\'./' + stringUtils.dasherize(`${options.entity.name}.pipe';`);
if (options.dryRun) {
return;
}
const returns = [];
const modulePath = path.join(this.project.root, this.dynamicPath.appRoot, 'app.module.ts');
const className = stringUtils.classify(`${options.entity.name}Pipe`);
const fileName = stringUtils.dasherize(`${options.entity.name}.pipe`);
const componentDir = path.relative(this.dynamicPath.appRoot, this.generatePath);
const importPath = componentDir ? `./${componentDir}/${fileName}` : `./${fileName}`;
if (!options.flat) {
returns.push(function() {
return addBarrelRegistration(this, this.generatePath)
});
returns.push(addBarrelRegistration(this, componentDir));
} else {
returns.push(function() {
return addBarrelRegistration(
this,
this.generatePath,
options.entity.name + '.pipe')
});
returns.push(addBarrelRegistration(this, componentDir, fileName));
}
if (!options['skip-import']) {
returns.push(astUtils.importComponent(modulePath, classifiedName, importPath));
returns.push(
astUtils.addComponentToModule(modulePath, className, importPath)
.then(change => change.apply()));
}
return Promise.all(returns);

View File

@ -3,10 +3,11 @@ var Blueprint = require('ember-cli/lib/models/blueprint');
var dynamicPathParser = require('../../utilities/dynamic-path-parser');
var addBarrelRegistration = require('../../utilities/barrel-management');
var getFiles = Blueprint.prototype.files;
const stringUtils = require('ember-cli-string-utils');
module.exports = {
description: '',
availableOptions: [
{ name: 'flat', type: Boolean, default: true }
],
@ -24,10 +25,10 @@ module.exports = {
flat: options.flat
};
},
files: function() {
var fileList = getFiles.call(this);
if (this.options && this.options.flat) {
fileList = fileList.filter(p => p.indexOf('index.ts') <= 0);
}
@ -48,17 +49,17 @@ module.exports = {
}
};
},
afterInstall: function(options) {
const returns = [];
const fileName = stringUtils.dasherize(`${options.entity.name}.service`);
if (!options.flat) {
return addBarrelRegistration(
this,
this.generatePath);
returns.push(addBarrelRegistration(this, this.generatePath));
} else {
return addBarrelRegistration(
this,
this.generatePath,
options.entity.name + '.service');
returns.push(addBarrelRegistration(this, this.generatePath, fileName));
}
return Promise.all(returns);
}
};

View File

@ -1,6 +1,24 @@
import * as ts from 'typescript';
import * as fs from 'fs';
import { InsertChange } from './change';
import {Symbols} from '@angular/tsc-wrapped/src/symbols';
import {
isMetadataImportedSymbolReferenceExpression,
isMetadataModuleReferenceExpression
} from '@angular/tsc-wrapped';
import {Change, InsertChange, NoopChange, MultiChange} from './change';
import {insertImport} from './route-utils';
import {Observable} from 'rxjs/Observable';
import {ReplaySubject} from 'rxjs/ReplaySubject';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/last';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/toArray';
import 'rxjs/add/operator/toPromise';
/**
* Get TS source file based on path.
@ -12,6 +30,32 @@ export function getSource(filePath: string): ts.SourceFile {
ts.ScriptTarget.ES6, true);
}
/**
* Get all the nodes from a source, as an observable.
* @param sourceFile The source file object.
* @returns {Observable<ts.Node>} An observable of all the nodes in the source.
*/
export function getSourceNodes(sourceFile: ts.SourceFile): Observable<ts.Node> {
const subject = new ReplaySubject<ts.Node>();
let nodes: ts.Node[] = [sourceFile];
while(nodes.length > 0) {
const node = nodes.shift();
if (node) {
subject.next(node);
if (node.getChildCount(sourceFile) >= 0) {
nodes.unshift(...node.getChildren());
}
}
}
subject.complete();
return subject.asObservable();
}
/**
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
* @param node
@ -30,25 +74,6 @@ export function findNodes(node: ts.Node, kind: ts.SyntaxKind): ts.Node[] {
foundNodes.concat(findNodes(child, kind)), arr);
}
/**
* Find all nodes from the AST in the subtree based on text.
* @param node
* @param text
* @return all nodes of text, or [] if none is found
*/
export function findNodesByText(node: ts.Node, text: string): ts.Node[] {
if (!node) {
return [];
}
let arr: ts.Node[] = [];
if (node.getText() === text) {
arr.push(node);
}
return node.getChildren().reduce((foundNodes, child) => {
return foundNodes.concat(findNodesByText(child, text));
}, arr);
}
/**
* Helper for sorting nodes.
@ -58,6 +83,7 @@ function nodesByPosition(first: ts.Node, second: ts.Node): number {
return first.pos - second.pos;
}
/**
* Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
* or after the last of occurence of `syntaxKind` if the last occurence is a sub child
@ -84,39 +110,163 @@ export function insertAfterLastOccurrence(nodes: ts.Node[], toInsert: string,
return new InsertChange(file, lastItemPosition, toInsert);
}
/**
* Custom function to insert component (component, pipe, directive)
* into NgModule declarations. It also imports the component.
* @param modulePath
* @param classifiedName
* @param importPath
* @return Promise
*/
export function importComponent(modulePath: string, classifiedName: string,
importPath: string): Promise<void> {
let source: ts.SourceFile = this.getSource(modulePath);
let importNode: ts.Node =
this.findNodesByText(source, 'import').pop();
let iPos: ts.LineAndCharacter =
source.getLineAndCharacterOfPosition(importNode.getEnd());
let iLine: number = iPos.line + 1;
let iStart: number = source.getPositionOfLineAndCharacter(iLine, 0);
let iStr: string = `import { ${classifiedName} } from ${importPath}\n`;
let changeImport: InsertChange = new InsertChange(modulePath, iStart, iStr);
export function getDecoratorMetadata(source: ts.SourceFile, identifier: string,
module: string): Observable<ts.Node> {
const symbols = new Symbols(source);
return getSourceNodes(source)
.filter(node => {
return node.kind == ts.SyntaxKind.Decorator
&& (<ts.Decorator>node).expression.kind == ts.SyntaxKind.CallExpression;
})
.map(node => <ts.CallExpression>(<ts.Decorator>node).expression)
.filter(expr => {
if (expr.expression.kind == ts.SyntaxKind.Identifier) {
const id = <ts.Identifier>expr.expression;
const metaData = symbols.resolve(id.getFullText(source));
if (isMetadataImportedSymbolReferenceExpression(metaData)) {
return metaData.name == identifier && metaData.module == module;
}
} else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
// This covers foo.NgModule when importing * as foo.
const paExpr = <ts.PropertyAccessExpression>expr.expression;
// If the left expression is not an identifier, just give up at that point.
if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
return false;
}
return changeImport.apply().then(() => {
source = this.getSource(modulePath);
let declarationsNode: ts.Node =
this.findNodesByText(source, 'declarations').shift();
let dPos: ts.LineAndCharacter =
source.getLineAndCharacterOfPosition(declarationsNode.getEnd());
let dStart: number =
source.getPositionOfLineAndCharacter(dPos.line + 1, -1);
let dStr: string = `\n ${classifiedName},`;
let changeDeclarations: InsertChange = new InsertChange(modulePath, dStart, dStr);
return changeDeclarations.apply();
});
const id = paExpr.name;
const moduleId = <ts.Identifier>paExpr.expression;
const moduleMetaData = symbols.resolve(moduleId.getFullText(source));
if (isMetadataModuleReferenceExpression(moduleMetaData)) {
return moduleMetaData.module == module && id.getFullText(source) == identifier;
}
}
return false;
})
.filter(expr => expr.arguments[0]
&& expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression)
.map(expr => <ts.ObjectLiteralExpression>expr.arguments[0]);
}
function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: string,
symbolName: string, importPath: string) {
const source: ts.SourceFile = getSource(ngModulePath);
let metadata = getDecoratorMetadata(source, 'NgModule', '@angular/core');
// Find the decorator declaration.
return metadata
.toPromise()
.then((node: ts.ObjectLiteralExpression) => {
if (!node) {
return null;
}
// Get all the children property assignment of object literals.
return node.properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
.filter(prop => {
switch (prop.name.kind) {
case ts.SyntaxKind.Identifier:
return prop.name.getText(source) == metadataField;
case ts.SyntaxKind.StringLiteral:
return prop.name.text == metadataField;
}
return false;
});
})
// Get the last node of the array literal.
.then(matchingProperties => {
if (!matchingProperties) {
return;
}
if (matchingProperties.length == 0) {
return metadata
.toPromise();
}
const assignment = <ts.PropertyAssignment>matchingProperties[0];
// If it's not an array, nothing we can do really.
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
return Observable.empty();
}
const arrLiteral = <ts.ArrayLiteralExpression>assignment.initializer;
if (arrLiteral.elements.length == 0) {
// Forward the property.
return arrLiteral;
}
return arrLiteral.elements;
})
.then((node: ts.Node) => {
if (!node) {
console.log('No app module found. Please add your new class to your component.');
return new NoopChange();
}
if (Array.isArray(node)) {
node = node[node.length - 1];
}
let toInsert;
let position = node.getEnd();
if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {
// We haven't found the field in the metadata declaration. Insert a new
// field.
let expr = <ts.ObjectLiteralExpression>node;
if (expr.properties.length == 0) {
position = expr.getEnd() - 1;
toInsert = ` ${metadataField}: [${symbolName}]\n`;
} else {
node = expr.properties[expr.properties.length - 1];
position = node.getEnd();
// Get the indentation of the last element, if any.
const text = node.getFullText(source);
if (text.startsWith('\n')) {
toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${metadataField}: [${symbolName}]`;
} else {
toInsert = `, ${metadataField}: [${symbolName}]`;
}
}
} else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {
// We found the field but it's empty. Insert it just before the `]`.
position--;
toInsert = `${symbolName}`;
} else {
// Get the indentation of the last element, if any.
const text = node.getFullText(source);
if (text.startsWith('\n')) {
toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${symbolName}`;
} else {
toInsert = `, ${symbolName}`;
}
}
const insert = new InsertChange(ngModulePath, position, toInsert);
const importInsert: Change = insertImport(ngModulePath, symbolName, importPath);
return new MultiChange([insert, importInsert]);
});
}
/**
* Custom function to insert a declaration (component, pipe, directive)
* into NgModule declarations. It also imports the component.
*/
export function addComponentToModule(modulePath: string, classifiedName: string,
importPath: string): Promise<Change> {
return _addSymbolToNgModuleMetadata(modulePath, 'declarations', classifiedName, importPath);
}
/**
* Custom function to insert a provider into NgModule. It also imports it.
*/
export function addProviderToModule(modulePath: string, classifiedName: string,
importPath: string): Promise<Change> {
return _addSymbolToNgModuleMetadata(modulePath, 'providers', classifiedName, importPath);
}

View File

@ -22,6 +22,58 @@ export interface Change {
description: string;
}
/**
* An operation that does nothing.
*/
export class NoopChange implements Change {
get description() { return 'No operation.'; }
get order() { return Infinity; }
get path() { return null; }
apply() { return Promise.resolve(); }
}
/**
* An operation that mixes two or more changes, and merge them (in order).
* Can only apply to a single file. Use a ChangeManager to apply changes to multiple
* files.
*/
export class MultiChange implements Change {
private _path: string;
private _changes: Change[];
constructor(...changes: Array<Change[], Change>) {
this._changes = [];
[].concat(...changes).forEach(change => this.appendChange(change));
}
appendChange(change: Change) {
// Validate that the path is the same for everyone of those.
if (this._path === undefined) {
this._path = change.path;
} else if (change.path !== this._path) {
throw new Error('Cannot apply a change to a different path.');
}
this._changes.push(change);
}
get description() {
return `Changes:\n ${this._changes.map(x => x.description).join('\n ')}`;
}
// Always apply as early as the highest change.
get order() { return Math.max(...this._changes); }
get path() { return this._path; }
apply() {
return this._changes
.sort((a: Change, b: Change) => b.order - a.order)
.reduce((promise, change) => {
return promise.then(() => change.apply())
}, Promise.resolve());
}
}
/**
* Will add text to the source code.
*/

View File

@ -4,7 +4,8 @@ var fs = require('fs');
module.exports = function dynamicPathParser(project, entityName) {
var projectRoot = project.root;
var appRoot = path.join(project.ngConfig.defaults.sourceDir, 'app');
var sourceDir = project.ngConfig.defaults.sourceDir;
var appRoot = path.join(sourceDir, 'app');
var cwd = process.env.PWD;
var rootPath = path.join(projectRoot, appRoot);
@ -52,7 +53,8 @@ module.exports = function dynamicPathParser(project, entityName) {
}
parsedPath.dir = parsedPath.dir === path.sep ? '' : parsedPath.dir;
parsedPath.appRoot = appRoot
parsedPath.appRoot = appRoot;
parsedPath.sourceDir = sourceDir;
return parsedPath;
};

View File

@ -32,6 +32,7 @@
},
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
"@angular/tsc-wrapped": "^0.2.2",
"@types/lodash": "^4.0.25-alpha",
"@types/rimraf": "0.0.25-alpha",
"@types/webpack": "^1.12.22-alpha",
@ -74,6 +75,7 @@
"remap-istanbul": "^0.6.4",
"resolve": "^1.1.7",
"rimraf": "^2.5.3",
"rxjs": "^5.0.0-beta.11",
"sass-loader": "^3.2.0",
"shelljs": "^0.7.0",
"silent-error": "^1.0.0",

View File

@ -6,7 +6,8 @@ import { InsertChange, RemoveChange } from '../../addon/ng2/utilities/change';
import * as Promise from 'ember-cli/lib/ext/promise';
import {
findNodes,
insertAfterLastOccurrence
insertAfterLastOccurrence,
addComponentToModule
} from '../../addon/ng2/utilities/ast-utils';
const readFile = Promise.denodeify(fs.readFile);
@ -164,6 +165,122 @@ describe('ast-utils: insertAfterLastOccurrence', () => {
});
});
describe('addComponentToModule', () => {
beforeEach(() => {
mockFs( {
'1.ts': `
import {NgModule} from '@angular/core';
@NgModule({
declarations: []
})
class Module {}`,
'2.ts': `
import {NgModule} from '@angular/core';
@NgModule({
declarations: [
Other
]
})
class Module {}`,
'3.ts': `
import {NgModule} from '@angular/core';
@NgModule({
})
class Module {}`,
'4.ts': `
import {NgModule} from '@angular/core';
@NgModule({
field1: [],
field2: {}
})
class Module {}`
});
});
afterEach(() => mockFs.restore());
it('works with empty array', () => {
return addComponentToModule('1.ts', 'MyClass', 'MyImportPath')
.then(change => change.apply())
.then(() => readFile('1.ts', 'utf-8'))
.then(content => {
expect(content).to.equal(
'\n' +
'import {NgModule} from \'@angular/core\';\n' +
'import { MyClass } from \'MyImportPath\';\n' +
'\n' +
'@NgModule({\n' +
' declarations: [MyClass]\n' +
'})\n' +
'class Module {}'
);
})
});
it('works with array with declarations', () => {
return addComponentToModule('2.ts', 'MyClass', 'MyImportPath')
.then(change => change.apply())
.then(() => readFile('2.ts', 'utf-8'))
.then(content => {
expect(content).to.equal(
'\n' +
'import {NgModule} from \'@angular/core\';\n' +
'import { MyClass } from \'MyImportPath\';\n' +
'\n' +
'@NgModule({\n' +
' declarations: [\n' +
' Other,\n' +
' MyClass\n' +
' ]\n' +
'})\n' +
'class Module {}'
);
})
});
it('works without any declarations', () => {
return addComponentToModule('3.ts', 'MyClass', 'MyImportPath')
.then(change => change.apply())
.then(() => readFile('3.ts', 'utf-8'))
.then(content => {
expect(content).to.equal(
'\n' +
'import {NgModule} from \'@angular/core\';\n' +
'import { MyClass } from \'MyImportPath\';\n' +
'\n' +
'@NgModule({\n' +
' declarations: [MyClass]\n' +
'})\n' +
'class Module {}'
);
})
});
it('works without a declaration field', () => {
return addComponentToModule('4.ts', 'MyClass', 'MyImportPath')
.then(change => change.apply())
.then(() => readFile('4.ts', 'utf-8'))
.then(content => {
expect(content).to.equal(
'\n' +
'import {NgModule} from \'@angular/core\';\n' +
'import { MyClass } from \'MyImportPath\';\n' +
'\n' +
'@NgModule({\n' +
' field1: [],\n' +
' field2: {},\n' +
' declarations: [MyClass]\n' +
'})\n' +
'class Module {}'
);
})
});
});
/**
* Gets node of kind kind from sourceFile
*/

View File

@ -1,4 +1,3 @@
/*eslint-disable no-console */
'use strict';
var fs = require('fs-extra');
@ -11,6 +10,10 @@ var root = process.cwd();
var conf = require('ember-cli/tests/helpers/conf');
var Promise = require('ember-cli/lib/ext/promise');
var SilentError = require('silent-error');
const denodeify = require('denodeify');
const readFile = denodeify(fs.readFile);
describe('Acceptance: ng generate component', function () {
before(conf.setup);
@ -27,18 +30,23 @@ describe('Acceptance: ng generate component', function () {
afterEach(function () {
this.timeout(10000);
return tmp.teardown('./tmp');
});
it('ng generate component my-comp', function () {
return ng(['generate', 'component', 'my-comp']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
});
it('my-comp', function () {
const testPath = path.join(root, 'tmp/foo/src/app/my-comp/my-comp.component.ts');
const appModule = path.join(root, 'tmp/foo/src/app/app.module.ts');
return ng(['generate', 'component', 'my-comp'])
.then(() => expect(existsSync(testPath)).to.equal(true))
.then(() => readFile(appModule, 'utf-8'))
.then(content => {
// Expect that the app.module contains a reference to my-comp and its import.
expect(content).matches(/import.*MyCompComponent.*from '.\/my-comp\/my-comp.component';/);
expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyCompComponent\n/m);
});
});
it('ng generate component test' + path.sep + 'my-comp', function () {
it('test' + path.sep + 'my-comp', function () {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', 'test'));
return ng(['generate', 'component', 'test' + path.sep + 'my-comp']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'test', 'my-comp', 'my-comp.component.ts');
@ -46,7 +54,7 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component test' + path.sep + '..' + path.sep + 'my-comp', function () {
it('test' + path.sep + '..' + path.sep + 'my-comp', function () {
return ng(['generate', 'component', 'test' + path.sep + '..' + path.sep + 'my-comp'])
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.ts');
@ -54,7 +62,7 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component my-comp from a child dir', () => {
it('my-comp from a child dir', () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return new Promise(function (resolve) {
process.chdir('./src');
@ -68,10 +76,10 @@ describe('Acceptance: ng generate component', function () {
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate component child-dir' + path.sep + 'my-comp from a child dir', () => {
it('child-dir' + path.sep + 'my-comp from a child dir', () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'child-dir'));
return new Promise(function (resolve) {
process.chdir('./src');
@ -86,50 +94,39 @@ describe('Acceptance: ng generate component', function () {
var testPath = path.join(
root, 'tmp', 'foo', 'src', 'app', '1', 'child-dir', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate component child-dir' + path.sep + '..' + path.sep + 'my-comp from a child dir',
() => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return new Promise(function (resolve) {
process.chdir('./src');
resolve();
it('child-dir' + path.sep + '..' + path.sep + 'my-comp from a child dir', () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return Promise.resolve()
.then(() => process.chdir(path.normalize('./src/app/1')))
.then(() => {
return ng([
'generate', 'component', 'child-dir' + path.sep + '..' + path.sep + 'my-comp'
])
})
.then(() => process.chdir('./app'))
.then(() => process.chdir('./1'))
.then(() => {
return ng([
'generate', 'component', 'child-dir' + path.sep + '..' + path.sep + 'my-comp'
])
})
.then(() => {
var testPath =
path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
.then(() => {
var testPath =
path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
});
});
it('ng generate component ' + path.sep + 'my-comp from a child dir, gens under ' +
path.join('src', 'app'),
() => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return new Promise(function (resolve) {
process.chdir('./src');
resolve();
it(path.sep + 'my-comp from a child dir, gens under ' + path.join('src', 'app'), () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return Promise.resolve()
.then(() => process.chdir(path.normalize('./src/app/1')))
.then(() => {
return ng(['generate', 'component', path.sep + 'my-comp'])
})
.then(() => process.chdir('./app'))
.then(() => process.chdir('./1'))
.then(() => {
return ng(['generate', 'component', path.sep + 'my-comp'])
})
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.ts');
expect(existsSync(testPath)).to.equal(true);
});
});
it('ng generate component ..' + path.sep + 'my-comp from root dir will fail', () => {
it('..' + path.sep + 'my-comp from root dir will fail', () => {
return ng(['generate', 'component', '..' + path.sep + 'my-comp']).then(() => {
throw new SilentError(`ng generate component ..${path.sep}my-comp from root dir should fail.`);
}, (err) => {
@ -137,7 +134,7 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component mycomp will prefix selector', () => {
it('mycomp will prefix selector', () => {
return ng(['generate', 'component', 'mycomp'])
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'mycomp', 'mycomp.component.ts');
@ -147,7 +144,7 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component mycomp --no-prefix will not prefix selector', () => {
it('mycomp --no-prefix will not prefix selector', () => {
return ng(['generate', 'component', 'mycomp', '--no-prefix'])
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'mycomp', 'mycomp.component.ts');
@ -157,7 +154,7 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component myComp will succeed', () => {
it('myComp will succeed', () => {
return ng(['generate', 'component', 'myComp'])
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.ts');
@ -165,24 +162,24 @@ describe('Acceptance: ng generate component', function () {
});
});
it('ng generate component my-comp --inline-template', function () {
it('my-comp --inline-template', function () {
return ng(['generate', 'component', 'my-comp', '--inline-template']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.html');
expect(existsSync(testPath)).to.equal(false);
});
});
it('ng generate component my-comp --inline-style', function () {
it('my-comp --inline-style', function () {
return ng(['generate', 'component', 'my-comp', '--inline-style']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.css');
expect(existsSync(testPath)).to.equal(false);
});
});
it('ng generate component my-comp --nospec', function() {
it('my-comp --nospec', function() {
return ng(['generate', 'component', 'my-comp', '--nospec']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-comp', 'my-comp.component.spec.ts');
expect(existsSync(testPath)).to.equal(false);
});
})
});
});

View File

@ -1,4 +1,3 @@
/*eslint-disable no-console */
'use strict';
var fs = require('fs-extra');
@ -11,6 +10,10 @@ var root = process.cwd();
var conf = require('ember-cli/tests/helpers/conf');
var Promise = require('ember-cli/lib/ext/promise');
var SilentError = require('silent-error');
const denodeify = require('denodeify');
const readFile = denodeify(fs.readFile);
describe('Acceptance: ng generate directive', function () {
before(conf.setup);
@ -31,21 +34,28 @@ describe('Acceptance: ng generate directive', function () {
return tmp.teardown('./tmp');
});
it('ng generate flat directive', function () {
it('flat', function () {
return ng(['generate', 'directive', 'flat']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'flat.directive.ts');
var testPath = path.join(root, 'tmp/foo/src/app/flat.directive.ts');
expect(existsSync(testPath)).to.equal(true);
});
});
it('ng generate directive my-dir', function () {
return ng(['generate', 'directive', 'my-dir', '--flat', 'false']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-dir', 'my-dir.directive.ts');
expect(existsSync(testPath)).to.equal(true);
});
it('my-dir --flat false', function () {
const appRoot = path.join(root, 'tmp/foo');
const testPath = path.join(appRoot, 'src/app/my-dir/my-dir.directive.ts');
const appModulePath = path.join(appRoot, 'src/app/app.module.ts');
return ng(['generate', 'directive', 'my-dir', '--flat', 'false'])
.then(() => expect(existsSync(testPath)).to.equal(true))
.then(() => readFile(appModulePath, 'utf-8'))
.then(content => {
expect(content).matches(/import.*\bMyDir\b.*from '.\/my-dir\/my-dir.directive';/);
expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyDir\n/m);
});
});
it('ng generate directive test' + path.sep + 'my-dir', function () {
it('test' + path.sep + 'my-dir', function () {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', 'test'));
return ng(['generate', 'directive', 'test' + path.sep + 'my-dir', '--flat', 'false']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'test', 'my-dir', 'my-dir.directive.ts');
@ -53,7 +63,7 @@ describe('Acceptance: ng generate directive', function () {
});
});
it('ng generate directive test' + path.sep + '..' + path.sep + 'my-dir', function () {
it('test' + path.sep + '..' + path.sep + 'my-dir', function () {
return ng(['generate', 'directive', 'test' + path.sep + '..' + path.sep + 'my-dir', '--flat', 'false'])
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-dir', 'my-dir.directive.ts');
@ -61,7 +71,7 @@ describe('Acceptance: ng generate directive', function () {
});
});
it('ng generate directive my-dir from a child dir', () => {
it('my-dir from a child dir', () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return new Promise(function (resolve) {
process.chdir('./src');
@ -76,10 +86,10 @@ describe('Acceptance: ng generate directive', function () {
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-dir', 'my-dir.directive.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate directive child-dir' + path.sep + 'my-dir from a child dir', () => {
it('child-dir' + path.sep + 'my-dir from a child dir', () => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'child-dir'));
return new Promise(function (resolve) {
process.chdir('./src');
@ -95,10 +105,10 @@ describe('Acceptance: ng generate directive', function () {
var testPath = path.join(
root, 'tmp', 'foo', 'src', 'app', '1', 'child-dir', 'my-dir', 'my-dir.directive.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate directive child-dir' + path.sep + '..' + path.sep + 'my-dir from a child dir',
it('child-dir' + path.sep + '..' + path.sep + 'my-dir from a child dir',
() => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
return new Promise(function (resolve) {
@ -116,10 +126,10 @@ describe('Acceptance: ng generate directive', function () {
var testPath =
path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-dir', 'my-dir.directive.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate directive ' + path.sep + 'my-dir from a child dir, gens under ' +
it(path.sep + 'my-dir from a child dir, gens under ' +
path.join('src', 'app'),
() => {
fs.mkdirsSync(path.join(root, 'tmp', 'foo', 'src', 'app', '1'));
@ -136,10 +146,10 @@ describe('Acceptance: ng generate directive', function () {
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-dir', 'my-dir.directive.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate directive ..' + path.sep + 'my-dir from root dir will fail', () => {
it('..' + path.sep + 'my-dir from root dir will fail', () => {
return ng(['generate', 'directive', '..' + path.sep + 'my-dir']).then(() => {
throw new SilentError(`ng generate directive ..${path.sep}my-dir from root dir should fail.`);
}, (err) => {

View File

@ -11,6 +11,10 @@ var root = process.cwd();
var conf = require('ember-cli/tests/helpers/conf');
var Promise = require('ember-cli/lib/ext/promise');
var SilentError = require('silent-error');
const denodeify = require('denodeify');
const readFile = denodeify(fs.readFile);
describe('Acceptance: ng generate pipe', function () {
before(conf.setup);
@ -32,10 +36,16 @@ describe('Acceptance: ng generate pipe', function () {
});
it('ng generate pipe my-pipe', function () {
return ng(['generate', 'pipe', 'my-pipe']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-pipe.pipe.ts');
expect(existsSync(testPath)).to.equal(true);
});
const appRoot = path.join(root, 'tmp/foo');
const testPath = path.join(appRoot, 'src/app/my-pipe.pipe.ts');
const appModulePath = path.join(appRoot, 'src/app/app.module.ts');
return ng(['generate', 'pipe', 'my-pipe'])
.then(() => expect(existsSync(testPath)).to.equal(true))
.then(() => readFile(appModulePath, 'utf-8'))
.then(content => {
expect(content).matches(/import.*\bMyPipePipe\b.*from '.\/my-pipe.pipe';/);
expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyPipePipe\n/m);
});
});
it('ng generate pipe test' + path.sep + 'my-pipe', function () {

View File

@ -1,4 +1,3 @@
/*eslint-disable no-console */
'use strict';
var fs = require('fs-extra');
@ -11,6 +10,10 @@ var root = process.cwd();
var conf = require('ember-cli/tests/helpers/conf');
var Promise = require('ember-cli/lib/ext/promise');
var SilentError = require('silent-error');
const denodeify = require('denodeify');
const readFile = denodeify(fs.readFile);
describe('Acceptance: ng generate service', function () {
before(conf.setup);
@ -32,10 +35,17 @@ describe('Acceptance: ng generate service', function () {
});
it('ng generate service my-svc', function () {
return ng(['generate', 'service', 'my-svc']).then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-svc.service.ts');
expect(existsSync(testPath)).to.equal(true);
});
const appRoot = path.join(root, 'tmp/foo');
const testPath = path.join(appRoot, 'src/app/my-svc.service.ts');
const appModulePath = path.join(appRoot, 'src/app/app.module.ts');
return ng(['generate', 'service', 'my-svc'])
.then(() => expect(existsSync(testPath)).to.equal(true))
.then(() => readFile(appModulePath, 'utf-8'))
.then(content => {
expect(content).not.to.matches(/import.*\MySvcService\b.*from '.\/my-svc.service';/);
expect(content).not.to.matches(/providers:\s*\[MySvcService\]/m);
});
});
it('ng generate service test' + path.sep + 'my-svc', function () {
@ -68,7 +78,7 @@ describe('Acceptance: ng generate service', function () {
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-svc.service.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate service child-dir' + path.sep + 'my-svc from a child dir', () => {
@ -87,7 +97,7 @@ describe('Acceptance: ng generate service', function () {
var testPath = path.join(
root, 'tmp', 'foo', 'src', 'app', '1', 'child-dir', 'my-svc.service.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate service child-dir' + path.sep + '..' + path.sep + 'my-svc from a child dir',
@ -108,7 +118,7 @@ describe('Acceptance: ng generate service', function () {
var testPath =
path.join(root, 'tmp', 'foo', 'src', 'app', '1', 'my-svc.service.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate service ' + path.sep + 'my-svc from a child dir, gens under ' +
@ -128,7 +138,7 @@ describe('Acceptance: ng generate service', function () {
.then(() => {
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-svc.service.ts');
expect(existsSync(testPath)).to.equal(true);
}, err => console.log('ERR: ', err));
});
});
it('ng generate service ..' + path.sep + 'my-svc from root dir will fail', () => {