mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 02:54:21 +08:00
refactor(tests): Refactor e2e tests entirely. (#1986)
This commit is contained in:
parent
e20af2109e
commit
9703dfa79c
@ -11,5 +11,6 @@ test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm test
|
||||
- npm run test:e2e
|
||||
|
||||
build: off
|
||||
|
@ -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
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
import LinkCli from '../tasks/link-cli';
|
||||
|
||||
const Command = require('ember-cli/lib/models/command');
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,6 @@ export {
|
||||
insertAfterLastOccurrence,
|
||||
getContentOfKeyLiteral,
|
||||
getDecoratorMetadata,
|
||||
addComponentToModule,
|
||||
addDeclarationToModule,
|
||||
addProviderToModule
|
||||
} from '@angular-cli/ast-tools';
|
||||
|
@ -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
|
||||
|
2
lib/bootstrap-local.js
vendored
2
lib/bootstrap-local.js
vendored
@ -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);
|
||||
|
7211
npm-debug.log.aa8c16ea08920c0b1500cf805515bb63
Normal file
7211
npm-debug.log.aa8c16ea08920c0b1500cf805515bb63
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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 => {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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>
|
||||
|
@ -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')
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -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 () {
|
||||
|
@ -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'];
|
||||
}
|
13
tests/e2e/setup/000-npm-link.ts
Normal file
13
tests/e2e/setup/000-npm-link.ts
Normal 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'));
|
||||
}
|
9
tests/e2e/setup/010-create-tmp-dir.ts
Normal file
9
tests/e2e/setup/010-create-tmp-dir.ts
Normal 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);
|
||||
}
|
50
tests/e2e/setup/020-create-project.ts
Normal file
50
tests/e2e/setup/020-create-project.ts
Normal 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'));
|
||||
}
|
8
tests/e2e/tests/build/base-href.ts
Normal file
8
tests/e2e/tests/build/base-href.ts
Normal 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">/));
|
||||
}
|
10
tests/e2e/tests/build/dev-build.ts
Normal file
10
tests/e2e/tests/build/dev-build.ts
Normal 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());
|
||||
}
|
18
tests/e2e/tests/build/environment.ts
Normal file
18
tests/e2e/tests/build/environment.ts
Normal 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'));
|
||||
}
|
10
tests/e2e/tests/build/no-implicit-any.ts
Normal file
10
tests/e2e/tests/build/no-implicit-any.ts
Normal 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'));
|
||||
}
|
12
tests/e2e/tests/build/output-dir.ts
Normal file
12
tests/e2e/tests/build/output-dir.ts
Normal 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));
|
||||
}
|
41
tests/e2e/tests/build/prod-build.ts
Normal file
41
tests/e2e/tests/build/prod-build.ts
Normal 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());
|
||||
}
|
19
tests/e2e/tests/build/styles/css.ts
Normal file
19
tests/e2e/tests/build/styles/css.ts
Normal 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; }'));
|
||||
}
|
33
tests/e2e/tests/build/styles/less.ts
Normal file
33
tests/e2e/tests/build/styles/less.ts
Normal 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'));
|
||||
}
|
41
tests/e2e/tests/build/styles/scss.ts
Normal file
41
tests/e2e/tests/build/styles/scss.ts
Normal 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'));
|
||||
}
|
18
tests/e2e/tests/generate/component.ts
Normal file
18
tests/e2e/tests/generate/component.ts
Normal 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'));
|
||||
}
|
15
tests/e2e/tests/generate/interface.ts
Normal file
15
tests/e2e/tests/generate/interface.ts
Normal 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'));
|
||||
}
|
17
tests/e2e/tests/generate/pipe.ts
Normal file
17
tests/e2e/tests/generate/pipe.ts
Normal 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'));
|
||||
}
|
17
tests/e2e/tests/generate/service.ts
Normal file
17
tests/e2e/tests/generate/service.ts
Normal 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'));
|
||||
}
|
13
tests/e2e/tests/misc/assets.ts
Normal file
13
tests/e2e/tests/misc/assets.ts
Normal 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')));
|
||||
}
|
9
tests/e2e/tests/misc/coverage.ts
Normal file
9
tests/e2e/tests/misc/coverage.ts
Normal 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'));
|
||||
}
|
31
tests/e2e/tests/misc/lazy-module.ts
Normal file
31
tests/e2e/tests/misc/lazy-module.ts
Normal 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.');
|
||||
}
|
||||
});
|
||||
}
|
46
tests/e2e/tests/misc/proxy.ts
Normal file
46
tests/e2e/tests/misc/proxy.ts
Normal 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')));
|
||||
}
|
20
tests/e2e/tests/mobile/prod-features.ts
Normal file
20
tests/e2e/tests/mobile/prod-features.ts
Normal 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')));
|
||||
}
|
21
tests/e2e/tests/test/e2e.ts
Normal file
21
tests/e2e/tests/test/e2e.ts
Normal 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'));
|
||||
}
|
6
tests/e2e/tests/test/lint.ts
Normal file
6
tests/e2e/tests/test/lint.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import {ng} from '../../utils/process';
|
||||
|
||||
|
||||
export default function() {
|
||||
return ng('lint');
|
||||
}
|
6
tests/e2e/tests/test/test.ts
Normal file
6
tests/e2e/tests/test/test.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import {ng} from '../../utils/process';
|
||||
|
||||
|
||||
export default function() {
|
||||
return ng('test', '--watch=false');
|
||||
}
|
30
tests/e2e/tests/third-party/bootstrap.ts
vendored
Normal file
30
tests/e2e/tests/third-party/bootstrap.ts
vendored
Normal 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
15
tests/e2e/utils/ast.ts
Normal 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
96
tests/e2e/utils/fs.ts
Normal 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
37
tests/e2e/utils/git.ts
Normal 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
17
tests/e2e/utils/http.ts
Normal 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
111
tests/e2e/utils/process.ts
Normal 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);
|
||||
}
|
25
tests/e2e/utils/project.ts
Normal file
25
tests/e2e/utils/project.ts
Normal 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
17
tests/e2e/utils/utils.ts
Normal 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
155
tests/e2e_runner.js
Normal 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('');
|
||||
}
|
@ -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' });
|
||||
|
||||
|
@ -27,7 +27,9 @@
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"addon/ng2/blueprints/*/files/**/*",
|
||||
"dist/**/*",
|
||||
"node_modules/**/*",
|
||||
"tmp/**/*"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user