From 48ad70f7b172d6d6308894a89502181310014b09 Mon Sep 17 00:00:00 2001 From: FallenRiteMonk Date: Tue, 30 Aug 2016 09:05:55 +0200 Subject: [PATCH] # This is a combination of 3 commits. # The first commit's message is: fix: add loader for gif add loader to support loading of gif images Closes #1878 # The 2nd commit message will be skipped: # fix(lint): Updated the main.ts's blueprint to fix the ng lint failure (#1903) (#1904) # # The 3rd commit message will be skipped: # Fix for issue 1876: noImplicitAny means make them explicite (#1892) # --- .appveyor.yml | 4 + .gitignore | 3 +- .travis.yml | 7 + README.md | 2 +- WEBPACK_UPDATE.md | 3 - addon/ng2/blueprints/component/index.js | 28 +- addon/ng2/blueprints/directive/index.js | 14 +- .../module/files/__path__/__name__.module.ts | 13 +- .../module/files/__path__/__name__.routing.ts | 6 + addon/ng2/blueprints/module/index.js | 21 +- .../ng2/blueprints/ng2/files/__path__/main.ts | 2 +- .../ng2/blueprints/ng2/files/__path__/test.ts | 4 +- .../ng2/files/__path__/typings.d.ts | 3 - .../ng2/blueprints/ng2/files/angular-cli.json | 3 +- addon/ng2/blueprints/ng2/files/package.json | 33 +- addon/ng2/blueprints/ng2/index.js | 5 +- addon/ng2/blueprints/pipe/index.js | 18 +- addon/ng2/blueprints/service/index.js | 6 + addon/ng2/commands/help.ts | 56 ++ addon/ng2/commands/init.ts | 10 +- addon/ng2/index.js | 1 + addon/ng2/models/find-lazy-modules.ts | 70 +- addon/ng2/models/webpack-build-common.ts | 6 +- addon/ng2/models/webpack-build-production.ts | 3 +- addon/ng2/package.json | 1 - addon/ng2/tsconfig.json | 1 + addon/ng2/utilities/ast-utils.ts | 2 +- addon/ng2/utilities/find-parent-module.ts | 32 + lib/bootstrap-local.js | 4 +- lib/config/schema.d.ts | 1 - lib/config/schema.json | 3 - package.json | 28 +- packages/ast-tools/package.json | 4 +- packages/ast-tools/src/ast-utils.spec.ts | 12 +- packages/ast-tools/src/ast-utils.ts | 25 +- packages/base-href-webpack/package.json | 25 + .../src/base-href-webpack-plugin.spec.ts | 66 ++ .../src}/base-href-webpack-plugin.ts | 0 packages/base-href-webpack/src/index.ts | 2 + packages/base-href-webpack/tsconfig.json | 24 + scripts/run-packages-spec.js | 4 +- .../base-href-webpack-plugin.spec.js | 50 -- tests/acceptance/find-lazy-module.spec.ts | 50 ++ tests/acceptance/generate-component.spec.js | 1 + tests/acceptance/generate-route.spec.js | 21 +- tests/e2e/e2e_workflow.spec.js | 738 ------------------ tests/e2e/setup/000-npm-link.ts | 13 + tests/e2e/setup/010-create-tmp-dir.ts | 9 + tests/e2e/setup/020-create-project.ts | 50 ++ tests/e2e/tests/build/base-href.ts | 8 + tests/e2e/tests/build/dev-build.ts | 10 + tests/e2e/tests/build/environment.ts | 18 + tests/e2e/tests/build/no-implicit-any.ts | 10 + tests/e2e/tests/build/output-dir.ts | 12 + tests/e2e/tests/build/prod-build.ts | 41 + tests/e2e/tests/build/styles/css.ts | 19 + tests/e2e/tests/build/styles/less.ts | 33 + tests/e2e/tests/build/styles/scss.ts | 41 + tests/e2e/tests/generate/component.ts | 18 + tests/e2e/tests/generate/interface.ts | 15 + .../e2e/tests/generate/module/module-basic.ts | 21 + .../tests/generate/module/module-routing.ts | 20 + tests/e2e/tests/generate/pipe.ts | 17 + tests/e2e/tests/generate/service.ts | 17 + tests/e2e/tests/misc/assets.ts | 13 + tests/e2e/tests/misc/coverage.ts | 9 + tests/e2e/tests/misc/lazy-module.ts | 31 + tests/e2e/tests/misc/proxy.ts | 46 ++ tests/e2e/tests/mobile/prod-features.ts | 20 + tests/e2e/tests/test/e2e.ts | 21 + tests/e2e/tests/test/lint.ts | 6 + tests/e2e/tests/test/test.ts | 6 + tests/e2e/tests/third-party/bootstrap.ts | 30 + tests/e2e/utils/ast.ts | 15 + tests/e2e/utils/fs.ts | 96 +++ tests/e2e/utils/git.ts | 37 + tests/e2e/utils/http.ts | 17 + tests/e2e/utils/process.ts | 111 +++ tests/e2e/utils/project.ts | 25 + tests/e2e/utils/utils.ts | 17 + tests/e2e_runner.js | 158 ++++ tests/runner.js | 2 +- tsconfig.json | 3 + 83 files changed, 1485 insertions(+), 965 deletions(-) create mode 100644 addon/ng2/blueprints/module/files/__path__/__name__.routing.ts create mode 100644 addon/ng2/commands/help.ts create mode 100644 addon/ng2/utilities/find-parent-module.ts create mode 100644 packages/base-href-webpack/package.json create mode 100644 packages/base-href-webpack/src/base-href-webpack-plugin.spec.ts rename {addon/ng2/utilities => packages/base-href-webpack/src}/base-href-webpack-plugin.ts (100%) create mode 100644 packages/base-href-webpack/src/index.ts create mode 100644 packages/base-href-webpack/tsconfig.json delete mode 100644 tests/acceptance/base-href-webpack-plugin.spec.js create mode 100644 tests/acceptance/find-lazy-module.spec.ts delete mode 100644 tests/e2e/e2e_workflow.spec.js create mode 100644 tests/e2e/setup/000-npm-link.ts create mode 100644 tests/e2e/setup/010-create-tmp-dir.ts create mode 100644 tests/e2e/setup/020-create-project.ts create mode 100644 tests/e2e/tests/build/base-href.ts create mode 100644 tests/e2e/tests/build/dev-build.ts create mode 100644 tests/e2e/tests/build/environment.ts create mode 100644 tests/e2e/tests/build/no-implicit-any.ts create mode 100644 tests/e2e/tests/build/output-dir.ts create mode 100644 tests/e2e/tests/build/prod-build.ts create mode 100644 tests/e2e/tests/build/styles/css.ts create mode 100644 tests/e2e/tests/build/styles/less.ts create mode 100644 tests/e2e/tests/build/styles/scss.ts create mode 100644 tests/e2e/tests/generate/component.ts create mode 100644 tests/e2e/tests/generate/interface.ts create mode 100644 tests/e2e/tests/generate/module/module-basic.ts create mode 100644 tests/e2e/tests/generate/module/module-routing.ts create mode 100644 tests/e2e/tests/generate/pipe.ts create mode 100644 tests/e2e/tests/generate/service.ts create mode 100644 tests/e2e/tests/misc/assets.ts create mode 100644 tests/e2e/tests/misc/coverage.ts create mode 100644 tests/e2e/tests/misc/lazy-module.ts create mode 100644 tests/e2e/tests/misc/proxy.ts create mode 100644 tests/e2e/tests/mobile/prod-features.ts create mode 100644 tests/e2e/tests/test/e2e.ts create mode 100644 tests/e2e/tests/test/lint.ts create mode 100644 tests/e2e/tests/test/test.ts create mode 100644 tests/e2e/tests/third-party/bootstrap.ts create mode 100644 tests/e2e/utils/ast.ts create mode 100644 tests/e2e/utils/fs.ts create mode 100644 tests/e2e/utils/git.ts create mode 100644 tests/e2e/utils/http.ts create mode 100644 tests/e2e/utils/process.ts create mode 100644 tests/e2e/utils/project.ts create mode 100644 tests/e2e/utils/utils.ts create mode 100644 tests/e2e_runner.js diff --git a/.appveyor.yml b/.appveyor.yml index ec90cf597337..2ffc56173f8c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,6 +3,9 @@ environment: - nodejs_version: "5.0" - nodejs_version: "6.0" +matrix: + fast_finish: true + install: - ps: Install-Product node $env:nodejs_version - npm install @@ -11,5 +14,6 @@ test_script: - node --version - npm --version - npm test + - npm run test:e2e build: off diff --git a/.gitignore b/.gitignore index 656fd0df7ddf..34424b90ae12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ dist/ node_modules/ -npm-debug.log +npm-debug.log* # IDEs .idea/ jsconfig.json +.vscode/ # Typings file. typings/ diff --git a/.travis.yml b/.travis.yml index c8dd1c568356..aacff678fe4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,15 +13,22 @@ env: matrix: - SCRIPT=lint # - SCRIPT=build + - SCRIPT=e2e + - SCRIPT=e2e:nightly - SCRIPT=test # - TARGET=mobile SCRIPT=mobile_test matrix: fast_finish: true allow_failures: - os: osx + - env: SCRIPT=e2e:nightly exclude: - node_js: "6" env: SCRIPT=lint + - os: osx + env: SCRIPT=e2e:nightly + - node_js: "6" + env: SCRIPT=e2e:nightly - os: osx node_js: "5" env: SCRIPT=lint diff --git a/README.md b/README.md index ce0178c2d371..518569848425 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Navigate to `http://localhost:4200/`. The app will automatically reload if you c You can configure the default HTTP port and the one used by the LiveReload server with two command-line options : ```bash -ng serve --port 4201 --live-reload-port 49153 +ng serve --host 0.0.0.0 --port 4201 --live-reload-port 49153 ``` ### Generating Components, Directives, Pipes and Services diff --git a/WEBPACK_UPDATE.md b/WEBPACK_UPDATE.md index 281e4a35f3df..9a03299f45d2 100644 --- a/WEBPACK_UPDATE.md +++ b/WEBPACK_UPDATE.md @@ -16,9 +16,6 @@ rm -rf node_modules dist tmp typings npm install --save-dev angular-cli@webpack ``` -IMPORTANT NOTE: -Currently project generated with `ng new` will use a wrong local CLI version (see https://github.com/angular/angular-cli/issues/1528). After initializing your project, run `npm install --save-dev angular-cli@webpack` to set the correct version. - ## Project files You will need to run `ng init` to check for changes in all the auto-generated files created by `ng new` and allow you to update yours. You are offered four choices for each changed file: `y` (overwrite), `n` (don't overwrite), `d` (show diff between your file and the updated file) and `h` (help). diff --git a/addon/ng2/blueprints/component/index.js b/addon/ng2/blueprints/component/index.js index 2f9f4cea9e9f..3f671370397e 100644 --- a/addon/ng2/blueprints/component/index.js +++ b/addon/ng2/blueprints/component/index.js @@ -2,6 +2,7 @@ var path = require('path'); var chalk = require('chalk'); var Blueprint = require('ember-cli/lib/models/blueprint'); var dynamicPathParser = require('../../utilities/dynamic-path-parser'); +const findParentModule = require('../../utilities/find-parent-module').default; var getFiles = Blueprint.prototype.files; const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); @@ -11,13 +12,20 @@ module.exports = { availableOptions: [ { name: 'flat', type: Boolean, default: false }, - { name: 'route', type: Boolean, default: false }, { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] }, { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] }, { name: 'prefix', type: Boolean, default: true }, { name: 'spec', type: Boolean, default: true } ], + beforeInstall: function() { + try { + this.pathToModule = findParentModule(this.project, this.dynamicPath.dir); + } catch(e) { + throw `Error locating module for declaration\n\t${e}`; + } + }, + normalizeEntityName: function (entityName) { var parsedPath = dynamicPathParser(this.project, entityName); @@ -54,7 +62,6 @@ module.exports = { inlineTemplate: options.inlineTemplate, inlineStyle: options.inlineStyle, route: options.route, - isLazyRoute: !!options.isLazyRoute, isAppComponent: !!options.isAppComponent, selector: this.selector, styleExt: this.styleExt @@ -84,18 +91,6 @@ module.exports = { var dir = this.dynamicPath.dir; if (!options.locals.flat) { dir += path.sep + options.dasherizedModuleName; - - if (options.locals.isLazyRoute) { - var lazyRoutePrefix = '+'; - if (this.project.ngConfig && - this.project.ngConfig.defaults && - this.project.ngConfig.defaults.lazyRoutePrefix !== undefined) { - lazyRoutePrefix = this.project.ngConfig.defaults.lazyRoutePrefix; - } - var dirParts = dir.split(path.sep); - dirParts[dirParts.length - 1] = `${lazyRoutePrefix}${dirParts[dirParts.length - 1]}`; - dir = dirParts.join(path.sep); - } } var srcDir = this.project.ngConfig.apps[0].root; this.appDir = dir.substr(dir.indexOf(srcDir) + srcDir.length); @@ -114,15 +109,14 @@ module.exports = { } 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 componentDir = path.relative(path.dirname(this.pathToModule), this.generatePath); const importPath = componentDir ? `./${componentDir}/${fileName}` : `./${fileName}`; if (!options['skip-import']) { returns.push( - astUtils.addComponentToModule(modulePath, className, importPath) + astUtils.addDeclarationToModule(this.pathToModule, className, importPath) .then(change => change.apply())); } diff --git a/addon/ng2/blueprints/directive/index.js b/addon/ng2/blueprints/directive/index.js index 6b19947731d1..db0858d2fcc9 100644 --- a/addon/ng2/blueprints/directive/index.js +++ b/addon/ng2/blueprints/directive/index.js @@ -2,6 +2,7 @@ var path = require('path'); var dynamicPathParser = require('../../utilities/dynamic-path-parser'); const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); +const findParentModule = require('../../utilities/find-parent-module').default; module.exports = { description: '', @@ -11,11 +12,19 @@ module.exports = { { name: 'prefix', type: Boolean, default: true } ], + beforeInstall: function() { + try { + this.pathToModule = findParentModule(this.project, this.dynamicPath.dir); + } catch(e) { + throw `Error locating module for declaration\n\t${e}`; + } + }, + normalizeEntityName: function (entityName) { var parsedPath = dynamicPathParser(this.project, entityName); this.dynamicPath = parsedPath; - + var defaultPrefix = ''; if (this.project.ngConfig && this.project.ngConfig.apps[0] && @@ -56,7 +65,6 @@ module.exports = { } const returns = []; - const modulePath = path.join(this.project.root, this.dynamicPath.appRoot, 'app.module.ts'); const className = stringUtils.classify(`${options.entity.name}Directive`); const fileName = stringUtils.dasherize(`${options.entity.name}.directive`); const componentDir = path.relative(this.dynamicPath.appRoot, this.generatePath); @@ -64,7 +72,7 @@ module.exports = { if (!options['skip-import']) { returns.push( - astUtils.addComponentToModule(modulePath, className, importPath) + astUtils.addDeclarationToModule(this.pathToModule, className, importPath) .then(change => change.apply())); } diff --git a/addon/ng2/blueprints/module/files/__path__/__name__.module.ts b/addon/ng2/blueprints/module/files/__path__/__name__.module.ts index 822b4c57379b..67f4f4a7a854 100644 --- a/addon/ng2/blueprints/module/files/__path__/__name__.module.ts +++ b/addon/ng2/blueprints/module/files/__path__/__name__.module.ts @@ -1,15 +1,12 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { routing } from './<%= dasherizedModuleName %>.routes'; -import { <%= classifiedModuleName %>Component } from './<%= dasherizedModuleName %>.component'; +import { CommonModule } from '@angular/common';<% if (routing) { %> +import { <%= camelizedModuleName %>Routing } from './<%= dasherizedModuleName %>.routing';<% } %> @NgModule({ imports: [ - CommonModule, - routing + CommonModule<% if (routing) { %>, + <%= camelizedModuleName %>Routing<% } %> ], - declarations: [ - <%= classifiedModuleName %>Component - ] + declarations: [] }) export class <%= classifiedModuleName %>Module { } diff --git a/addon/ng2/blueprints/module/files/__path__/__name__.routing.ts b/addon/ng2/blueprints/module/files/__path__/__name__.routing.ts new file mode 100644 index 000000000000..7a5a6bf2c5b5 --- /dev/null +++ b/addon/ng2/blueprints/module/files/__path__/__name__.routing.ts @@ -0,0 +1,6 @@ +import { Routes, RouterModule } from '@angular/router'; + +export const <%= camelizedModuleName %>Routes: Routes = []; + +export const <%= camelizedModuleName %>Routing = RouterModule.forChild(<%= camelizedModuleName %>˝Routes); + diff --git a/addon/ng2/blueprints/module/index.js b/addon/ng2/blueprints/module/index.js index 8d636dd1638b..7b2f01ff0e04 100644 --- a/addon/ng2/blueprints/module/index.js +++ b/addon/ng2/blueprints/module/index.js @@ -7,9 +7,10 @@ module.exports = { description: '', availableOptions: [ - { name: 'spec', type: Boolean, default: false } + { name: 'spec', type: Boolean, default: false }, + { name: 'routing', type: Boolean, default: false } ], - + normalizeEntityName: function (entityName) { this.entityName = entityName; var parsedPath = dynamicPathParser(this.project, entityName); @@ -19,9 +20,10 @@ module.exports = { }, locals: function (options) { - return { + return { dynamicPath: this.dynamicPath.dir, - spec: options.spec + spec: options.spec, + routing: options.routing }; }, @@ -31,6 +33,9 @@ module.exports = { if (!this.options || !this.options.spec) { fileList = fileList.filter(p => p.indexOf('__name__.module.spec.ts') < 0); } + if (this.options && !this.options.routing) { + fileList = fileList.filter(p => p.indexOf('__name__.routing.ts') < 0); + } return fileList; }, @@ -40,8 +45,8 @@ module.exports = { this.dasherizedModuleName = options.dasherizedModuleName; return { __path__: () => { - this.generatePath = this.dynamicPath.dir - + path.sep + this.generatePath = this.dynamicPath.dir + + path.sep + options.dasherizedModuleName; return this.generatePath; } @@ -49,8 +54,8 @@ module.exports = { }, afterInstall: function (options) { - options.entity.name = this.entityName; - options.flat = false; + options.entity.name = path.join(this.entityName, this.dasherizedModuleName); + options.flat = true; options.route = false; options.inlineTemplate = false; options.inlineStyle = false; diff --git a/addon/ng2/blueprints/ng2/files/__path__/main.ts b/addon/ng2/blueprints/ng2/files/__path__/main.ts index 80160e71251e..5c3c520409d2 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/main.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/main.ts @@ -1,4 +1,4 @@ -import "./polyfills.ts"; +import './polyfills.ts'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; diff --git a/addon/ng2/blueprints/ng2/files/__path__/test.ts b/addon/ng2/blueprints/ng2/files/__path__/test.ts index a85e8bb7823d..7727c8e6f802 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/test.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/test.ts @@ -1,13 +1,15 @@ import './polyfills.ts'; import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; import 'zone.js/dist/jasmine-patch'; import 'zone.js/dist/async-test'; import 'zone.js/dist/fake-async-test'; -import 'zone.js/dist/sync-test'; // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. declare var __karma__: any; +declare var require: any; // Prevent Karma from running prematurely. __karma__.loaded = function () {}; diff --git a/addon/ng2/blueprints/ng2/files/__path__/typings.d.ts b/addon/ng2/blueprints/ng2/files/__path__/typings.d.ts index dc0276ded1cd..a73f58673ef0 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/typings.d.ts +++ b/addon/ng2/blueprints/ng2/files/__path__/typings.d.ts @@ -3,6 +3,3 @@ // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html declare var System: any; -declare var module: { id: string }; -declare var require: any; - diff --git a/addon/ng2/blueprints/ng2/files/angular-cli.json b/addon/ng2/blueprints/ng2/files/angular-cli.json index 8e2ec1e19637..d86fae02fb79 100644 --- a/addon/ng2/blueprints/ng2/files/angular-cli.json +++ b/addon/ng2/blueprints/ng2/files/angular-cli.json @@ -39,7 +39,6 @@ }, "defaults": { "styleExt": "<%= styleExt %>", - "prefixInterfaces": false, - "lazyRoutePrefix": "+" + "prefixInterfaces": false } } diff --git a/addon/ng2/blueprints/ng2/files/package.json b/addon/ng2/blueprints/ng2/files/package.json index 169e02a48055..414e164ac89c 100644 --- a/addon/ng2/blueprints/ng2/files/package.json +++ b/addon/ng2/blueprints/ng2/files/package.json @@ -12,21 +12,21 @@ }, "private": true, "dependencies": { - "@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", + "@angular/common": "2.0.0-rc.6", + "@angular/compiler": "2.0.0-rc.6", + "@angular/core": "2.0.0-rc.6", + "@angular/forms": "2.0.0-rc.6", + "@angular/http": "2.0.0-rc.6", + "@angular/platform-browser": "2.0.0-rc.6", + "@angular/platform-browser-dynamic": "2.0.0-rc.6", + "@angular/router": "3.0.0-rc.2", + "core-js": "^2.4.1", "rxjs": "5.0.0-beta.11", "ts-helpers": "^1.1.1", - "zone.js": "0.6.12" + "zone.js": "^0.6.17" }, "devDependencies": {<% if(isMobile) { %> - "@angular/platform-server": "2.0.0-rc.4", + "@angular/platform-server": "2.0.0-rc.6", "@angular/service-worker": "0.2.0", "@angular/app-shell": "0.0.0", "angular2-universal":"0.104.5", @@ -38,13 +38,14 @@ "codelyzer": "~0.0.26", "jasmine-core": "2.4.1", "jasmine-spec-reporter": "2.5.0", - "karma": "0.13.22", - "karma-chrome-launcher": "0.2.3", - "karma-jasmine": "0.3.8", + "karma": "1.2.0", + "karma-chrome-launcher": "^2.0.0", + "karma-cli": "^1.0.1", + "karma-jasmine": "^1.0.2", "karma-remap-istanbul": "^0.2.1", - "protractor": "4.0.3", + "protractor": "4.0.5", "ts-node": "1.2.1", "tslint": "3.13.0", - "typescript": "^2.0.0" + "typescript": "2.0.2" } } diff --git a/addon/ng2/blueprints/ng2/index.js b/addon/ng2/blueprints/ng2/index.js index 90588b23bcf5..0d92a58ea137 100644 --- a/addon/ng2/blueprints/ng2/index.js +++ b/addon/ng2/blueprints/ng2/index.js @@ -21,7 +21,7 @@ module.exports = { locals: function(options) { this.styleExt = options.style; - this.version = require(path.resolve(__dirname, '..', '..', '..', '..', 'package.json')).version; + this.version = require(path.resolve(__dirname, '../../../../package.json')).version; // Join with / not path.sep as reference to typings require forward slashes. const relativeRootPath = options.sourceDir.split(path.sep).map(() => '..').join('/'); @@ -44,12 +44,11 @@ module.exports = { files: function() { var fileList = getFiles.call(this); - if (this.options && this.options.mobile) { fileList = fileList.filter(p => p.indexOf('__name__.component.html') < 0); fileList = fileList.filter(p => p.indexOf('__name__.component.__styleext__') < 0); } - + return fileList; }, diff --git a/addon/ng2/blueprints/pipe/index.js b/addon/ng2/blueprints/pipe/index.js index d6fe6ce7f2b7..13f4d0902891 100644 --- a/addon/ng2/blueprints/pipe/index.js +++ b/addon/ng2/blueprints/pipe/index.js @@ -2,14 +2,23 @@ var path = require('path'); var dynamicPathParser = require('../../utilities/dynamic-path-parser'); const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); +const findParentModule = require('../../utilities/find-parent-module').default; module.exports = { description: '', - + availableOptions: [ { name: 'flat', type: Boolean, default: true } ], + beforeInstall: function() { + try { + this.pathToModule = findParentModule(this.project, this.dynamicPath.dir); + } catch(e) { + throw `Error locating module for declaration\n\t${e}`; + } + }, + normalizeEntityName: function (entityName) { var parsedPath = dynamicPathParser(this.project, entityName); @@ -18,7 +27,7 @@ module.exports = { }, locals: function (options) { - return { + return { dynamicPath: this.dynamicPath.dir, flat: options.flat }; @@ -37,14 +46,13 @@ module.exports = { } }; }, - + afterInstall: function(options) { 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); @@ -52,7 +60,7 @@ module.exports = { if (!options['skip-import']) { returns.push( - astUtils.addComponentToModule(modulePath, className, importPath) + astUtils.addDeclarationToModule(this.pathToModule, className, importPath) .then(change => change.apply())); } diff --git a/addon/ng2/blueprints/service/index.js b/addon/ng2/blueprints/service/index.js index d29dae56eb33..ea535fd44d2b 100644 --- a/addon/ng2/blueprints/service/index.js +++ b/addon/ng2/blueprints/service/index.js @@ -1,4 +1,5 @@ var path = require('path'); +const chalk = require('chalk'); var dynamicPathParser = require('../../utilities/dynamic-path-parser'); module.exports = { @@ -34,5 +35,10 @@ module.exports = { return dir; } }; + }, + + afterInstall() { + const warningMessage = 'Service is generated but not provided, it must be provided to be used'; + this._writeStatusToUI(chalk.yellow, 'WARNING', warningMessage); } }; diff --git a/addon/ng2/commands/help.ts b/addon/ng2/commands/help.ts new file mode 100644 index 000000000000..06c5773ba1c2 --- /dev/null +++ b/addon/ng2/commands/help.ts @@ -0,0 +1,56 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +const Command = require('ember-cli/lib/models/command'); +const stringUtils = require('ember-cli-string-utils'); +const lookupCommand = require('ember-cli/lib/cli/lookup-command'); + +const commandsToIgnore = [ + 'help', + 'easter-egg', + 'completion', + 'github-pages-deploy' +]; + +const HelpCommand = Command.extend({ + name: 'help', + description: 'Shows help for the CLI', + works: 'outsideProject', + + availableOptions: [], + + run: function (commandOptions: any) { + let commandFiles = fs.readdirSync(__dirname) + .map(file => path.parse(file).name) + .map(file => file.toLowerCase()); + + commandFiles = commandFiles.filter(file => { + return commandsToIgnore.indexOf(file) < 0; + }); + + let commandMap = commandFiles.reduce((acc: any, curr: string) => { + let classifiedName = stringUtils.classify(curr); + let defaultImport = require(`./${curr}`).default; + + acc[classifiedName] = defaultImport; + + return acc; + }, {}); + + commandFiles.forEach(cmd => { + let Command = lookupCommand(commandMap, cmd); + + let command = new Command({ + ui: this.ui, + project: this.project, + commands: this.commands, + tasks: this.tasks + }); + + this.ui.writeLine(command.printBasicHelp(commandOptions)); + }); + } +}); + +HelpCommand.overrideCore = true; +export default HelpCommand; diff --git a/addon/ng2/commands/init.ts b/addon/ng2/commands/init.ts index a3252a73d984..8be39cca2819 100644 --- a/addon/ng2/commands/init.ts +++ b/addon/ng2/commands/init.ts @@ -1,5 +1,3 @@ -'use strict'; - import LinkCli from '../tasks/link-cli'; const Command = require('ember-cli/lib/models/command'); @@ -55,7 +53,7 @@ const InitCommand: any = Command.extend({ // needs an explicit check in case it's just 'undefined' // due to passing of options from 'new' and 'addon' - let gitInit; + let gitInit: any; if (commandOptions.skipGit === false) { gitInit = new GitInit({ ui: this.ui, @@ -63,7 +61,7 @@ const InitCommand: any = Command.extend({ }); } - let linkCli; + let linkCli: any; if (commandOptions.linkCli) { linkCli = new LinkCli({ ui: this.ui, @@ -72,7 +70,7 @@ const InitCommand: any = Command.extend({ }); } - let npmInstall; + let npmInstall: any; if (!commandOptions.skipNpm) { npmInstall = new NpmInstall({ ui: this.ui, @@ -81,7 +79,7 @@ const InitCommand: any = Command.extend({ }); } - let bowerInstall; + let bowerInstall: any; if (!commandOptions.skipBower) { bowerInstall = new this.tasks.BowerInstall({ ui: this.ui, diff --git a/addon/ng2/index.js b/addon/ng2/index.js index 58c02dc47fa7..218588d30f34 100644 --- a/addon/ng2/index.js +++ b/addon/ng2/index.js @@ -16,6 +16,7 @@ module.exports = { 'serve': require('./commands/serve').default, 'new': require('./commands/new').default, 'generate': require('./commands/generate').default, + 'help': require('./commands/help').default, 'init': require('./commands/init').default, 'test': require('./commands/test').default, 'e2e': require('./commands/e2e').default, diff --git a/addon/ng2/models/find-lazy-modules.ts b/addon/ng2/models/find-lazy-modules.ts index 9b98a9079ec2..85d233091e55 100644 --- a/addon/ng2/models/find-lazy-modules.ts +++ b/addon/ng2/models/find-lazy-modules.ts @@ -6,39 +6,39 @@ import * as ts from 'typescript'; import {getSource, findNodes, getContentOfKeyLiteral} from '../utilities/ast-utils'; -function flatMap(obj: Array, mapFn: (item: T) => R | R[]): Array { - return obj.reduce((arr: R[], current: T) => { - const result = mapFn.call(null, current); - return result !== undefined ? arr.concat(result) : arr; - }, []); -} - - export function findLoadChildren(tsFilePath: string): string[] { const source = getSource(tsFilePath); const unique: { [path: string]: boolean } = {}; - let nodes = flatMap( - findNodes(source, ts.SyntaxKind.ObjectLiteralExpression), - node => findNodes(node, ts.SyntaxKind.PropertyAssignment)) - .filter((node: ts.PropertyAssignment) => { - const key = getContentOfKeyLiteral(source, node.name); - if (!key) { - // key is an expression, can't do anything. - return false; - } - return key == 'loadChildren'; - }) - // Remove initializers that are not files. - .filter((node: ts.PropertyAssignment) => { - return node.initializer.kind === ts.SyntaxKind.StringLiteral; - }) - // Get the full text of the initializer. - .map((node: ts.PropertyAssignment) => { - return JSON.parse(node.initializer.getText(source)); // tslint:disable-line - }); - - return nodes + return ( + // Find all object literals. + findNodes(source, ts.SyntaxKind.ObjectLiteralExpression) + // Get all their property assignments. + .map(node => findNodes(node, ts.SyntaxKind.PropertyAssignment)) + // Flatten into a single array (from an array of array). + .reduce((prev, curr) => curr ? prev.concat(curr) : prev, []) + // Remove every property assignment that aren't 'loadChildren'. + .filter((node: ts.PropertyAssignment) => { + const key = getContentOfKeyLiteral(source, node.name); + if (!key) { + // key is an expression, can't do anything. + return false; + } + return key == 'loadChildren'; + }) + // Remove initializers that are not files. + .filter((node: ts.PropertyAssignment) => { + return node.initializer.kind === ts.SyntaxKind.StringLiteral; + }) + // Get the full text of the initializer. + .map((node: ts.PropertyAssignment) => { + const literal = node.initializer as ts.StringLiteral; + return literal.text; + }) + // Map to the module name itself. + .map((moduleName: string) => moduleName.split('#')[0]) + // Only get unique values (there might be multiple modules from a single URL, or a module used + // multiple times). .filter((value: string) => { if (unique[value]) { return false; @@ -46,21 +46,21 @@ export function findLoadChildren(tsFilePath: string): string[] { unique[value] = true; return true; } - }) - .map((moduleName: string) => moduleName.split('#')[0]); + })); } export function findLazyModules(projectRoot: any): string[] { - const result: {[key: string]: boolean} = {}; + const result: {[key: string]: string} = {}; glob.sync(path.join(projectRoot, '/**/*.ts')) .forEach(tsPath => { findLoadChildren(tsPath).forEach(moduleName => { - const fileName = path.resolve(path.dirname(tsPath), moduleName) + '.ts'; + const fileName = path.resolve(projectRoot, moduleName) + '.ts'; if (fs.existsSync(fileName)) { - result[moduleName] = true; + // Put the moduleName as relative to the main.ts. + result[moduleName] = fileName; } }); }); - return Object.keys(result); + return result; } diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index cbe92ef13711..287dec4b9e86 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -4,12 +4,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); import * as webpack from 'webpack'; const atl = require('awesome-typescript-loader'); +import { BaseHrefWebpackPlugin } from '@angular-cli/base-href-webpack'; import { findLazyModules } from './find-lazy-modules'; -import { BaseHrefWebpackPlugin } from '../utilities/base-href-webpack-plugin'; - - export function getWebpackCommonConfig( projectRoot: string, environment: string, @@ -116,7 +114,7 @@ export function getWebpackCommonConfig( { include: scripts, test: /\.js$/, loader: 'script-loader' },        { test: /\.json$/, loader: 'json-loader' }, -        { test: /\.(jpg|png)$/, loader: 'url-loader?limit=10000' }, +        { test: /\.(jpg|png|gif)$/, loader: 'url-loader?limit=10000' },        { test: /\.html$/, loader: 'raw-loader' }, { test: /\.(woff|ttf|svg)$/, loader: 'url?limit=10000' }, diff --git a/addon/ng2/models/webpack-build-production.ts b/addon/ng2/models/webpack-build-production.ts index 4b41cefee76e..da7c9b636281 100644 --- a/addon/ng2/models/webpack-build-production.ts +++ b/addon/ng2/models/webpack-build-production.ts @@ -15,9 +15,8 @@ export const getWebpackProdConfigPartial = function(projectRoot: string, appConf }, plugins: [ new WebpackMd5Hash(), - new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({ - mangle: { screw_ie8 : true, keep_fnames: true }, + mangle: { screw_ie8 : true }, compress: { screw_ie8: true } }), new CompressionPlugin({ diff --git a/addon/ng2/package.json b/addon/ng2/package.json index 53d9f38255cc..8a0c28b1cd33 100644 --- a/addon/ng2/package.json +++ b/addon/ng2/package.json @@ -2,7 +2,6 @@ "name": "ng2", "version": "0.0.0", "description": "An addon to generate an ng2 project", - "author": "rodyhaddad", "license": "MIT", "keywords": [ "ember-addon" diff --git a/addon/ng2/tsconfig.json b/addon/ng2/tsconfig.json index 116aa140dbe1..f347288cb434 100644 --- a/addon/ng2/tsconfig.json +++ b/addon/ng2/tsconfig.json @@ -20,6 +20,7 @@ "baseUrl": "", "paths": { "@angular-cli/ast-tools": [ "../../packages/ast-tools/src" ], + "@angular-cli/base-href-webpack": [ "../../packages/base-href-webpack/src" ], "@angular-cli/webpack": [ "../../packages/webpack/src" ] } }, diff --git a/addon/ng2/utilities/ast-utils.ts b/addon/ng2/utilities/ast-utils.ts index ab0396bd55eb..6adbe798d071 100644 --- a/addon/ng2/utilities/ast-utils.ts +++ b/addon/ng2/utilities/ast-utils.ts @@ -7,6 +7,6 @@ export { insertAfterLastOccurrence, getContentOfKeyLiteral, getDecoratorMetadata, - addComponentToModule, + addDeclarationToModule, addProviderToModule } from '@angular-cli/ast-tools'; diff --git a/addon/ng2/utilities/find-parent-module.ts b/addon/ng2/utilities/find-parent-module.ts new file mode 100644 index 000000000000..7648560ad5ca --- /dev/null +++ b/addon/ng2/utilities/find-parent-module.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import * as path from 'path'; +const SilentError = require('silent-error'); + +export default function findParentModule(project: any, currentDir: string): string { + const sourceRoot = path.join(project.root, project.ngConfig.apps[0].root, 'app'); + + // trim currentDir + currentDir = currentDir.replace(path.join(project.ngConfig.apps[0].root, 'app'), ''); + + let pathToCheck = path.join(sourceRoot, currentDir); + + while (pathToCheck.length >= sourceRoot.length) { + // let files: string[] = fs.readdirSync(pathToCheck); + + // files = files.filter(file => file.indexOf('.module.ts') > 0); + const files = fs.readdirSync(pathToCheck) + .filter(fileName => fileName.endsWith('.module.ts')) + .filter(fileName => fs.statSync(path.join(pathToCheck, fileName)).isFile()); + + if (files.length === 1) { + return path.join(pathToCheck, files[0]); + } else if (files.length > 1) { + throw new SilentError(`Multiple module files found: ${pathToCheck.replace(sourceRoot, '')}`); + } + + // move to parent directory + pathToCheck = path.dirname(pathToCheck); + } + + throw new SilentError('No module files found'); +}; diff --git a/lib/bootstrap-local.js b/lib/bootstrap-local.js index 80e219cbce88..e658d3b02291 100644 --- a/lib/bootstrap-local.js +++ b/lib/bootstrap-local.js @@ -24,7 +24,7 @@ require.extensions['.ts'] = function(m, filename) { const source = fs.readFileSync(filename).toString(); try { - const result = ts.transpile(source, compilerOptions); + const result = ts.transpile(source, compilerOptions['compilerOptions']); // Send it to node to execute. return m._compile(result, filename); @@ -42,7 +42,7 @@ require.extensions['.ts'] = function(m, filename) { // }); // If we're running locally, meaning npm linked. This is basically "developer mode". -if (!__dirname.match(/\/node_modules\//)) { +if (!__dirname.match(new RegExp(`\\${path.sep}node_modules\\${path.sep}`))) { const packages = require('./packages'); // We mock the module loader so that we can fake our packages when running locally. diff --git a/lib/config/schema.d.ts b/lib/config/schema.d.ts index 68849bc3c920..f660f183f221 100644 --- a/lib/config/schema.d.ts +++ b/lib/config/schema.d.ts @@ -59,6 +59,5 @@ export interface CliConfig { defaults?: { styleExt?: string; prefixInterfaces?: boolean; - lazyRoutePrefix?: string; }; } diff --git a/lib/config/schema.json b/lib/config/schema.json index 412f8eb0ed60..af5d76267e71 100644 --- a/lib/config/schema.json +++ b/lib/config/schema.json @@ -135,9 +135,6 @@ }, "prefixInterfaces": { "type": "boolean" - }, - "lazyRoutePrefix": { - "type": "string" } }, "additionalProperties": false diff --git a/package.json b/package.json index 67a56cc62e00..234ac566d044 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "build:main": "tsc -p addon/ng2", "build:packages": "for PKG in packages/*; do echo Building $PKG...; tsc -P $PKG; done", "test": "npm run test:packages && npm run test:cli", + "e2e": "npm run test:e2e", + "e2e:nightly": "node tests/e2e_runner.js --nightly", "mobile_test": "mocha tests/e2e/e2e_workflow.spec.js", + "test:e2e": "node tests/e2e_runner.js", "test:cli": "node tests/runner", "test:inspect": "node --inspect --debug-brk tests/runner", "test:packages": "node scripts/run-packages-spec.js", @@ -40,13 +43,13 @@ }, "homepage": "https://github.com/angular/angular-cli", "dependencies": { - "@angular/compiler": "^2.0.0-rc.5", - "@angular/compiler-cli": "^0.5.0", - "@angular/core": "^2.0.0-rc.5", - "@angular/tsc-wrapped": "^0.2.2", "@angular-cli/ast-tools": "^1.0.0", + "@angular/compiler": "^2.0.0-rc.6", + "@angular/compiler-cli": "^0.6.0", + "@angular/core": "^2.0.0-rc.6", + "@angular/tsc-wrapped": "^0.2.2", "angular2-template-loader": "^0.5.0", - "awesome-typescript-loader": "^2.2.1", + "awesome-typescript-loader": "^2.2.3", "chalk": "^1.1.3", "common-tags": "^1.3.1", "compression-webpack-plugin": "^0.3.1", @@ -62,6 +65,7 @@ "expose-loader": "^0.7.1", "file-loader": "^0.8.5", "fs-extra": "^0.30.0", + "fs.realpath": "^1.0.0", "glob": "^7.0.3", "handlebars": "^4.0.5", "html-webpack-plugin": "^2.19.0", @@ -79,8 +83,6 @@ "offline-plugin": "^3.4.1", "opn": "4.0.1", "parse5": "^2.1.5", - "phantomjs-polyfill": "0.0.2", - "phantomjs-prebuilt": "^2.1.7", "postcss-loader": "^0.9.1", "protractor": "^3.3.0", "raw-loader": "^0.5.1", @@ -102,10 +104,10 @@ "ts-loader": "^0.8.2", "tslint-loader": "^2.1.4", "typedoc": "^0.4.2", - "typescript": "^2.0.0", + "typescript": "2.0.2", "url-loader": "^0.5.7", - "webpack": "2.1.0-beta.21", - "webpack-dev-server": "2.1.0-beta.0", + "webpack": "^2.1.0-beta.22", + "webpack-dev-server": "^2.1.0-beta.3", "webpack-md5-hash": "0.0.5", "webpack-merge": "^0.14.0" }, @@ -115,15 +117,18 @@ ] }, "devDependencies": { + "@types/chai": "^3.4.32", "@types/chalk": "^0.4.28", "@types/common-tags": "^1.2.3", "@types/denodeify": "^1.2.29", + "@types/express": "^4.0.32", "@types/fs-extra": "^0.0.31", "@types/glob": "^5.0.29", "@types/jasmine": "^2.2.32", "@types/lodash": "^4.0.25-alpha", "@types/mock-fs": "3.6.28", "@types/node": "^6.0.36", + "@types/request": "0.0.30", "@types/rimraf": "0.0.25-alpha", "@types/webpack": "^1.12.22-alpha", "chai": "^3.5.0", @@ -131,12 +136,15 @@ "dtsgenerator": "^0.7.1", "eslint": "^2.8.0", "exists-sync": "0.0.3", + "express": "^4.14.0", "jasmine": "^2.4.1", "jasmine-spec-reporter": "^2.7.0", "minimatch": "^3.0.0", + "minimist": "^1.2.0", "mocha": "^2.4.5", "mock-fs": "3.10.0", "object-assign": "^4.0.1", + "request": "^2.74.0", "rewire": "^2.5.1", "sinon": "^1.17.3", "through": "^2.3.8", diff --git a/packages/ast-tools/package.json b/packages/ast-tools/package.json index f95f5bcd049c..90591e58782c 100644 --- a/packages/ast-tools/package.json +++ b/packages/ast-tools/package.json @@ -2,7 +2,7 @@ "name": "@angular-cli/ast-tools", "version": "1.0.0-beta.11-webpack.5", "description": "CLI tool for Angular", - "main": "./index.js", + "main": "./src/index.js", "keywords": [ "angular", "cli", @@ -22,6 +22,6 @@ "dependencies": { "rxjs": "^5.0.0-beta.11", "denodeify": "^1.2.1", - "typescript": "^2.0.0" + "typescript": "2.0.0" } } diff --git a/packages/ast-tools/src/ast-utils.spec.ts b/packages/ast-tools/src/ast-utils.spec.ts index 7161b03f166b..001ff9a9beab 100644 --- a/packages/ast-tools/src/ast-utils.spec.ts +++ b/packages/ast-tools/src/ast-utils.spec.ts @@ -4,7 +4,7 @@ import ts = require('typescript'); import fs = require('fs'); import {InsertChange, RemoveChange} from './change'; -import {insertAfterLastOccurrence, addComponentToModule} from './ast-utils'; +import {insertAfterLastOccurrence, addDeclarationToModule} from './ast-utils'; import {findNodes} from './node'; import {it} from './spec-utils'; @@ -172,7 +172,7 @@ describe('ast-utils: insertAfterLastOccurrence', () => { }); -describe('addComponentToModule', () => { +describe('addDeclarationToModule', () => { beforeEach(() => { mockFs({ '1.ts': ` @@ -210,7 +210,7 @@ class Module {}` afterEach(() => mockFs.restore()); it('works with empty array', () => { - return addComponentToModule('1.ts', 'MyClass', 'MyImportPath') + return addDeclarationToModule('1.ts', 'MyClass', 'MyImportPath') .then(change => change.apply()) .then(() => readFile('1.ts', 'utf-8')) .then(content => { @@ -228,7 +228,7 @@ class Module {}` }); it('works with array with declarations', () => { - return addComponentToModule('2.ts', 'MyClass', 'MyImportPath') + return addDeclarationToModule('2.ts', 'MyClass', 'MyImportPath') .then(change => change.apply()) .then(() => readFile('2.ts', 'utf-8')) .then(content => { @@ -249,7 +249,7 @@ class Module {}` }); it('works without any declarations', () => { - return addComponentToModule('3.ts', 'MyClass', 'MyImportPath') + return addDeclarationToModule('3.ts', 'MyClass', 'MyImportPath') .then(change => change.apply()) .then(() => readFile('3.ts', 'utf-8')) .then(content => { @@ -267,7 +267,7 @@ class Module {}` }); it('works without a declaration field', () => { - return addComponentToModule('4.ts', 'MyClass', 'MyImportPath') + return addDeclarationToModule('4.ts', 'MyClass', 'MyImportPath') .then(change => change.apply()) .then(() => readFile('4.ts', 'utf-8')) .then(content => { diff --git a/packages/ast-tools/src/ast-utils.ts b/packages/ast-tools/src/ast-utils.ts index 0ffe4c6d8580..41fd8664c764 100644 --- a/packages/ast-tools/src/ast-utils.ts +++ b/packages/ast-tools/src/ast-utils.ts @@ -96,13 +96,9 @@ export function insertAfterLastOccurrence(nodes: ts.Node[], toInsert: string, export function getContentOfKeyLiteral(source: ts.SourceFile, node: ts.Node): string { if (node.kind == ts.SyntaxKind.Identifier) { - return (node).text; + return (node as ts.Identifier).text; } else if (node.kind == ts.SyntaxKind.StringLiteral) { - try { - return JSON.parse(node.getFullText(source)); - } catch (e) { - return null; - } + return (node as ts.StringLiteral).text; } else { return null; } @@ -247,7 +243,8 @@ function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: strin } const insert = new InsertChange(ngModulePath, position, toInsert); - const importInsert: Change = insertImport(ngModulePath, symbolName, importPath); + const importInsert: Change = insertImport( + ngModulePath, symbolName.replace(/\..*$/, ''), importPath); return new MultiChange([insert, importInsert]); }); } @@ -256,12 +253,22 @@ function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: strin * 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 { +export function addDeclarationToModule(modulePath: string, classifiedName: string, + importPath: string): Promise { return _addSymbolToNgModuleMetadata(modulePath, 'declarations', classifiedName, importPath); } +/** + * Custom function to insert a declaration (component, pipe, directive) + * into NgModule declarations. It also imports the component. + */ +export function addImportToModule(modulePath: string, classifiedName: string, + importPath: string): Promise { + + return _addSymbolToNgModuleMetadata(modulePath, 'imports', classifiedName, importPath); +} + /** * Custom function to insert a provider into NgModule. It also imports it. */ diff --git a/packages/base-href-webpack/package.json b/packages/base-href-webpack/package.json new file mode 100644 index 000000000000..43f31ab33f5e --- /dev/null +++ b/packages/base-href-webpack/package.json @@ -0,0 +1,25 @@ +{ + "name": "@angular-cli/base-href-webpack", + "version": "1.0.0", + "description": "Base HREF Webpack plugin", + "main": "./src/index.js", + "keywords": [ + "angular", + "cli", + "webpack", + "plugin", + "tool" + ], + "repository": { + "type": "git", + "url": "https://github.com/angular/angular-cli.git" + }, + "author": "angular", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular-cli/issues" + }, + "homepage": "https://github.com/angular/angular-cli", + "dependencies": { + } +} diff --git a/packages/base-href-webpack/src/base-href-webpack-plugin.spec.ts b/packages/base-href-webpack/src/base-href-webpack-plugin.spec.ts new file mode 100644 index 000000000000..c0c2e320857c --- /dev/null +++ b/packages/base-href-webpack/src/base-href-webpack-plugin.spec.ts @@ -0,0 +1,66 @@ +import {oneLineTrim} from 'common-tags'; +import {BaseHrefWebpackPlugin} from './base-href-webpack-plugin'; + + +function mockCompiler(indexHtml: string, callback: Function) { + return { + plugin: function (event: any, compilerCallback: Function) { + const compilation = { + plugin: function (hook: any, compilationCallback: Function) { + const htmlPluginData = { + html: indexHtml + }; + compilationCallback(htmlPluginData, callback); + } + }; + compilerCallback(compilation); + } + }; +} + +describe('base href webpack plugin', () => { + const html = oneLineTrim` + + + + + `; + + it('should do nothing when baseHref is null', () => { + const plugin = new BaseHrefWebpackPlugin({ baseHref: null }); + + const compiler = mockCompiler(html, (x: any, htmlPluginData: any) => { + expect(htmlPluginData.html).toEqual(''); + }); + plugin.apply(compiler); + }); + + it('should insert base tag when not exist', function () { + const plugin = new BaseHrefWebpackPlugin({ baseHref: '/' }); + const compiler = mockCompiler(html, (x: any, htmlPluginData: any) => { + expect(htmlPluginData.html).toEqual(oneLineTrim` + + + + + `); + }); + + plugin.apply(compiler); + }); + + it('should replace href attribute when base tag already exists', function () { + const plugin = new BaseHrefWebpackPlugin({ baseHref: '/myUrl/' }); + + const compiler = mockCompiler(oneLineTrim` + + + `, (x: any, htmlPluginData: any) => { + expect(htmlPluginData.html).toEqual(oneLineTrim` + + + `); + }); + plugin.apply(compiler); + }); +}); diff --git a/addon/ng2/utilities/base-href-webpack-plugin.ts b/packages/base-href-webpack/src/base-href-webpack-plugin.ts similarity index 100% rename from addon/ng2/utilities/base-href-webpack-plugin.ts rename to packages/base-href-webpack/src/base-href-webpack-plugin.ts diff --git a/packages/base-href-webpack/src/index.ts b/packages/base-href-webpack/src/index.ts new file mode 100644 index 000000000000..3140fa868c4e --- /dev/null +++ b/packages/base-href-webpack/src/index.ts @@ -0,0 +1,2 @@ + +export * from './base-href-webpack-plugin'; diff --git a/packages/base-href-webpack/tsconfig.json b/packages/base-href-webpack/tsconfig.json new file mode 100644 index 000000000000..a607ecd030db --- /dev/null +++ b/packages/base-href-webpack/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "declaration": true, + "experimentalDecorators": true, + "mapRoot": "", + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "outDir": "../../dist/base-href-webpack", + "rootDir": ".", + "sourceMap": true, + "sourceRoot": "/", + "target": "es5", + "lib": ["es6"], + "typeRoots": [ + "../../node_modules/@types" + ], + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/scripts/run-packages-spec.js b/scripts/run-packages-spec.js index 07f18f5140cb..8e798a7402ce 100644 --- a/scripts/run-packages-spec.js +++ b/scripts/run-packages-spec.js @@ -11,9 +11,7 @@ const projectBaseDir = path.join(__dirname, '../packages'); // Create a Jasmine runner and configure it. const jasmine = new Jasmine({ projectBaseDir: projectBaseDir }); -jasmine.loadConfig({ - spec_dir: projectBaseDir -}); +jasmine.loadConfig({}); jasmine.addReporter(new JasmineSpecReporter()); // Run the tests. diff --git a/tests/acceptance/base-href-webpack-plugin.spec.js b/tests/acceptance/base-href-webpack-plugin.spec.js deleted file mode 100644 index 58bf73442e25..000000000000 --- a/tests/acceptance/base-href-webpack-plugin.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -/*eslint-disable no-console */ -'use strict'; - -var expect = require('chai').expect; -var BaseHrefWebpackPlugin = require('../../addon/ng2/utilities/base-href-webpack-plugin').BaseHrefWebpackPlugin; - -function mockCompiler(indexHtml, callback) { - return { - plugin: function (event, compilerCallback) { - var compilation = { - plugin: function (hook, compilationCallback) { - var htmlPluginData = { - html: indexHtml - }; - compilationCallback(htmlPluginData, callback); - } - }; - compilerCallback(compilation); - } - }; -} - -describe('base href webpack plugin', function () { - it('should do nothing when baseHref is null', function () { - var plugin = new BaseHrefWebpackPlugin({ baseHref: null }); - - var compiler = mockCompiler('', function (x, htmlPluginData) { - expect(htmlPluginData.html).to.equal(''); - }); - plugin.apply(compiler); - }); - - it('should insert base tag when not exist', function () { - var plugin = new BaseHrefWebpackPlugin({ baseHref: '/' }); - - var compiler = mockCompiler('', function (x, htmlPluginData) { - expect(htmlPluginData.html).to.equal(''); - }); - plugin.apply(compiler); - }); - - it('should replace href attribute when base tag already exists', function () { - var plugin = new BaseHrefWebpackPlugin({ baseHref: '/myUrl/' }); - - var compiler = mockCompiler('', function (x, htmlPluginData) { - expect(htmlPluginData.html).to.equal(''); - }); - plugin.apply(compiler); - }); -}); diff --git a/tests/acceptance/find-lazy-module.spec.ts b/tests/acceptance/find-lazy-module.spec.ts new file mode 100644 index 000000000000..6f926dc6461f --- /dev/null +++ b/tests/acceptance/find-lazy-module.spec.ts @@ -0,0 +1,50 @@ +import * as mockFs from 'mock-fs'; +import {stripIndents} from 'common-tags'; +import {expect} from 'chai'; +import {join} from 'path'; + +import {findLazyModules} from '../../addon/ng2/models/find-lazy-modules'; + + +describe('find-lazy-module', () => { + beforeEach(() => { + mockFs({ + 'project-root': { + 'fileA.ts': stripIndents` + const r1 = { + "loadChildren": "moduleA" + }; + const r2 = { + loadChildren: "moduleB" + }; + const r3 = { + 'loadChildren': 'moduleC' + }; + const r4 = { + "loadChildren": 'app/+workspace/+settings/settings.module#SettingsModule' + }; + const r5 = { + loadChildren: 'unexistentModule' + }; + `, + // Create those files too as they have to exist. + 'moduleA.ts': '', + 'moduleB.ts': '', + 'moduleC.ts': '', + 'moduleD.ts': '', + 'app': { '+workspace': { '+settings': { 'settings.module.ts': '' } } } + } + }); + }); + afterEach(() => mockFs.restore()); + + it('works', () => { + expect(findLazyModules('project-root')).to.eql({ + 'moduleA': join(process.cwd(), 'project-root', 'moduleA.ts'), + 'moduleB': join(process.cwd(), 'project-root', 'moduleB.ts'), + 'moduleC': join(process.cwd(), 'project-root', 'moduleC.ts'), + 'app/+workspace/+settings/settings.module': + join(process.cwd(), 'project-root', 'app/+workspace/+settings/settings.module.ts'), + }); + }); +}); diff --git a/tests/acceptance/generate-component.spec.js b/tests/acceptance/generate-component.spec.js index ec1e4f82291c..c5abd319faf7 100644 --- a/tests/acceptance/generate-component.spec.js +++ b/tests/acceptance/generate-component.spec.js @@ -21,6 +21,7 @@ describe('Acceptance: ng generate component', function () { after(conf.restore); beforeEach(function () { + this.timeout(10000); return tmp.setup('./tmp').then(function () { process.chdir('./tmp'); }).then(function () { diff --git a/tests/acceptance/generate-route.spec.js b/tests/acceptance/generate-route.spec.js index 579d9ad2fd21..79ec44ac5b99 100644 --- a/tests/acceptance/generate-route.spec.js +++ b/tests/acceptance/generate-route.spec.js @@ -12,9 +12,8 @@ const root = process.cwd(); const testPath = path.join(root, 'tmp', 'foo', 'src', 'app'); -function fileExpectations(lazy, expectation) { - const lazyPrefix = lazy ? '+' : ''; - const dir = `${lazyPrefix}my-route`; +function fileExpectations(expectation) { + const dir = 'my-route'; expect(existsSync(path.join(testPath, dir, 'my-route.component.ts'))).to.equal(expectation); } @@ -43,12 +42,6 @@ xdescribe('Acceptance: ng generate route', function () { }); }); - it('ng generate route my-route --lazy false', function () { - return ng(['generate', 'route', 'my-route', '--lazy', 'false']).then(() => { - fileExpectations(false, true); - }); - }); - it('ng generate route +my-route', function () { return ng(['generate', 'route', '+my-route']).then(() => { fileExpectations(true, true); @@ -102,14 +95,4 @@ xdescribe('Acceptance: ng generate route', function () { expect(afterGenerateParentHtml).to.equal(unmodifiedParentComponentHtml); }); }); - - it('lazy route prefix', () => { - return ng(['set', 'defaults.lazyRoutePrefix', 'myprefix']) - .then(() => ng(['generate', 'route', 'prefix-test'])) - .then(() => { - var folderPath = path.join(testPath, 'myprefixprefix-test', 'prefix-test.component.ts'); - expect(existsSync(folderPath)) - .to.equal(true); - }); - }); }); diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js deleted file mode 100644 index aae76bf54701..000000000000 --- a/tests/e2e/e2e_workflow.spec.js +++ /dev/null @@ -1,738 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var tmp = require('../helpers/tmp'); -var chai = require('chai'); -var expect = chai.expect; -var conf = require('ember-cli/tests/helpers/conf'); -var sh = require('shelljs'); -var treeKill = require('tree-kill'); -var child_process = require('child_process'); -var ng = require('../helpers/ng'); -var root = path.join(process.cwd(), 'tmp'); -var express = require('express'); -var http = require('http'); -var request = require('request'); - -function existsSync(path) { - try { - fs.accessSync(path); - return true; - } catch (e) { - return false; - } -} - -const ngBin = `node ${path.join(process.cwd(), 'bin', 'ng')}`; -const it_mobile = isMobileTest() ? it : function() {}; -const it_not_mobile = isMobileTest() ? function() {} : it; - -describe('Basic end-to-end Workflow', function () { - before(conf.setup); - - after(conf.restore); - - var testArgs = ['test', '--watch', 'false']; - - it('Installs angular-cli correctly', function () { - this.timeout(300000); - - sh.exec('npm link', { silent: true }); - - return tmp.setup('./tmp').then(function () { - process.chdir('./tmp'); - expect(existsSync(path.join(process.cwd(), 'bin', 'ng'))); - }); - }); - - - it('Can create new project using `ng new test-project`', function () { - this.timeout(4200000); - let args = ['--link-cli']; - // If testing in the mobile matrix on Travis, create project with mobile flag - if (isMobileTest()) { - args = args.concat(['--mobile']); - } - return ng(['new', 'test-project'].concat(args)).then(function () { - expect(existsSync(path.join(root, 'test-project'))); - }); - }); - - it('Can change current working directory to `test-project`', function () { - process.chdir(path.join(root, 'test-project')); - expect(path.basename(process.cwd())).to.equal('test-project'); - }); - - it('Supports production builds via `ng build -prod`', function() { - this.timeout(420000); - - // Can't use the `ng` helper because somewhere the environment gets - // stuck to the first build done - sh.exec(`${ngBin} build -prod`); - expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); - const indexHtml = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8'); - // Check for cache busting hash script src - expect(indexHtml).to.match(/main\.[0-9a-f]{20}\.bundle\.js/); - // Also does not create new things in GIT. - expect(sh.exec('git status --porcelain').output).to.be.equal(undefined); - }); - - it_mobile('Enables mobile-specific production features in prod builds', () => { - let indexHtml = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8'); - // Service Worker - expect(indexHtml).to.match(/sw-install\.[0-9a-f]{20}\.bundle\.js/); - expect(existsSync(path.join(process.cwd(), 'dist/sw.js' ))).to.be.equal(true); - - // App Manifest - expect(indexHtml.includes('')).to.be.equal(true); - expect(existsSync(path.join(process.cwd(), 'dist/manifest.webapp'))).to.be.equal(true); - - // Icons folder - expect(existsSync(path.join(process.cwd(), 'dist/icons'))).to.be.equal(true); - - // Prerender content - expect(indexHtml).to.match(/app works!/); - }); - - it('Supports build config file replacement', function() { - this.timeout(420000); - - sh.exec(`${ngBin} build --env=prod`); - var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js'); - var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' }); - - expect(mainBundleContent.includes('production: true')).to.be.equal(true); - }); - - it('Build fails on invalid build target', function (done) { - this.timeout(420000); - sh.exec(`${ngBin} build --target=potato`, (code) => { - expect(code).to.not.equal(0); - done(); - }); - }); - - it('Build fails on invalid environment file', function (done) { - this.timeout(420000); - sh.exec(`${ngBin} build --environment=potato`, (code) => { - expect(code).to.not.equal(0); - done(); - }); - }); - - it('Supports base tag modifications via `ng build --base-href`', function() { - this.timeout(420000); - - sh.exec(`${ngBin} build --base-href /myUrl/`); - const indexHtmlPath = path.join(process.cwd(), 'dist/index.html'); - const indexHtml = fs.readFileSync(indexHtmlPath, { encoding: 'utf8' }); - - expect(indexHtml).to.match(/ { - throw new Error('Build failed.'); - }) - .then(function () { - expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); - // Check the index.html to have no handlebar tokens in it. - const indexHtml = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8'); - expect(indexHtml).to.include('main.bundle.js'); - }) - .then(function () { - // Also does not create new things in GIT. - expect(sh.exec('git status --porcelain').output).to.be.equal(undefined); - }); - }); - - it('Build pack output files into a different folder', function () { - this.timeout(420000); - - return ng(['build', '-o', './build-output']) - .catch(() => { - throw new Error('Build failed.'); - }) - .then(function () { - expect(existsSync(path.join(process.cwd(), './build-output'))).to.be.equal(true); - }); - }); - - it_mobile('Does not include mobile prod features', () => { - let index = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8'); - // Service Worker - expect(index.includes('if (\'serviceWorker\' in navigator) {')).to.be.equal(false); - expect(existsSync(path.join(process.cwd(), 'dist/worker.js'))).to.be.equal(false); - - // Asynchronous bundle - expect(index.includes('')).to.be.equal(false); - expect(existsSync(path.join(process.cwd(), 'dist/app-concat.js'))).to.be.equal(false); - }); - - it('lints', () => { - this.timeout(420000); - - return ng(['lint']).then(() => { - }) - .catch(err => { - throw new Error('Linting failed: ' + err); - }); - }); - - it('Perform `ng test` after initial build', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - it('ng e2e fails with error exit code', function () { - this.timeout(240000); - - function executor(resolve, reject) { - child_process.exec(`${ngBin} e2e`, (error, stdout, stderr) => { - if (error !== null) { - resolve(stderr); - } else { - reject(stdout); - } - }); - } - - return new Promise(executor) - .catch((msg) => { - throw new Error(msg); - }); - }); - - it('Can create a test component using `ng generate component test-component`', function () { - this.timeout(10000); - return ng(['generate', 'component', 'test-component']).then(function () { - var componentDir = path.join(process.cwd(), 'src', 'app', 'test-component'); - expect(existsSync(componentDir)).to.be.equal(true); - expect(existsSync(path.join(componentDir, 'test-component.component.ts'))).to.be.equal(true); - expect(existsSync(path.join(componentDir, 'test-component.component.html'))).to.be.equal(true); - expect(existsSync(path.join(componentDir, 'test-component.component.css'))).to.be.equal(true); - }); - }); - - it('Perform `ng test` after adding a component', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - it('Can create a test service using `ng generate service test-service`', function () { - return ng(['generate', 'service', 'test-service']).then(function () { - var serviceDir = path.join(process.cwd(), 'src', 'app'); - expect(existsSync(serviceDir)).to.be.equal(true); - expect(existsSync(path.join(serviceDir, 'test-service.service.ts'))).to.be.equal(true); - expect(existsSync(path.join(serviceDir, 'test-service.service.spec.ts'))).to.be.equal(true); - }); - }); - - it('Perform `ng test` after adding a service', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - it('Can create a test pipe using `ng generate pipe test-pipe`', function () { - return ng(['generate', 'pipe', 'test-pipe']).then(function () { - var pipeDir = path.join(process.cwd(), 'src', 'app'); - expect(existsSync(pipeDir)).to.be.equal(true); - expect(existsSync(path.join(pipeDir, 'test-pipe.pipe.ts'))).to.be.equal(true); - expect(existsSync(path.join(pipeDir, 'test-pipe.pipe.spec.ts'))).to.be.equal(true); - }); - }); - - it('Perform `ng test` after adding a pipe', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - xit('Can create a test route using `ng generate route test-route`', function () { - return ng(['generate', 'route', 'test-route']).then(function () { - var routeDir = path.join(process.cwd(), 'src', 'app', '+test-route'); - expect(existsSync(routeDir)).to.be.equal(true); - expect(existsSync(path.join(routeDir, 'test-route.component.ts'))).to.be.equal(true); - }); - }); - - xit('Perform `ng test` after adding a route', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - it('Can create a test interface using `ng generate interface test-interface model`', function () { - return ng(['generate', 'interface', 'test-interface', 'model']).then(function () { - var interfaceDir = path.join(process.cwd(), 'src', 'app'); - expect(existsSync(interfaceDir)).to.be.equal(true); - expect(existsSync(path.join(interfaceDir, 'test-interface.model.ts'))).to.be.equal(true); - }); - }); - - it('Perform `ng test` after adding a interface', function () { - this.timeout(420000); - - return ng(testArgs).then(function (result) { - const exitCode = typeof result === 'object' ? result.exitCode : result; - expect(exitCode).to.be.equal(0); - }); - }); - - it('Make sure the correct coverage folder is created', function () { - const coverageReport = path.join(process.cwd(), 'coverage', 'src', 'app'); - - expect(existsSync(coverageReport)).to.be.equal(true); - }); - - it('Make sure that LCOV file is generated inside coverage folder', function() { - const lcovReport = path.join(process.cwd(), 'coverage', 'coverage.lcov'); - - expect(existsSync(lcovReport)).to.be.equal(true); - }); - - it('moves all files that live inside `assets` into `dist`', function () { - this.timeout(420000); - - const dotFile = path.join(process.cwd(), 'src', 'assets', '.file'); - const distDotFile = path.join(process.cwd(), 'dist', 'assets', '.file'); - fs.writeFileSync(dotFile, ''); - const testFile = path.join(process.cwd(), 'src', 'assets', 'test.abc'); - const distTestFile = path.join(process.cwd(), 'dist', 'assets', 'test.abc'); - fs.writeFileSync(testFile, 'hello world'); - const distDotGitkeep = path.join(process.cwd(), 'dist', 'assets', '.gitkeep'); - - sh.exec(`${ngBin} build`); - expect(existsSync(distDotFile)).to.be.equal(true); - expect(existsSync(distTestFile)).to.be.equal(true); - expect(existsSync(distDotGitkeep)).to.be.equal(false); - }); - - // Mobile mode doesn't have styles - it_not_mobile('Supports scss in styleUrls', function() { - this.timeout(420000); - - let cssFilename = 'app.component.css'; - let scssFilename = 'app.component.scss'; - let componentPath = path.join(process.cwd(), 'src', 'app'); - let componentFile = path.join(componentPath, 'app.component.ts'); - let cssFile = path.join(componentPath, cssFilename); - let scssFile = path.join(componentPath, scssFilename); - let scssExample = '@import "https://github.com/app.component.partial";\n\n.outer {\n .inner { background: #fff; }\n }'; - let scssPartialFile = path.join(componentPath, '_app.component.partial.scss'); - let scssPartialExample = '.partial {\n @extend .outer;\n }'; - let componentContents = fs.readFileSync(componentFile, 'utf8'); - - sh.mv(cssFile, scssFile); - fs.writeFileSync(scssFile, scssExample, 'utf8'); - fs.writeFileSync(scssPartialFile, scssPartialExample, 'utf8'); - fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), scssFilename), 'utf8'); - - sh.exec(`${ngBin} build`); - let destCssBundle = path.join(process.cwd(), 'dist', 'main.bundle.js'); - let contents = fs.readFileSync(destCssBundle, 'utf8'); - expect(contents).to.include('.outer .inner'); - expect(contents).to.include('.partial .inner'); - - sh.mv(scssFile, cssFile); - fs.writeFileSync(cssFile, '', 'utf8'); - fs.writeFileSync(componentFile, componentContents, 'utf8'); - sh.rm('-f', scssPartialFile); - }); - - it_not_mobile('Supports sass in styleUrls', function() { - this.timeout(420000); - - let cssFilename = 'app.component.css'; - let sassFilename = 'app.component.sass'; - let componentPath = path.join(process.cwd(), 'src', 'app'); - let componentFile = path.join(componentPath, 'app.component.ts'); - let cssFile = path.join(componentPath, cssFilename); - let sassFile = path.join(componentPath, sassFilename); - let sassExample = '@import "https://github.com/app.component.partial";\n\n.outer\n .inner\n background: #fff'; - let sassPartialFile = path.join(componentPath, '_app.component.partial.sass'); - let sassPartialExample = '.partial\n @extend .outer'; - let componentContents = fs.readFileSync(componentFile, 'utf8'); - - sh.mv(cssFile, sassFile); - fs.writeFileSync(sassFile, sassExample, 'utf8'); - fs.writeFileSync(sassPartialFile, sassPartialExample, 'utf8'); - fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), sassFilename), 'utf8'); - - sh.exec(`${ngBin} build`); - let destCssBundle = path.join(process.cwd(), 'dist', 'main.bundle.js'); - let contents = fs.readFileSync(destCssBundle, 'utf8'); - expect(contents).to.include('.outer .inner'); - expect(contents).to.include('.partial .inner'); - - sh.mv(sassFile, cssFile); - fs.writeFileSync(cssFile, '', 'utf8'); - fs.writeFileSync(componentFile, componentContents, 'utf8'); - sh.rm('-f', sassPartialFile); - }); - - // Mobile mode doesn't have styles - it_not_mobile('Supports less in styleUrls', function() { - this.timeout(420000); - - let cssFilename = 'app.component.css'; - let lessFilename = 'app.component.less'; - let componentPath = path.join(process.cwd(), 'src', 'app'); - let componentFile = path.join(componentPath, 'app.component.ts'); - let cssFile = path.join(componentPath, cssFilename); - let lessFile = path.join(componentPath, lessFilename); - let lessExample = '.outer {\n .inner { background: #fff; }\n }'; - let componentContents = fs.readFileSync(componentFile, 'utf8'); - - sh.mv(cssFile, lessFile); - fs.writeFileSync(lessFile, lessExample, 'utf8'); - fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), lessFilename), 'utf8'); - - sh.exec(`${ngBin} build`); - let destCssBundle = path.join(process.cwd(), 'dist', 'main.bundle.js'); - let contents = fs.readFileSync(destCssBundle, 'utf8'); - expect(contents).to.include('.outer .inner'); - - fs.writeFileSync(lessFile, '', 'utf8'); - sh.mv(lessFile, cssFile); - fs.writeFileSync(componentFile, componentContents, 'utf8'); - }); - - // Mobile mode doesn't have styles - it_not_mobile('Supports stylus in styleUrls', function() { - this.timeout(420000); - - let cssFilename = 'app.component.css'; - let stylusFilename = 'app.component.scss'; - let componentPath = path.join(process.cwd(), 'src', 'app'); - let componentFile = path.join(componentPath, 'app.component.ts'); - let cssFile = path.join(componentPath, cssFilename); - let stylusFile = path.join(componentPath, stylusFilename); - let stylusExample = '.outer {\n .inner { background: #fff; }\n }'; - let componentContents = fs.readFileSync(componentFile, 'utf8'); - - sh.mv(cssFile, stylusFile); - fs.writeFileSync(stylusFile, stylusExample, 'utf8'); - fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), stylusFilename), 'utf8'); - - sh.exec(`${ngBin} build`); - let destCssBundle = path.join(process.cwd(), 'dist', 'main.bundle.js'); - let contents = fs.readFileSync(destCssBundle, 'utf8'); - expect(contents).to.include('.outer .inner'); - - fs.writeFileSync(stylusFile, '', 'utf8'); - sh.mv(stylusFile, cssFile); - fs.writeFileSync(componentFile, componentContents, 'utf8'); - }); - - it('Turn on `noImplicitAny` in tsconfig.json and rebuild', function () { - this.timeout(420000); - - const configFilePath = path.join(process.cwd(), 'src', 'tsconfig.json'); - let config = require(configFilePath); - - config.compilerOptions.noImplicitAny = true; - fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), 'utf8'); - - sh.rm('-rf', path.join(process.cwd(), 'dist')); - - sh.exec(`${ngBin} build`); - expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); - }); - - it('Turn on path mapping in tsconfig.json and rebuild', function () { - this.timeout(420000); - - const configFilePath = path.join(process.cwd(), 'src', 'tsconfig.json'); - let config = require(configFilePath); - - config.compilerOptions.baseUrl = ''; - - // #TODO: When https://github.com/Microsoft/TypeScript/issues/9772 is fixed this should fail. - config.compilerOptions.paths = { '@angular/*': [] }; - fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), 'utf8'); - - sh.exec(`${ngBin} build`); - // #TODO: Uncomment these lines when https://github.com/Microsoft/TypeScript/issues/9772 is fixed. - // .catch(() => { - // return true; - // }) - // .then((passed) => { - // expect(passed).to.equal(true); - // }) - - // This should succeed. - config.compilerOptions.paths = { - '@angular/*': [ '../node_modules/@angular/*' ] - }; - fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), 'utf8'); - sh.exec(`${ngBin} build`); - - expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); - const indexHtml = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8'); - expect(indexHtml).to.include('main.bundle.js'); - }); - - it('styles.css is added to styles bundle', function() { - this.timeout(420000); - - let stylesPath = path.join(process.cwd(), 'src', 'styles.css'); - let testStyle = 'body { background-color: blue; }'; - fs.writeFileSync(stylesPath, testStyle, 'utf8'); - - sh.exec(`${ngBin} build`); - - var stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js'); - var stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' }); - - expect(stylesBundleContent.includes(testStyle)).to.be.equal(true); - }); - - it('styles.css supports css imports', function() { - this.timeout(420000); - - let importedStylePath = path.join(process.cwd(), 'src', 'imported-styles.css'); - let testStyle = 'body { background-color: blue; }'; - fs.writeFileSync(importedStylePath, testStyle, 'utf8'); - - let stylesPath = path.join(process.cwd(), 'src', 'styles.css'); - let importStyle = '@import \'./imported-styles.css\';'; - fs.writeFileSync(stylesPath, importStyle, 'utf8'); - - sh.exec(`${ngBin} build`); - - var stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js'); - var stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' }); - - expect(stylesBundleContent).to.include(testStyle); - }); - - it('build supports global styles and scripts', function() { - this.timeout(420000); - - sh.exec('npm install bootstrap@next', { silent: true }); - - const configFile = path.join(process.cwd(), 'angular-cli.json'); - let originalConfigContent = fs.readFileSync(configFile, { encoding: 'utf8' }); - let configContent = originalConfigContent.replace('"styles.css"', ` - "styles.css", - "../node_modules/bootstrap/dist/css/bootstrap.css" - `).replace('"scripts": [],',` - "scripts": [ - "../node_modules/jquery/dist/jquery.js", - "../node_modules/tether/dist/js/tether.js", - "../node_modules/bootstrap/dist/js/bootstrap.js" - ], - `); - - fs.writeFileSync(configFile, configContent, 'utf8'); - - sh.exec(`${ngBin} build`); - - // checking for strings that are part of the included files - const stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js'); - const stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' }); - expect(stylesBundleContent).to.include('* Bootstrap '); - - const scriptsBundlePath = path.join(process.cwd(), 'dist', 'scripts.bundle.js'); - const scriptsBundleContent = fs.readFileSync(scriptsBundlePath, { encoding: 'utf8' }); - expect(scriptsBundleContent).to.include('* jQuery JavaScript'); - expect(scriptsBundleContent).to.include('/*! tether '); - expect(scriptsBundleContent).to.include('* Bootstrap '); - - // check the scripts are loaded in the correct order - const indexPath = path.join(process.cwd(), 'dist', 'index.html'); - const indexContent = fs.readFileSync(indexPath, { encoding: 'utf8' }); - let scriptTags = '' + - '' + - '' + - '' - expect(indexContent).to.include(scriptTags); - - // restore config - fs.writeFileSync(configFile, originalConfigContent, 'utf8'); - }); - - it('Serve and run e2e tests on dev environment', function () { - this.timeout(240000); - - var ngServePid; - - function executor(resolve, reject) { - var serveProcess = child_process.exec(`${ngBin} serve`, { maxBuffer: 500*1024 }); - var startedProtractor = false; - ngServePid = serveProcess.pid; - - serveProcess.stdout.on('data', (data) => { - if (/webpack: bundle is now VALID/.test(data.toString('utf-8')) && !startedProtractor) { - startedProtractor = true; - child_process.exec(`${ngBin} e2e`, (error, stdout, stderr) => { - if (error !== null) { - reject(stderr) - } else { - resolve(); - } - }); - } - }); - - serveProcess.stderr.on('data', (data) => { - reject(data); - }); - serveProcess.on('close', (code) => { - code === 0 ? resolve() : reject('ng serve command closed with error') - }); - } - - return new Promise(executor) - .then(() => { - if (ngServePid) treeKill(ngServePid); - }) - .catch((msg) => { - if (ngServePid) treeKill(ngServePid); - throw new Error(msg); - }); - }); - - it('Serve and run e2e tests on prod environment', function () { - this.timeout(240000); - - var ngServePid; - - function executor(resolve, reject) { - var serveProcess = child_process.exec(`${ngBin} serve -e=prod`, { maxBuffer: 500*1024 }); - var startedProtractor = false; - ngServePid = serveProcess.pid; - - serveProcess.stdout.on('data', (data) => { - if (/webpack: bundle is now VALID/.test(data.toString('utf-8')) && !startedProtractor) { - startedProtractor = true; - child_process.exec(`${ngBin} e2e`, (error, stdout, stderr) => { - if (error !== null) { - reject(stderr) - } else { - resolve(); - } - }); - } - }); - - serveProcess.stderr.on('data', (data) => { - reject(data); - }); - serveProcess.on('close', (code) => { - code === 0 ? resolve() : reject('ng serve command closed with error') - }); - } - - return new Promise(executor) - .then(() => { - if (ngServePid) treeKill(ngServePid); - }) - .catch((msg) => { - if (ngServePid) treeKill(ngServePid); - throw new Error(msg); - }); - }); - - it('Serve with proxy config', function () { - this.timeout(240000); - var ngServePid; - var server; - - function executor(resolve, reject) { - var startedProtractor = false; - var app = express(); - server = http.createServer(app); - server.listen(); - app.set('port', server.address().port); - - app.get('/api/test', function (req, res) { - res.send('TEST_API_RETURN'); - }); - var backendHost = 'localhost'; - var backendPort = server.address().port - - var proxyServerUrl = `http://${backendHost}:${backendPort}`; - const proxyConfigFile = path.join(process.cwd(), 'proxy.config.json'); - const proxyConfig = { - '/api/*': { - target: proxyServerUrl - } - }; - fs.writeFileSync(proxyConfigFile, JSON.stringify(proxyConfig, null, 2), 'utf8'); - var serveProcess = child_process.exec(`${ngBin} serve --proxy-config proxy.config.json`, { maxBuffer: 500 * 1024 }); - ngServePid = serveProcess.pid; - - serveProcess.stdout.on('data', (data) => { - if (/webpack: bundle is now VALID/.test(data.toString('utf-8')) && !startedProtractor) { - - // How to get the url with out hardcoding here? - request( 'http://localhost:4200/api/test', function(err, response, body) { - expect(response.statusCode).to.be.equal(200); - expect(body).to.be.equal('TEST_API_RETURN'); - resolve(); - }); - } - }); - - serveProcess.stderr.on('data', (data) => { - reject(data); - }); - serveProcess.on('close', (code) => { - code === 0 ? resolve() : reject('ng serve command closed with error') - }); - } - - // Need a way to close the express server - return new Promise(executor) - .then(() => { - if (ngServePid) treeKill(ngServePid); - if(server){ - server.close(); - } - }) - .catch((msg) => { - if (ngServePid) treeKill(ngServePid); - if(server){ - server.close(); - } - throw new Error(msg); - }); - }); - - it('Serve fails on invalid proxy config file', function (done) { - this.timeout(420000); - sh.exec(`${ngBin} serve --proxy-config proxy.config.does_not_exist.json`, (code) => { - expect(code).to.not.equal(0); - done(); - }); - }); - -}); - -function isMobileTest() { - return !!process.env['MOBILE_TEST']; -} diff --git a/tests/e2e/setup/000-npm-link.ts b/tests/e2e/setup/000-npm-link.ts new file mode 100644 index 000000000000..d5c57610352f --- /dev/null +++ b/tests/e2e/setup/000-npm-link.ts @@ -0,0 +1,13 @@ +import {join} from 'path'; + +import {npm, exec} from '../utils/process'; + + +export default function (argv: any) { + // Setup to use the local angular-cli copy. + process.chdir(join(__dirname, '../..')); + + return Promise.resolve() + .then(() => argv.nolink || npm('link')) + .then(() => exec(process.platform.startsWith('win') ? 'where' : 'which', 'ng')); +} diff --git a/tests/e2e/setup/010-create-tmp-dir.ts b/tests/e2e/setup/010-create-tmp-dir.ts new file mode 100644 index 000000000000..e1a6dbfffc91 --- /dev/null +++ b/tests/e2e/setup/010-create-tmp-dir.ts @@ -0,0 +1,9 @@ +const temp = require('temp'); + + +export default function(argv: any) { + // Get to a temporary directory. + let tempRoot = argv.reuse || temp.mkdirSync('angular-cli-e2e-'); + console.log(` Using "${tempRoot}" as temporary directory for a new project.`); + process.chdir(tempRoot); +} diff --git a/tests/e2e/setup/020-create-project.ts b/tests/e2e/setup/020-create-project.ts new file mode 100644 index 000000000000..f3df387a5443 --- /dev/null +++ b/tests/e2e/setup/020-create-project.ts @@ -0,0 +1,50 @@ +import {join} from 'path'; +import {git, ng, npm} from '../utils/process'; +import {isMobileTest} from '../utils/utils'; +import {expectFileToExist} from '../utils/fs'; +import {updateTsConfig} from '../utils/project'; +import {gitClean, gitCommit} from '../utils/git'; + + +export default function(argv: any) { + let createProject = null; + + // If we're set to reuse an existing project, just chdir to it and clean it. + if (argv.reuse) { + createProject = Promise.resolve() + .then(() => process.chdir(argv.reuse)) + .then(() => gitClean()); + } else { + // Otherwise create a project from scratch. + createProject = Promise.resolve() + .then(() => ng('new', 'test-project', '--link-cli', isMobileTest() ? '--mobile' : undefined)) + .then(() => expectFileToExist(join(process.cwd(), 'test-project'))) + .then(() => process.chdir('./test-project')); + } + + if (argv.nightly) { + // Install over the project with nightly builds. + const angularPackages = [ + 'core', + 'common', + 'compiler', + 'forms', + 'http', + 'router', + 'platform-browser', + 'platform-browser-dynamic' + ]; + createProject = createProject + .then(() => npm('install', ...angularPackages.map(name => `angular/${name}-builds`))); + } + + return Promise.resolve() + .then(() => createProject) + // Force sourcemaps to be from the root of the filesystem. + .then(() => updateTsConfig(json => { + json['compilerOptions']['sourceRoot'] = '/'; + })) + .then(() => git('config', 'user.email', 'angular-core+e2e@google.com')) + .then(() => git('config', 'user.name', 'Angular CLI E2e')) + .then(() => gitCommit('tsconfig-e2e-update')); +} diff --git a/tests/e2e/tests/build/base-href.ts b/tests/e2e/tests/build/base-href.ts new file mode 100644 index 000000000000..c8ff910a1a20 --- /dev/null +++ b/tests/e2e/tests/build/base-href.ts @@ -0,0 +1,8 @@ +import {ng} from '../../utils/process'; +import {expectFileToMatch} from '../../utils/fs'; + + +export default function() { + return ng('build', '--base-href', '/myUrl') + .then(() => expectFileToMatch('dist/index.html', //)); +} diff --git a/tests/e2e/tests/build/dev-build.ts b/tests/e2e/tests/build/dev-build.ts new file mode 100644 index 000000000000..f6f96df9deb1 --- /dev/null +++ b/tests/e2e/tests/build/dev-build.ts @@ -0,0 +1,10 @@ +import {ng} from '../../utils/process'; +import {expectFileToMatch} from '../../utils/fs'; +import {expectGitToBeClean} from '../../utils/git'; + + +export default function() { + return ng('build', '--env=dev') + .then(() => expectFileToMatch('dist/index.html', 'main.bundle.js')) + .then(() => expectGitToBeClean()); +} diff --git a/tests/e2e/tests/build/environment.ts b/tests/e2e/tests/build/environment.ts new file mode 100644 index 000000000000..734461a2e0a5 --- /dev/null +++ b/tests/e2e/tests/build/environment.ts @@ -0,0 +1,18 @@ +import {ng} from '../../utils/process'; +import {expectFileToMatch} from '../../utils/fs'; +import {expectGitToBeClean} from '../../utils/git'; +import {expectToFail} from '../../utils/utils'; + + +export default function() { + // Try a prod build. + return ng('build', '--env=prod') + .then(() => expectFileToMatch('dist/main.bundle.js', 'production: true')) + .then(() => expectGitToBeClean()) + + // Build fails on invalid build target + .then(() => expectToFail(() => ng('build', '--target=potato'))) + + // This is a valid target. + .then(() => ng('build', '--target=production')); +} diff --git a/tests/e2e/tests/build/no-implicit-any.ts b/tests/e2e/tests/build/no-implicit-any.ts new file mode 100644 index 000000000000..08927a41467e --- /dev/null +++ b/tests/e2e/tests/build/no-implicit-any.ts @@ -0,0 +1,10 @@ +import {updateTsConfig} from '../../utils/project'; +import {ng} from '../../utils/process'; + + +export default function() { + return updateTsConfig(json => { + json['compilerOptions']['noImplicitAny'] = true; + }) + .then(() => ng('build')); +} diff --git a/tests/e2e/tests/build/output-dir.ts b/tests/e2e/tests/build/output-dir.ts new file mode 100644 index 000000000000..837ee7f49ed0 --- /dev/null +++ b/tests/e2e/tests/build/output-dir.ts @@ -0,0 +1,12 @@ +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; +import {expectToFail} from '../../utils/utils'; +import {expectGitToBeClean} from '../../utils/git'; + + +export default function() { + return ng('build', '-o', './build-output') + .then(() => expectFileToExist('./build-output/index.html')) + .then(() => expectFileToExist('./build-output/main.bundle.js')) + .then(() => expectToFail(expectGitToBeClean)); +} diff --git a/tests/e2e/tests/build/prod-build.ts b/tests/e2e/tests/build/prod-build.ts new file mode 100644 index 000000000000..fe0699af4356 --- /dev/null +++ b/tests/e2e/tests/build/prod-build.ts @@ -0,0 +1,41 @@ +import {join} from 'path'; +import {isMobileTest} from '../../utils/utils'; +import {expectFileToExist, expectFileToMatch} from '../../utils/fs'; +import {ng} from '../../utils/process'; +import {expectGitToBeClean} from '../../utils/git'; + + + +function mobileOnlyChecks() { + if (!isMobileTest()) { + return; + } + + // Check for mobile-specific features in prod builds. + return Promise.resolve() + // Service Worker + .then(() => expectFileToExist('dist/sw.js')) + .then(() => expectFileToMatch('dist/index.html', /sw-install\.[0-9a-f]{20}\.bundle\.js/)) + + // App Manifest + .then(() => expectFileToExist('dist/manifest.webapp')) + .then(() => expectFileToMatch('dist/index.html', + '')) + + // Icons folder + .then(() => expectFileToExist('dist/icons')); +} + + +export default function() { + // Can't use the `ng` helper because somewhere the environment gets + // stuck to the first build done + return ng('build', '--prod') + .then(() => expectFileToExist(join(process.cwd(), 'dist'))) + // Check for cache busting hash script src + .then(() => expectFileToMatch('dist/index.html', /main\.[0-9a-f]{20}\.bundle\.js/)) + + // Check that the process didn't change local files. + .then(() => expectGitToBeClean()) + .then(() => mobileOnlyChecks()); +} diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts new file mode 100644 index 000000000000..aa081ef1f45c --- /dev/null +++ b/tests/e2e/tests/build/styles/css.ts @@ -0,0 +1,19 @@ +import {writeMultipleFiles, expectFileToMatch} from '../../../utils/fs'; +import {ng} from '../../../utils/process'; + + +export default function() { + return writeMultipleFiles({ + 'src/styles.css': ` + @import 'https://github.com/imported-styles.css'; + + body { background-color: blue; } + `, + 'src/imported-styles.css': ` + p { background-color: red; } + ` + }) + .then(() => ng('build')) + .then(() => expectFileToMatch('dist/styles.bundle.js', 'body { background-color: blue; }')) + .then(() => expectFileToMatch('dist/styles.bundle.js', 'p { background-color: red; }')); +} diff --git a/tests/e2e/tests/build/styles/less.ts b/tests/e2e/tests/build/styles/less.ts new file mode 100644 index 000000000000..840d0bde2483 --- /dev/null +++ b/tests/e2e/tests/build/styles/less.ts @@ -0,0 +1,33 @@ +import { + writeMultipleFiles, + deleteFile, + expectFileToMatch, + moveFile, + replaceInFile +} from '../../../utils/fs'; +import {ng} from '../../../utils/process'; +import {stripIndents} from 'common-tags'; +import {isMobileTest} from '../../../utils/utils'; + + +export default function() { + if (isMobileTest()) { + return; + } + + return writeMultipleFiles({ + 'src/app/app.component.less': stripIndents` + .outer { + .inner { + background: #fff; + } + } + ` + }) + .then(() => deleteFile('src/app/app.component.css')) + .then(() => replaceInFile('src/app/app.component.ts', + './app.component.css', './app.component.less')) + .then(() => ng('build')) + .then(() => expectFileToMatch('dist/main.bundle.js', '.outer .inner')) + .then(() => moveFile('src/app/app.component.less', 'src/app/app.component.css')); +} diff --git a/tests/e2e/tests/build/styles/scss.ts b/tests/e2e/tests/build/styles/scss.ts new file mode 100644 index 000000000000..48b4792312a4 --- /dev/null +++ b/tests/e2e/tests/build/styles/scss.ts @@ -0,0 +1,41 @@ +import { + writeMultipleFiles, + deleteFile, + expectFileToMatch, + moveFile, + replaceInFile +} from '../../../utils/fs'; +import {ng} from '../../../utils/process'; +import {stripIndents} from 'common-tags'; +import {isMobileTest} from '../../../utils/utils'; + + +export default function() { + if (isMobileTest()) { + return; + } + + return writeMultipleFiles({ + 'src/app/app.component.scss': stripIndents` + @import "https://github.com/app.component.partial"; + + .outer { + .inner { + background: #def; + } + } + `, + 'src/app/app.component.partial.scss': stripIndents` + .partial { + @extend .outer; + } + ` + }) + .then(() => deleteFile('src/app/app.component.css')) + .then(() => replaceInFile('src/app/app.component.ts', + './app.component.css', './app.component.scss')) + .then(() => ng('build')) + .then(() => expectFileToMatch('dist/main.bundle.js', '.outer .inner')) + .then(() => expectFileToMatch('dist/main.bundle.js', '.partial .inner')) + .then(() => moveFile('src/app/app.component.scss', 'src/app/app.component.css')); +} diff --git a/tests/e2e/tests/generate/component.ts b/tests/e2e/tests/generate/component.ts new file mode 100644 index 000000000000..251c14ebcc4e --- /dev/null +++ b/tests/e2e/tests/generate/component.ts @@ -0,0 +1,18 @@ +import {join} from 'path'; +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; + + +export default function() { + const componentDir = join('src', 'app', 'test-component'); + + return ng('generate', 'component', 'test-component') + .then(() => expectFileToExist(componentDir)) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.html'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/generate/interface.ts b/tests/e2e/tests/generate/interface.ts new file mode 100644 index 000000000000..e74f2570f4f1 --- /dev/null +++ b/tests/e2e/tests/generate/interface.ts @@ -0,0 +1,15 @@ +import {join} from 'path'; +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; + + +export default function() { + const interfaceDir = join('src', 'app'); + + return ng('generate', 'interface', 'test-interface', 'model') + .then(() => expectFileToExist(interfaceDir)) + .then(() => expectFileToExist(join(interfaceDir, 'test-interface.model.ts'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/generate/module/module-basic.ts b/tests/e2e/tests/generate/module/module-basic.ts new file mode 100644 index 000000000000..4e7aa59b3d27 --- /dev/null +++ b/tests/e2e/tests/generate/module/module-basic.ts @@ -0,0 +1,21 @@ +import {join} from 'path'; +import {ng} from '../../../utils/process'; +import {expectFileToExist} from '../../../utils/fs'; +import {expectToFail} from '../../../utils/utils'; + + +export default function() { + const moduleDir = join('src', 'app', 'test-module'); + + return ng('generate', 'module', 'test-module') + .then(() => expectFileToExist(moduleDir)) + .then(() => expectFileToExist(join(moduleDir, 'test-module.module.ts'))) + .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test-module.routing.ts')))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.spec.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.html'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.css'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/generate/module/module-routing.ts b/tests/e2e/tests/generate/module/module-routing.ts new file mode 100644 index 000000000000..d339ebf01740 --- /dev/null +++ b/tests/e2e/tests/generate/module/module-routing.ts @@ -0,0 +1,20 @@ +import {join} from 'path'; +import {ng} from '../../../utils/process'; +import {expectFileToExist} from '../../../utils/fs'; + + +export default function() { + const moduleDir = join('src', 'app', 'test-module'); + + return ng('generate', 'module', 'test-module', '--routing') + .then(() => expectFileToExist(moduleDir)) + .then(() => expectFileToExist(join(moduleDir, 'test-module.module.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.routing.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.spec.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.html'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module.component.css'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/generate/pipe.ts b/tests/e2e/tests/generate/pipe.ts new file mode 100644 index 000000000000..bc0cc0f02bea --- /dev/null +++ b/tests/e2e/tests/generate/pipe.ts @@ -0,0 +1,17 @@ +import {join} from 'path'; +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; + + +export default function() { + // Create the pipe in the same directory. + const pipeDir = join('src', 'app'); + + return ng('generate', 'pipe', 'test-pipe') + .then(() => expectFileToExist(pipeDir)) + .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.ts'))) + .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.spec.ts'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/generate/service.ts b/tests/e2e/tests/generate/service.ts new file mode 100644 index 000000000000..938ed7bd0cf2 --- /dev/null +++ b/tests/e2e/tests/generate/service.ts @@ -0,0 +1,17 @@ +import {join} from 'path'; +import {ng} from '../../utils/process'; +import {expectFileToExist} from '../../utils/fs'; + + +export default function() { + // Does not create a sub directory. + const serviceDir = join('src', 'app'); + + return ng('generate', 'service', 'test-service') + .then(() => expectFileToExist(serviceDir)) + .then(() => expectFileToExist(join(serviceDir, 'test-service.service.ts'))) + .then(() => expectFileToExist(join(serviceDir, 'test-service.service.spec.ts'))) + + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')); +} diff --git a/tests/e2e/tests/misc/assets.ts b/tests/e2e/tests/misc/assets.ts new file mode 100644 index 000000000000..aed01b53cf26 --- /dev/null +++ b/tests/e2e/tests/misc/assets.ts @@ -0,0 +1,13 @@ +import {writeFile, expectFileToExist, expectFileToMatch} from '../../utils/fs'; +import {ng} from '../../utils/process'; +import {expectToFail} from '../../utils/utils'; + + +export default function() { + return writeFile('src/assets/.file', '') + .then(() => writeFile('src/assets/test.abc', 'hello world')) + .then(() => ng('build')) + .then(() => expectFileToExist('dist/assets/.file')) + .then(() => expectFileToMatch('dist/assets/test.abc', 'hello world')) + .then(() => expectToFail(() => expectFileToExist('dist/assets/.gitkeep'))); +} diff --git a/tests/e2e/tests/misc/coverage.ts b/tests/e2e/tests/misc/coverage.ts new file mode 100644 index 000000000000..42d4f6e1d805 --- /dev/null +++ b/tests/e2e/tests/misc/coverage.ts @@ -0,0 +1,9 @@ +import {expectFileToExist} from '../../utils/fs'; +import {ng} from '../../utils/process'; + + +export default function() { + return ng('test', '--watch=false') + .then(() => expectFileToExist('coverage/src/app')) + .then(() => expectFileToExist('coverage/coverage.lcov')); +} diff --git a/tests/e2e/tests/misc/lazy-module.ts b/tests/e2e/tests/misc/lazy-module.ts new file mode 100644 index 000000000000..9f25a42e663f --- /dev/null +++ b/tests/e2e/tests/misc/lazy-module.ts @@ -0,0 +1,31 @@ +import {readdirSync} from 'fs'; +import {oneLine} from 'common-tags'; + +import {ng} from '../../utils/process'; +import {addImportToModule} from '../../utils/ast'; + + +export default function(argv: any) { + /** This test is disabled when not on nightly. */ + if (!argv.nightly) { + return Promise.resolve(); + } + + let oldNumberOfFiles = 0; + let currentNumberOfDistFiles = 0; + + return Promise.resolve() + .then(() => ng('build')) + .then(() => oldNumberOfFiles = readdirSync('dist').length) + .then(() => ng('generate', 'module', 'lazy', '--routing')) + .then(() => addImportToModule('src/app/app.module.ts', oneLine` + RouterModule.forRoot([{ path: "lazy", loadChildren: "app/lazy/lazy.module#LazyModule" }]) + `, '@angular/router')) + .then(() => ng('build')) + .then(() => currentNumberOfDistFiles = readdirSync('dist').length) + .then(() => { + if (oldNumberOfFiles >= currentNumberOfDistFiles) { + throw new Error('A bundle for the lazy module was not created.'); + } + }); +} diff --git a/tests/e2e/tests/misc/proxy.ts b/tests/e2e/tests/misc/proxy.ts new file mode 100644 index 000000000000..26e058c863b3 --- /dev/null +++ b/tests/e2e/tests/misc/proxy.ts @@ -0,0 +1,46 @@ +import * as express from 'express'; +import * as http from 'http'; + +import {writeFile} from '../../utils/fs'; +import {request} from '../../utils/http'; +import {killAllProcesses, ng} from '../../utils/process'; +import {ngServe} from '../../utils/project'; +import {expectToFail} from '../../utils/utils'; + + +export default function() { + // Create an express app that serves as a proxy. + const app = express(); + const server = http.createServer(app); + server.listen(0); + + app.set('port', server.address().port); + app.get('/api/test', function (req, res) { + res.send('TEST_API_RETURN'); + }); + + const backendHost = 'localhost'; + const backendPort = server.address().port; + const proxyServerUrl = `http://${backendHost}:${backendPort}`; + const proxyConfigFile = 'proxy.config.json'; + const proxyConfig = { + '/api/*': { + target: proxyServerUrl + } + }; + + return Promise.resolve() + .then(() => writeFile(proxyConfigFile, JSON.stringify(proxyConfig, null, 2))) + .then(() => ngServe('--proxy', proxyConfigFile)) + .then(() => request('http://localhost:4200/api/test')) + .then(body => { + if (!body.match(/TEST_API_RETURN/)) { + throw new Error('Response does not match expected value.'); + } + }) + .then(() => server.close(), (err) => { server.close(); throw err; }) + .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) + + // A non-existing proxy file should error. + .then(() => expectToFail(() => ng('serve', '--proxy', 'proxy.non-existent.json'))); +} diff --git a/tests/e2e/tests/mobile/prod-features.ts b/tests/e2e/tests/mobile/prod-features.ts new file mode 100644 index 000000000000..e649c9a53f69 --- /dev/null +++ b/tests/e2e/tests/mobile/prod-features.ts @@ -0,0 +1,20 @@ +import {isMobileTest, expectToFail} from '../../utils/utils'; +import {expectFileToMatch, expectFileToExist} from '../../utils/fs'; + + +export default function() { + if (!isMobileTest()) { + return; + } + + return Promise.resolve() + // Service Worker + .then(() => expectToFail(() => expectFileToMatch('dist/index.html', + 'if (\'serviceWorker\' in navigator) {'))) + .then(() => expectToFail(() => expectFileToExist('dist/worker.js'))) + + // Asynchronous bundle + .then(() => expectToFail(() => expectFileToMatch('dist/index.html', + ''))) + .then(() => expectToFail(() => expectFileToExist('dist/app-concat.js'))); +} diff --git a/tests/e2e/tests/test/e2e.ts b/tests/e2e/tests/test/e2e.ts new file mode 100644 index 000000000000..b0e49f05286d --- /dev/null +++ b/tests/e2e/tests/test/e2e.ts @@ -0,0 +1,21 @@ +import {ng, killAllProcesses} from '../../utils/process'; +import {expectToFail} from '../../utils/utils'; +import {ngServe} from '../../utils/project'; + + +function _runServeAndE2e(...args: string[]) { + return ngServe(...args) + .then(() => ng('e2e')) + .then(() => killAllProcesses(), (err: any) => { + killAllProcesses(); + throw err; + }); +} + +export default function() { + // This is supposed to fail without serving first... + return expectToFail(() => ng('e2e')) + // These should work. + .then(() => _runServeAndE2e()) + .then(() => _runServeAndE2e('--prod')); +} diff --git a/tests/e2e/tests/test/lint.ts b/tests/e2e/tests/test/lint.ts new file mode 100644 index 000000000000..55242cc11cd2 --- /dev/null +++ b/tests/e2e/tests/test/lint.ts @@ -0,0 +1,6 @@ +import {ng} from '../../utils/process'; + + +export default function() { + return ng('lint'); +} diff --git a/tests/e2e/tests/test/test.ts b/tests/e2e/tests/test/test.ts new file mode 100644 index 000000000000..08fd2fc457c3 --- /dev/null +++ b/tests/e2e/tests/test/test.ts @@ -0,0 +1,6 @@ +import {ng} from '../../utils/process'; + + +export default function() { + return ng('test', '--watch=false'); +} diff --git a/tests/e2e/tests/third-party/bootstrap.ts b/tests/e2e/tests/third-party/bootstrap.ts new file mode 100644 index 000000000000..803bcb8e5d02 --- /dev/null +++ b/tests/e2e/tests/third-party/bootstrap.ts @@ -0,0 +1,30 @@ +import {npm, ng} from '../../utils/process'; +import {updateJsonFile} from '../../utils/project'; +import {expectFileToMatch} from '../../utils/fs'; +import {oneLineTrim} from 'common-tags'; + + +export default function() { + return Promise.resolve() + .then(() => npm('install', 'bootstrap@next')) + .then(() => updateJsonFile('angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['styles'].push('../node_modules/bootstrap/dist/css/bootstrap.css'); + app['scripts'].push( + '../node_modules/jquery/dist/jquery.js', + '../node_modules/tether/dist/js/tether.js', + '../node_modules/bootstrap/dist/js/bootstrap.js' + ); + })) + .then(() => ng('build')) + .then(() => expectFileToMatch('dist/scripts.bundle.js', '/*!\\n * jQuery JavaScript')) + .then(() => expectFileToMatch('dist/scripts.bundle.js', '/*! tether ')) + .then(() => expectFileToMatch('dist/scripts.bundle.js', '/*!\\n * Bootstrap')) + .then(() => expectFileToMatch('dist/styles.bundle.js', '/*!\\n * Bootstrap')) + .then(() => expectFileToMatch('dist/index.html', oneLineTrim` + + + + + `)); +} diff --git a/tests/e2e/utils/ast.ts b/tests/e2e/utils/ast.ts new file mode 100644 index 000000000000..d5a5376f9996 --- /dev/null +++ b/tests/e2e/utils/ast.ts @@ -0,0 +1,15 @@ +import { + insertImport as _insertImport, + addImportToModule as _addImportToModule +} from '@angular-cli/ast-tools'; + + +export function insertImport(file: string, symbol: string, module: string) { + return _insertImport(file, symbol, module) + .then(change => change.apply()); +} + +export function addImportToModule(file: string, symbol: string, module: string) { + return _addImportToModule(file, symbol, module) + .then(change => change.apply()); +} diff --git a/tests/e2e/utils/fs.ts b/tests/e2e/utils/fs.ts new file mode 100644 index 000000000000..5f42c98f1b93 --- /dev/null +++ b/tests/e2e/utils/fs.ts @@ -0,0 +1,96 @@ +import * as fs from 'fs'; + + +export function readFile(fileName: string) { + return new Promise((resolve, reject) => { + fs.readFile(fileName, 'utf-8', (err: any, data: string) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); +} + +export function writeFile(fileName: string, content: string) { + return new Promise((resolve, reject) => { + fs.writeFile(fileName, content, (err: any) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + + +export function deleteFile(path: string) { + return new Promise((resolve, reject) => { + fs.unlink(path, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + + +export function moveFile(from: string, to: string) { + return new Promise((resolve, reject) => { + fs.rename(from, to, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + + +export function writeMultipleFiles(fs: any) { + return Object.keys(fs) + .reduce((previous, curr) => { + return previous.then(() => writeFile(curr, fs[curr])); + }, Promise.resolve()); +} + + +export function replaceInFile(filePath: string, match: string, replacement: string); +export function replaceInFile(filePath: string, match: RegExp, replacement: string) { + return readFile(filePath) + .then((content: string) => writeFile(filePath, content.replace(match, replacement))); +} + + + +export function expectFileToExist(fileName: string) { + return new Promise((resolve, reject) => { + fs.exists(fileName, (exist) => { + if (exist) { + resolve(); + } else { + reject(new Error(`File ${fileName} was expected to exist but not found...`)); + } + }); + }); +} + +export function expectFileToMatch(fileName: string, regEx: RegExp | string) { + return readFile(fileName) + .then(content => { + if (typeof regEx == 'string') { + if (content.indexOf(regEx) == -1) { + throw new Error(`File "${fileName}" did not contain "${regEx}"...`); + } + } else { + if (!content.match(regEx)) { + throw new Error(`File "${fileName}" did not match regex ${regEx}...`); + } + } + }); +} diff --git a/tests/e2e/utils/git.ts b/tests/e2e/utils/git.ts new file mode 100644 index 000000000000..875ba385e549 --- /dev/null +++ b/tests/e2e/utils/git.ts @@ -0,0 +1,37 @@ +import {git, silentGit} from './process'; + + +export function gitClean() { + console.log(' Cleaning git...'); + return silentGit('clean', '-df') + .then(() => silentGit('reset', '--hard')) + .then(() => { + // Checkout missing files + return silentGit('status', '--porcelain') + .then(output => output + .split(/[\n\r]+/g) + .filter(line => line.match(/^ D/)) + .map(line => line.replace(/^\s*\S+\s+/, ''))) + .then(files => silentGit('checkout', ...files)); + }) + .then(() => expectGitToBeClean()); +} + +export function expectGitToBeClean() { + return git('status', '--porcelain') + .then(output => { + if (output != '') { + throw new Error('Git repo is not clean...'); + } + }); +} + +export function gitCommit(message: string) { + return git('add', '-A') + .then(() => git('status', '--porcelain')) + .then(output => { + if (output != '') { + return git('commit', '-am', message); + } + }); +} diff --git a/tests/e2e/utils/http.ts b/tests/e2e/utils/http.ts new file mode 100644 index 000000000000..565beaf4f3d5 --- /dev/null +++ b/tests/e2e/utils/http.ts @@ -0,0 +1,17 @@ +import {IncomingMessage} from 'http'; +import * as _request from 'request'; + + +export function request(url: string): Promise { + return new Promise((resolve, reject) => { + _request(url, (error: any, response: IncomingMessage, body: string) => { + if (error) { + reject(error); + } else if (response.statusCode >= 400) { + reject(new Error(`Requesting "${url}" returned status code ${response.statusCode}.`); + } else { + resolve(body); + } + }); + }); +} diff --git a/tests/e2e/utils/process.ts b/tests/e2e/utils/process.ts new file mode 100644 index 000000000000..3e4b8d875d1f --- /dev/null +++ b/tests/e2e/utils/process.ts @@ -0,0 +1,111 @@ +import * as child_process from 'child_process'; +import {blue, white, yellow} from 'chalk'; +const treeKill = require('tree-kill'); + + +interface ExecOptions { + silent?: boolean; + waitForMatch?: RegExp; +} + + +let _processes: child_process.ChildProcess[] = []; + +function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { + let stdout = ''; + const cwd = process.cwd(); + console.log(white( + ` ==========================================================================================` + )); + + args = args.filter(x => x !== undefined); + + console.log(blue(` Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`...`)); + console.log(blue(` CWD: ${cwd}`)); + const spawnOptions: any = {cwd}; + + if (process.platform.startsWith('win')) { + args.unshift('/c', cmd); + cmd = 'cmd.exe'; + spawnOptions['stdio'] = 'pipe'; + } + + const npmProcess = child_process.spawn(cmd, args, spawnOptions); + npmProcess.stdout.on('data', (data: Buffer) => { + stdout += data.toString('utf-8'); + if (options.silent) { + return; + } + data.toString('utf-8') + .split(/[\n\r]+/) + .filter(line => line !== '') + .forEach(line => console.log(' ' + line)); + }); + npmProcess.stderr.on('data', (data: Buffer) => { + if (options.silent) { + return; + } + data.toString('utf-8') + .split(/[\n\r]+/) + .filter(line => line !== '') + .forEach(line => console.error(yellow(' ' + line))); + }); + + _processes.push(npmProcess); + + // Create the error here so the stack shows who called this function. + const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `); + return new Promise((resolve, reject) => { + npmProcess.on('exit', (error: any) => { + _processes = _processes.filter(p => p !== npmProcess); + + if (!error) { + resolve(stdout); + } else { + err.message += `${error}...`; + reject(err); + } + }); + + if (options.waitForMatch) { + npmProcess.stdout.on('data', (data: Buffer) => { + if (data.toString().match(options.waitForMatch)) { + resolve(stdout); + } + }); + } + }); +} + +export function killAllProcesses(signal = 'SIGTERM') { + _processes.forEach(process => treeKill(process.pid, signal)); + _processes = []; +} + +export function exec(cmd: string, ...args: string[]) { + return _exec({}, cmd, args); +} + +export function execAndWaitForOutputToMatch(cmd: string, args: string[], match: RegExp) { + return _exec({ waitForMatch: match }, cmd, args); +} + +export function ng(...args: string[]) { + if (args[0] == 'build') { + return _exec({silent: true}, 'ng', args); + } else { + return _exec({}, 'ng', args); + } +} + +export function npm(...args: string[]) { + return _exec({}, 'npm', args); +} + +export function git(...args: string[]) { + return _exec({}, 'git', args); +} + +export function silentGit(...args: string[]) { + return _exec({silent: true}, 'git', args); +} diff --git a/tests/e2e/utils/project.ts b/tests/e2e/utils/project.ts new file mode 100644 index 000000000000..bd477309d6b3 --- /dev/null +++ b/tests/e2e/utils/project.ts @@ -0,0 +1,25 @@ +import {readFile, writeFile} from './fs'; +import {execAndWaitForOutputToMatch} from './process'; + +const tsConfigPath = 'src/tsconfig.json'; + + +export function updateJsonFile(filePath: string, fn: (json: any) => any | void) { + return readFile(filePath) + .then(tsConfigJson => { + const tsConfig = JSON.parse(tsConfigJson); + const result = fn(tsConfig) || tsConfig; + + return writeFile(filePath, JSON.stringify(result, null, 2)); + }); +} + + +export function updateTsConfig(fn: (json: any) => any | void) { + return updateJsonFile(tsConfigPath, fn); +} + + +export function ngServe(...args: string[]) { + return execAndWaitForOutputToMatch('ng', ['serve', ...args], /webpack: bundle is now VALID/); +} diff --git a/tests/e2e/utils/utils.ts b/tests/e2e/utils/utils.ts new file mode 100644 index 000000000000..f1c92420181b --- /dev/null +++ b/tests/e2e/utils/utils.ts @@ -0,0 +1,17 @@ + +export function expectToFail(fn: () => Promise): Promise { + return fn() + .then(() => { + throw new Error(`Function ${fn.source} was expected to fail, but succeeded.`); + }, () => {}); +} + +export function isMobileTest() { + return !!process.env['MOBILE_TEST']; +} + +export function wait(msecs: number) { + return new Promise((resolve) => { + setTimeout(resolve, msecs); + }); +} diff --git a/tests/e2e_runner.js b/tests/e2e_runner.js new file mode 100644 index 000000000000..d173b9080ad9 --- /dev/null +++ b/tests/e2e_runner.js @@ -0,0 +1,158 @@ +/*eslint-disable no-console */ +'use strict'; +require('../lib/bootstrap-local'); + +/** + * This file is ran using the command line, not using Jasmine / Mocha. + */ +const chalk = require('chalk'); +const gitClean = require('./e2e/utils/git').gitClean; +const glob = require('glob'); +const minimist = require('minimist'); +const path = require('path'); +const blue = chalk.blue; +const bold = chalk.bold; +const green = chalk.green; +const red = chalk.red; +const white = chalk.white; + + +/** + * Here's a short description of those flags: + * --debug If a test fails, block the thread so the temporary directory isn't deleted. + * --nolink Skip linking your local angular-cli directory. Can save a few seconds. + * --nightly Install angular nightly builds over the test project. + * --reuse=/path Use a path instead of create a new project. That project should have been + * created, and npm installed. Ideally you want a project created by a previous + * run of e2e. + * If unnamed flags are passed in, the list of tests will be filtered to include only those passed. + */ +const argv = minimist(process.argv.slice(2), { + 'boolean': ['debug', 'nolink', 'nightly'], + 'string': ['reuse'] +}); + + +let currentFileName = null; +let index = 0; + +const e2eRoot = path.join(__dirname, 'e2e'); +const allSetups = glob.sync(path.join(e2eRoot, 'setup/**/*'), { nodir: true }) + .map(name => path.relative(e2eRoot, name)) + .sort(); +const allTests = glob.sync(path.join(e2eRoot, 'tests/**/*'), { nodir: true }) + .map(name => path.relative(e2eRoot, name)) + .sort(); + +const testsToRun = allSetups + .concat(allTests + .filter(name => { + // Check for naming tests on command line. + if (argv._.length == 0) { + return true; + } + + return argv._.some(argName => { + return path.join(process.cwd(), argName) == path.join(__dirname, name) + || argName == name + || argName == name.replace(/\.ts$/, ''); + }); + })); + + +/** + * Load all the files from the e2e, filter and sort them and build a promise of their default + * export. + */ +if (testsToRun.length == allTests.length) { + console.log(`Running ${testsToRun.length} tests`); +} else { + console.log(`Running ${testsToRun.length} tests (${allTests.length + allSetups.length} total)`); +} + +testsToRun.reduce((previous, relativeName) => { + // Make sure this is a windows compatible path. + let absoluteName = path.join(e2eRoot, relativeName); + if (/^win/.test(process.platform)) { + absoluteName = absoluteName.replace(/\\/g, path.posix.sep); + } + + return previous.then(() => { + currentFileName = relativeName.replace(/\.ts$/, ''); + const start = +new Date(); + + const module = require(absoluteName); + const fn = (typeof module == 'function') ? module + : (typeof module.default == 'function') ? module.default + : function() { + throw new Error('Invalid test module.'); + }; + + return Promise.resolve() + .then(() => printHeader(currentFileName)) + .then(() => fn(argv)) + .then(() => { + // Only clean after a real test, not a setup step. + if (allSetups.indexOf(relativeName) == -1) { + return gitClean(); + } + }) + .then(() => printFooter(currentFileName, start), + (err) => { + printFooter(currentFileName, start); throw err; + }); + }); +}, Promise.resolve()) +.then( + () => { + console.log(green('Done.')); + process.exit(0); + }, + (err) => { + console.log('\n'); + console.error(red(`Test "${currentFileName}" failed...`)); + console.error(red(err.message)); + console.error(red(err.stack)); + + if (argv.debug) { + console.log(`Current Directory: ${process.cwd()}`); + console.log('Will loop forever while you debug... CTRL-C to quit.'); + + /* eslint-disable no-constant-condition */ + while (1) { + // That's right! + } + } + + process.exit(1); + } +); + + +function encode(str) { + return str.replace(/[^A-Za-z\d\/]+/g, '-').replace(/\//g, '.').replace(/[\/-]$/, ''); +} + +function isTravis() { + return process.env['TRAVIS']; +} + +function printHeader(testName) { + const text = `${++index} of ${testsToRun.length}`; + console.log(green(`Running "${bold(blue(testName))}" (${bold(white(text))})...`)); + + if (isTravis()) { + console.log(`travis_fold:start:${encode(testName)}`); + } +} + +function printFooter(testName, startTime) { + if (isTravis()) { + console.log(`travis_fold:end:${encode(testName)}`); + } + + // Round to hundredth of a second. + const t = Math.round((Date.now() - startTime) / 10) / 100; + console.log(green('Last step took ') + bold(blue(t)) + green('s...')); + console.log(''); +} diff --git a/tests/runner.js b/tests/runner.js index 9387db81161f..ab9a4e1f4955 100644 --- a/tests/runner.js +++ b/tests/runner.js @@ -7,7 +7,7 @@ var Mocha = require('mocha'); var glob = require('glob'); var path = require('path'); -var root = 'tests/{acceptance,models,e2e}'; +var root = 'tests/{acceptance,models}'; var specFiles = glob.sync(root + '/**/*.spec.*'); var mocha = new Mocha({ timeout: 5000, reporter: 'spec' }); diff --git a/tsconfig.json b/tsconfig.json index 7d2ae56c1324..2ab080cf8703 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,11 +22,14 @@ ], "paths": { "@angular-cli/ast-tools": [ "./packages/ast-tools/src" ], + "@angular-cli/base-href-webpack": [ "./packages/base-href-webpack/src" ], "@angular-cli/webpack": [ "./packages/webpack/src" ] } }, "exclude": [ + "addon/ng2/blueprints/*/files/**/*", "dist/**/*", + "node_modules/**/*", "tmp/**/*" ] }