refactor(tests): Refactor e2e tests entirely. (#1986)

This commit is contained in:
Hans 2016-09-08 14:11:26 -07:00 committed by GitHub
parent e20af2109e
commit 9703dfa79c
54 changed files with 8265 additions and 775 deletions

View File

@ -11,5 +11,6 @@ test_script:
- node --version
- npm --version
- npm test
- npm run test:e2e
build: off

View File

@ -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

View File

@ -2,7 +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');
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');
@ -111,12 +111,12 @@ module.exports = {
const returns = [];
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(this.pathToModule, className, importPath)
astUtils.addDeclarationToModule(this.pathToModule, className, importPath)
.then(change => change.apply()));
}

View File

@ -2,7 +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');
const findParentModule = require('../../utilities/find-parent-module').default;
module.exports = {
description: '',
@ -72,7 +72,7 @@ module.exports = {
if (!options['skip-import']) {
returns.push(
astUtils.addComponentToModule(this.pathToModule, className, importPath)
astUtils.addDeclarationToModule(this.pathToModule, className, importPath)
.then(change => change.apply()));
}

View File

@ -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;
},

View File

@ -2,7 +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');
const findParentModule = require('../../utilities/find-parent-module').default;
module.exports = {
description: '',
@ -60,7 +60,7 @@ module.exports = {
if (!options['skip-import']) {
returns.push(
astUtils.addComponentToModule(this.pathToModule, className, importPath)
astUtils.addDeclarationToModule(this.pathToModule, className, importPath)
.then(change => change.apply()));
}

View File

@ -1,5 +1,3 @@
'use strict';
import LinkCli from '../tasks/link-cli';
const Command = require('ember-cli/lib/models/command');

View File

@ -57,7 +57,10 @@ export function findLazyModules(projectRoot: any): string[] {
findLoadChildren(tsPath).forEach(moduleName => {
const fileName = path.resolve(path.dirname(tsPath), moduleName) + '.ts';
if (fs.existsSync(fileName)) {
result[moduleName] = true;
// Put the moduleName as relative to the main.ts.
const fullPath = path.resolve(path.dirname(tsPath), moduleName);
const name = `./${path.relative(projectRoot, fullPath)}.ts`;
result[name] = true;
}
});
});

View File

@ -7,6 +7,6 @@ export {
insertAfterLastOccurrence,
getContentOfKeyLiteral,
getDecoratorMetadata,
addComponentToModule,
addDeclarationToModule,
addProviderToModule
} from '@angular-cli/ast-tools';

View File

@ -2,8 +2,7 @@ import * as fs from 'fs';
import * as path from 'path';
const SilentError = require('silent-error');
module.exports = function findParentModule(project: any, currentDir: string): string {
export default function findParentModule(project: any, currentDir: string): string {
const sourceRoot = path.join(project.root, project.ngConfig.apps[0].root, 'app');
// trim currentDir

View File

@ -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);

File diff suppressed because it is too large Load Diff

View File

@ -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",
@ -114,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",
@ -130,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",

View File

@ -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 => {

View File

@ -243,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]);
});
}
@ -252,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<Change> {
export function addDeclarationToModule(modulePath: string, classifiedName: string,
importPath: string): Promise<Change> {
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<Change> {
return _addSymbolToNgModuleMetadata(modulePath, 'imports', classifiedName, importPath);
}
/**
* Custom function to insert a provider into NgModule. It also imports it.
*/

View File

@ -2,11 +2,11 @@ import {oneLineTrim} from 'common-tags';
import {BaseHrefWebpackPlugin} from './base-href-webpack-plugin';
function mockCompiler(indexHtml, callback) {
function mockCompiler(indexHtml: string, callback: Function) {
return {
plugin: function (event, compilerCallback) {
plugin: function (event: any, compilerCallback: Function) {
const compilation = {
plugin: function (hook, compilationCallback) {
plugin: function (hook: any, compilationCallback: Function) {
const htmlPluginData = {
html: indexHtml
};
@ -29,7 +29,7 @@ describe('base href webpack plugin', () => {
it('should do nothing when baseHref is null', () => {
const plugin = new BaseHrefWebpackPlugin({ baseHref: null });
const compiler = mockCompiler(html, (x, htmlPluginData) => {
const compiler = mockCompiler(html, (x: any, htmlPluginData: any) => {
expect(htmlPluginData.html).toEqual('<body><head></head></body>');
});
plugin.apply(compiler);
@ -37,7 +37,7 @@ describe('base href webpack plugin', () => {
it('should insert base tag when not exist', function () {
const plugin = new BaseHrefWebpackPlugin({ baseHref: '/' });
const compiler = mockCompiler(html, (x, htmlPluginData) => {
const compiler = mockCompiler(html, (x: any, htmlPluginData: any) => {
expect(htmlPluginData.html).toEqual(oneLineTrim`
<html>
<head><base href="/"></head>
@ -55,7 +55,7 @@ describe('base href webpack plugin', () => {
const compiler = mockCompiler(oneLineTrim`
<head><base href="/" target="_blank"></head>
<body></body>
`, (x, htmlPluginData) => {
`, (x: any, htmlPluginData: any) => {
expect(htmlPluginData.html).toEqual(oneLineTrim`
<head><base href="/myUrl/" target="_blank"></head>
<body></body>

View File

@ -1,6 +1,7 @@
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';
@ -39,10 +40,10 @@ describe('find-lazy-module', () => {
it('works', () => {
expect(findLazyModules('project-root')).to.eql([
'moduleA',
'moduleB',
'moduleC',
'app/+workspace/+settings/settings.module'
'./' + join('.', 'moduleA.ts'),
'./' + join('.', 'moduleB.ts'),
'./' + join('.', 'moduleC.ts'),
'./' + join('.', 'app/+workspace/+settings/settings.module.ts')
]);
});
});

View File

@ -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 () {

View File

@ -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('<link rel="manifest" href="/manifest.webapp">')).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(/<base href="\/myUrl\/"/);
});
it('Can run `ng build` in created project', function () {
this.timeout(420000);
return ng(['build'])
.catch(() => {
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('<script src="/app-concat.js" async></script>')).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 "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 "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 = '<script type="text/javascript" src="inline.js"></script>' +
'<script type="text/javascript" src="styles.bundle.js"></script>' +
'<script type="text/javascript" src="scripts.bundle.js"></script>' +
'<script type="text/javascript" src="main.bundle.js"></script>'
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'];
}

View File

@ -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'));
}

View File

@ -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);
}

View File

@ -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'));
}

View File

@ -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', /<base href="\/myUrl">/));
}

View File

@ -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());
}

View File

@ -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'));
}

View File

@ -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'));
}

View File

@ -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));
}

View File

@ -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',
'<link rel="manifest" href="/manifest.webapp">'))
// 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());
}

View File

@ -0,0 +1,19 @@
import {writeMultipleFiles, expectFileToMatch} from '../../../utils/fs';
import {ng} from '../../../utils/process';
export default function() {
return writeMultipleFiles({
'src/styles.css': `
@import './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; }'));
}

View File

@ -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'));
}

View File

@ -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 "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'));
}

View File

@ -0,0 +1,18 @@
import {join} from 'path';
import {ng} from '../../utils/process';
import {expectFileToExist} from '../../utils/fs';
export default function() {
const componentDir = join(process.cwd(), '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'));
}

View File

@ -0,0 +1,15 @@
import {join} from 'path';
import {ng} from '../../utils/process';
import {expectFileToExist} from '../../utils/fs';
export default function() {
const interfaceDir = join(process.cwd(), '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'));
}

View File

@ -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(process.cwd(), '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'));
}

View File

@ -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(process.cwd(), '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'));
}

View File

@ -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')));
}

View File

@ -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'));
}

View File

@ -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'))
.then(() => addImportToModule('src/app/app.module.ts', oneLine`
RouterModule.forRoot([{ path: "/lazy", loadChildren: "./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.');
}
});
}

View File

@ -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')));
}

View File

@ -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',
'<script src="/app-concat.js" async></script>')))
.then(() => expectToFail(() => expectFileToExist('dist/app-concat.js')));
}

View File

@ -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'));
}

View File

@ -0,0 +1,6 @@
import {ng} from '../../utils/process';
export default function() {
return ng('lint');
}

View File

@ -0,0 +1,6 @@
import {ng} from '../../utils/process';
export default function() {
return ng('test', '--watch=false');
}

View File

@ -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`
<script type="text/javascript" src="inline.js"></script>
<script type="text/javascript" src="styles.bundle.js"></script>
<script type="text/javascript" src="scripts.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
`));
}

15
tests/e2e/utils/ast.ts Normal file
View File

@ -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());
}

96
tests/e2e/utils/fs.ts Normal file
View File

@ -0,0 +1,96 @@
import * as fs from 'fs';
export function readFile(fileName: string) {
return new Promise<string>((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<void>((resolve, reject) => {
fs.writeFile(fileName, content, (err: any) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export function deleteFile(path: string) {
return new Promise<void>((resolve, reject) => {
fs.unlink(path, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export function moveFile(from: string, to: string) {
return new Promise<void>((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}...`);
}
}
});
}

37
tests/e2e/utils/git.ts Normal file
View File

@ -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);
}
});
}

17
tests/e2e/utils/http.ts Normal file
View File

@ -0,0 +1,17 @@
import {IncomingMessage} from 'http';
import * as _request from 'request';
export function request(url: string): Promise<string> {
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);
}
});
});
}

111
tests/e2e/utils/process.ts Normal file
View File

@ -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<string> {
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);
}

View File

@ -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/);
}

17
tests/e2e/utils/utils.ts Normal file
View File

@ -0,0 +1,17 @@
export function expectToFail(fn: () => Promise<any>): Promise<void> {
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);
});
}

155
tests/e2e_runner.js Normal file
View File

@ -0,0 +1,155 @@
/*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.')),
(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('');
}

View File

@ -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' });

View File

@ -27,7 +27,9 @@
}
},
"exclude": [
"addon/ng2/blueprints/*/files/**/*",
"dist/**/*",
"node_modules/**/*",
"tmp/**/*"
]
}