diff --git a/package.json b/package.json index 719e54f726..b01074cb4b 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ "@angular/forms": "^2.0.0", "@angular/http": "^2.0.0", "@angular/platform-browser": "^2.0.0", - "@angular/platform-browser-dynamic": "^2.0.0", + "@angular/platform-browser-dynamic": "^2.1.0", "@angular/platform-server": "^2.0.0", - "ionicons": "^3.0.0", "ionic-native": "^2.0.3", + "ionicons": "^3.0.0", "rxjs": "^5.0.0-beta.12", "zone.js": "^0.6.21" }, @@ -55,6 +55,8 @@ "@types/semver": "5.3.30", "@types/serve-static": "1.7.31", "@types/through2": "2.0.29", + "babel-plugin-transform-es2015-modules-systemjs": "^6.14.0", + "babel-preset-es2015": "^6.16.0", "canonical-path": "0.0.2", "connect": "3.5.0", "conventional-changelog": "1.1.0", @@ -69,6 +71,8 @@ "glob": "7.0.6", "gulp": "3.9.1", "gulp-autoprefixer": "3.1.1", + "gulp-babel": "^6.1.2", + "gulp-cached": "^1.1.0", "gulp-clean-css": "2.0.12", "gulp-concat": "2.6.0", "gulp-connect": "^5.0.0", diff --git a/scripts/README.md b/scripts/README.md index 2e22a20555..596e463152 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -2,7 +2,8 @@ ## Getting Started -All of these commands require you to run `npm install` first. Add the `--typecheck` flag to generate type definitions (`.d.ts`) and do type checking, but keep in mind builds and rebuilds when watching will be significantly slower (~1min and ~1s respectively, vs ~20s and ~200ms without typechecking). +All of these commands require you to run `npm install` first. To see a full list of the gulp commands, run `gulp`. + ### Installing Nightly Version @@ -20,14 +21,24 @@ Run `gulp build` or `gulp watch` to watch for changes. ### Building & Running e2e Tests -1. Run `gulp e2e` or `gulp e2e.watch` with a folder passed to watch for changes. -2. Navigate to `http://localhost:8080/dist/e2e` +#### Development + +1. Run `gulp e2e` or `gulp e2e.watch` to watch for changes. +2. Navigate to `http://localhost:8000/dist/e2e` + +#### Validation + +The following commands take longer to run because they use AoT compilation. They should really only be used to validate that our components work with AoT, and fix them if not. + +1. Run `gulp e2e.prod` to bundle all e2e tests, or pass a folder for a specific test. For example, `gulp e2e.prod --f=alert/basic` will build the test in `src/components/alert/test/basic`. +2. Run `gulp e2e.watchProd` with a folder passed to watch a test. For example, `gulp e2e.watchProd --f=select/single-value` will watch the test in `src/components/select/test/single-value`. +3. Navigate to `http://localhost:8000/dist/e2e` ### Building & Running API Demos -1. Run `gulp demos` or `gulp demos.watch` with a folder passed to watch for changes. -2. Navigate to `http://localhost:80808080/dist/demos` +1. Run `gulp demos` or `gulp demos.watch` to watch for changes. +2. Navigate to `http://localhost:8000/dist/demos` ### Building API Docs @@ -54,11 +65,22 @@ To remove the linked version of `ionic-angular` do `npm rm ionic-angular`, and t ### Running Snapshot +#### Setup + 1. Install [Protractor](https://angular.github.io/protractor/#/): `npm install -g protractor@2.5.1` 2. Run `webdriver-manager update` 3. Export `IONIC_SNAPSHOT_KEY` (get from someone) -4. Run `gulp snapshot` +#### Commands + +- `gulp snapshot` will run the `gulp e2e.prod` task with AoT compilation. +- `gulp snapshot.skipBuild` will skip the `gulp e2e.prod` task with AoT compilation. +- `gulp snapshot.dev` will run a development build using the `gulp e2e` task. +- `gulp snapshot.quick` will skip the build and run snapshot without uploading to the server. + +#### Flags + +- `--f | -folder` will run the command with a test folder. For example, `gulp snapshot --f=action-sheet/basic` will run snapshot for the test at `src/components/action-sheet/test/basic`. ### Running Tests diff --git a/scripts/e2e/e2e.template.dev.html b/scripts/e2e/e2e.template.dev.html new file mode 100644 index 0000000000..63ccbcc220 --- /dev/null +++ b/scripts/e2e/e2e.template.dev.html @@ -0,0 +1,83 @@ + + + + + Ionic E2E + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/e2e/e2e.template.js b/scripts/e2e/e2e.template.js index dcf196f45c..34aeb4a0ba 100644 --- a/scripts/e2e/e2e.template.js +++ b/scripts/e2e/e2e.template.js @@ -1,7 +1,7 @@ describe('<%= relativePath %>: <%= platform %>', function() { it('should init', function() { - browser.get('http://localhost:<%= buildConfig.protractorPort %>/dist/e2e/components/<%= relativePath %>/index.html?ionicplatform=<%= platform %>&ionicOverlayCreatedDiff=0&ionicanimate=false&snapshot=true'); + browser.get('http://localhost:<%= buildConfig.protractorPort %>/dist/e2e/<%= relativePath %>/index.html?ionicplatform=<%= platform %>&ionicOverlayCreatedDiff=0&ionicanimate=false&snapshot=true'); }); <%= contents %> diff --git a/scripts/e2e/index.html b/scripts/e2e/e2e.template.prod.html similarity index 100% rename from scripts/e2e/index.html rename to scripts/e2e/e2e.template.prod.html diff --git a/scripts/gulp/constants.ts b/scripts/gulp/constants.ts index 07170134c6..861a1309c0 100644 --- a/scripts/gulp/constants.ts +++ b/scripts/gulp/constants.ts @@ -45,4 +45,4 @@ export const NPM_VENDOR_FILES = [ // SERVER -export const LOCAL_SERVER_PORT = 8080; +export const LOCAL_SERVER_PORT = 8000; diff --git a/scripts/gulp/declarations.d.ts b/scripts/gulp/declarations.d.ts index 407b87f1c9..d73cc38eff 100644 --- a/scripts/gulp/declarations.d.ts +++ b/scripts/gulp/declarations.d.ts @@ -2,18 +2,25 @@ declare module 'conventional-changelog'; declare module 'dgeni'; declare module 'event-stream'; declare module 'github'; +declare module 'gulp-babel'; +declare module 'gulp-cached'; +declare module 'gulp-concat'; declare module 'gulp-connect'; declare module 'gulp-if'; -declare module 'gulp-open'; +declare module 'gulp-remember'; +declare module 'gulp-rename'; declare module 'gulp-scss-lint'; declare module 'gulp-server-livereload'; declare module 'gulp-tslint'; +declare module 'gulp-typescript'; declare module 'html-entities'; +declare module 'path'; declare module 'rollup'; declare module 'rollup-plugin-commonjs'; declare module 'rollup-plugin-multi-entry'; declare module 'rollup-plugin-node-resolve'; declare module 'rollup-plugin-uglify'; +declare module 'through2'; declare module 'semver'; declare module 'vinyl'; declare module 'yargs'; diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts index 2bb4f9b921..5d6fe71295 100644 --- a/scripts/gulp/gulpfile.ts +++ b/scripts/gulp/gulpfile.ts @@ -4,6 +4,8 @@ import './tasks/default'; import './tasks/demos'; import './tasks/docs'; import './tasks/e2e'; +import './tasks/e2e.dev'; +import './tasks/e2e.prod'; import './tasks/lint'; import './tasks/release'; import './tasks/snapshot'; diff --git a/scripts/gulp/tasks/e2e.dev.ts b/scripts/gulp/tasks/e2e.dev.ts new file mode 100644 index 0000000000..9625048ed3 --- /dev/null +++ b/scripts/gulp/tasks/e2e.dev.ts @@ -0,0 +1,226 @@ +import { readFileSync } from 'fs'; +import { dirname, join, sep } from 'path'; + +import { dest, src, start, task } from 'gulp'; +import * as babel from 'gulp-babel'; +import * as cache from 'gulp-cached'; +import * as concat from 'gulp-concat'; +import * as connect from 'gulp-connect'; +import * as gulpif from 'gulp-if'; +import * as remember from 'gulp-remember'; +import * as rename from 'gulp-rename'; +import * as tsc from 'gulp-typescript'; +import * as watch from 'gulp-watch'; +import { template } from 'lodash'; +import * as merge from 'merge2'; +import * as runSequence from 'run-sequence'; +import { obj } from 'through2'; +import * as VinylFile from 'vinyl'; + +import { DIST_E2E_ROOT, DIST_NAME, E2E_NAME, ES5, ES_2015, SCRIPTS_ROOT } from '../constants'; + +const buildConfig = require('../../build/config'); + +/** + * Builds Ionic e2e tests to dist/e2e and creates the necessary files for tests + * to run. + */ +task('e2e', e2eBuild); + +function e2eBuild(done: (err: any) => void) { + runSequence( + 'e2e.clean', + 'e2e.build', + 'e2e.polyfill', + 'e2e.copyExternalDependencies', + 'e2e.sass', + 'e2e.fonts', + 'e2e.bundle', + done); +} + +/** + * Builds Ionic e2e tests to dist/e2e. + */ +task('e2e.build', function () { + var indexTemplate = template( + readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.dev.html`).toString() + )({ + buildConfig: buildConfig + }); + + // Get each test folder with src + var tsResult = src([ + 'src/components/*/test/*/**/*.ts', + '!src/components/*/test/*/**/*.spec.ts' + ]) + .pipe(cache('e2e.ts')) + .pipe(tsc(getTscOptions(), undefined, tscReporter)) + .on('error', function (error) { + console.log(error.message); + }) + .pipe(gulpif(/app-module.js$/, createIndexHTML())) + .pipe(gulpif(/e2e.js$/, createPlatformTests())); + + var testFiles = src([ + 'src/components/*/test/*/**/*', + '!src/components/*/test/*/**/*.ts' + ]) + .pipe(cache('e2e.files')); + + return merge([ + tsResult, + testFiles + ]) + .pipe(rename(function (file) { + file.dirname = file.dirname.replace(sep + 'test' + sep, sep); + })) + .pipe(dest(DIST_E2E_ROOT)) + .pipe(connect.reload()); + + function createIndexHTML() { + return obj(function (file, enc, next) { + this.push(new VinylFile({ + base: file.base, + contents: new Buffer(indexTemplate), + path: join(dirname(file.path), 'index.html'), + })); + next(null, file); + }); + } + + function createPlatformTests() { + let platforms = [ + 'android', + 'ios', + 'windows' + ]; + + let testTemplate = template(readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.js`).toString()); + + return obj(function (file, enc, next) { + let self = this; + + let relativePath = dirname(file.path.replace(/^.*?src(\/|\\)components(\/|\\)/, '')); + relativePath = relativePath.replace('/test/', '/'); + + let contents = file.contents.toString(); + platforms.forEach(function (platform) { + let platformContents = testTemplate({ + contents: contents, + buildConfig: buildConfig, + relativePath: relativePath, + platform: platform + }); + self.push(new VinylFile({ + base: file.base, + contents: new Buffer(platformContents), + path: file.path.replace(/e2e.js$/, platform + '.e2e.js') + })); + }); + next(); + }); + } +}); + +/** + * Creates SystemJS bundle from Ionic source files. + */ +task('e2e.bundle', function () { + var tsResult = tsCompile(getTscOptions('es6'), 'system') + .pipe(babel(babelOptions)); + + var swiper = src('src/components/slides/swiper-widget.system.js'); + + return merge([tsResult, swiper]) + .pipe(remember('system')) + .pipe(concat('ionic.system.js')) + .pipe(dest(`${DIST_NAME}/bundles`)) + .pipe(connect.reload()); +}); + +function tsCompile(options, cacheName) { + return src([ + 'typings/main.d.ts', + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/components/*/test/**/*', + '!src/util/test/*', + '!src/config/test/*', + '!src/platform/test/*', + '!src/**/*.spec.ts' + ]) + .pipe(cache(cacheName, { optimizeMemory: true })) + .pipe(tsc(options, undefined, tscReporter)); +} + +function getTscOptions(name?: string) { + var opts = { + emitDecoratorMetadata: true, + experimentalDecorators: true, + target: ES5, + module: 'commonjs', + isolatedModules: true, + typescript: require('typescript'), + declaration: false + }; + + if (name === 'typecheck') { + opts.declaration = true; + delete opts.isolatedModules; + } else if (name === 'es6') { + opts.target = 'es6'; + delete opts.module; + } + return opts; +} + +var tscReporter = { + error: function (error) { + console.error(error.message); + } +}; + +// We use Babel to easily create named System.register modules +// See: https://github.com/Microsoft/TypeScript/issues/4801 +// and https://github.com/ivogabe/gulp-typescript/issues/211 +const babelOptions = { + moduleIds: true, + getModuleId: function (name) { + return 'ionic-angular/' + name; + }, + plugins: ['transform-es2015-modules-systemjs'], + presets: [ES_2015] +}; + +/** + * Builds e2e tests to dist/e2e and watches for changes. Runs 'e2e.bundle' or + * 'sass' on Ionic source changes and 'e2e.build' for e2e test changes. + */ +task('e2e.watch', ['e2e'], function () { + watchTask('e2e.bundle'); + + watch('src/components/*/test/**/*', function (file) { + start('e2e.build'); + }); +}); + +function watchTask(task) { + watch([ + 'src/**/*.ts', + '!src/components/*/test/**/*', + '!src/util/test/*' + ], + function (file) { + if (file.event !== 'unlink') { + start(task); + } + } + ); + + watch('src/**/*.scss', function () { + start('e2e.sass'); + }); + + start('e2e.serve'); +} diff --git a/scripts/gulp/tasks/e2e.prod.ts b/scripts/gulp/tasks/e2e.prod.ts new file mode 100644 index 0000000000..170a3bee1b --- /dev/null +++ b/scripts/gulp/tasks/e2e.prod.ts @@ -0,0 +1,279 @@ +import { accessSync, F_OK, readFileSync, writeFileSync } from 'fs'; +import { dirname, join } from 'path'; + +import * as glob from 'glob'; +import { dest, src, start, task } from 'gulp'; +import * as gulpif from 'gulp-if'; +import * as watch from 'gulp-watch'; +import { template } from 'lodash'; +import * as rollup from 'rollup'; +import * as nodeResolve from 'rollup-plugin-node-resolve'; +import * as commonjs from 'rollup-plugin-commonjs'; +import * as runSequence from 'run-sequence'; +import { obj } from 'through2'; +import * as VinylFile from 'vinyl'; +import { argv } from 'yargs'; + +import { DIST_E2E_COMPONENTS_ROOT, DIST_E2E_ROOT, DIST_NAME, E2E_NAME, ES5, ES_2015, LOCAL_SERVER_PORT, PROJECT_ROOT, SCRIPTS_ROOT, SRC_COMPONENTS_ROOT, SRC_ROOT } from '../constants'; +import { createTempTsConfig, deleteFiles, runNgc } from '../util'; + +task('e2e.prod', e2eBuild); + +function e2eBuild(done: (err: any) => void) { + runSequence( + 'e2e.clean', + 'e2e.polyfill', + 'e2e.copySource', + 'e2e.compileTests', + 'e2e.copyExternalDependencies', + 'e2e.sass', + 'e2e.fonts', + 'e2e.bundleProd', + done); +} + +task('e2e.copySource', (done: Function) => { + + const buildConfig = require('../../build/config'); + + const stream = src([`${SRC_ROOT}/**/*`, `!${SRC_ROOT}/**/*.spec.ts`]) + .pipe(gulpif(/app-module.ts$/, createIndexHTML())) + .pipe(gulpif(/e2e.ts$/, createPlatformTests())) + .pipe(dest(DIST_E2E_ROOT)); + + stream.on('end', done); + + function createIndexHTML() { + const indexTemplate = readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.prod.html`); + const indexTs = readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/entry.ts`); + + return obj(function (file, enc, next) { + this.push(new VinylFile({ + base: file.base, + contents: new Buffer(indexTemplate), + path: join(dirname(file.path), 'index.html'), + })); + this.push(new VinylFile({ + base: file.base, + contents: new Buffer(indexTs), + path: join(dirname(file.path), 'entry.ts'), + })); + next(null, file); + }); + } + + // TODO this is almost the same as dev, diff and combine + function createPlatformTests() { + let platforms = [ + 'android', + 'ios', + 'windows' + ]; + + let testTemplate = template(readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.js`).toString()); + + return obj(function (file, enc, next) { + let self = this; + + let relativePath = dirname(file.path.replace(/^.*?src(\/|\\)/, '')); + + let contents = file.contents.toString(); + platforms.forEach(function (platform) { + let platformContents = testTemplate({ + contents: contents, + buildConfig: buildConfig, + relativePath: relativePath, + platform: platform + }); + self.push(new VinylFile({ + base: file.base, + contents: new Buffer(platformContents), + path: file.path.replace(/e2e.ts$/, platform + '.e2e.js') + })); + }); + next(); + }); + } +}); + +task('e2e.compileTests', (done: Function) => { + let folderInfo = getFolderInfo(); + buildE2ETests(folderInfo, done); +}); + +function buildE2ETests(folderInfo: any, done: Function) { + let includeGlob = ['./components/*/test/*/app-module.ts', './components/*/test/*/entry.ts']; + if (folderInfo.componentName && folderInfo.componentTest) { + includeGlob = [ + `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app-module.ts`, + `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/entry.ts`, + ]; + } + createTempTsConfig(includeGlob, ES5, ES_2015, `${DIST_E2E_ROOT}/tsconfig.json`); + runNgc(`${DIST_E2E_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + // clean up any .ts files that remain + deleteFiles([`${DIST_E2E_ROOT}/**/*.ts`, `!${DIST_E2E_ROOT}/**/*.ngfactory.ts`, `!${DIST_E2E_ROOT}/**/*.d.ts`], done); + }); +} + +task('e2e.bundleProd', (done) => { + let includeGlob = `${DIST_E2E_ROOT}/components/*/test/*/entry.js`; + let folderInfo = getFolderInfo(); + if (folderInfo.componentName && folderInfo.componentTest) { + includeGlob = `${DIST_E2E_ROOT}/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/entry.js`; + } + glob(includeGlob, {}, function (er, files) { + var directories = files.map(function (file) { + return dirname(file); + }); + + let indexFileContents = directories.map(function (dir) { + let testName = dir.replace(`${DIST_E2E_ROOT}/components/`, ''); + let fileName = dir.replace(`${PROJECT_ROOT}`, ''); + return `

${testName}

`; + }, []); + + writeFileSync(`${DIST_E2E_ROOT}/index.html`, + '\n' + + indexFileContents.join('\n') + + '' + ); + + createBundles(files).then(() => { + done(); + }).catch(err => { + done(err); + }); + }); +}); + +function createBundles(files: string[]) { + let start; + if (!files) { + return Promise.reject(new Error('list of files is null')); + } else if (files.length === 0) { + return Promise.resolve(); + } else { + const outputFileName = join(dirname(files[0]), 'app.bundle.js'); + start = Date.now(); + return bundle(files[0], outputFileName).then(() => { + const end = Date.now(); + const seconds = (end - start) / 1000; + console.log(`Took ${seconds} seconds to process ${files[0]}`); + const remainingFiles = files.concat(); + remainingFiles.shift(); + return createBundles(remainingFiles); + }).catch(err => { + return Promise.reject(err); + }); + } +} + +function bundle(inputFile: string, outputFile: string): Promise { + console.log(`Starting rollup on ${inputFile} ... writing to ${outputFile}`); + return rollup.rollup({ + entry: inputFile, + plugins: [ + commonjs(), + nodeResolve({ + module: true, + jsnext: true, + main: true, + extensions: ['.js'] + }) + ] + }).then(bundle => { + return bundle.write({ + format: 'iife', + dest: outputFile, + }); + }); +} + +task('e2e.watchProd', ['e2e.copyExternalDependencies', 'e2e.sass', 'e2e.fonts'], (done: Function) => { + const folderInfo = getFolderInfo(); + let e2eTestPath = SRC_COMPONENTS_ROOT; + + if (folderInfo.componentName && folderInfo.componentTest) { + e2eTestPath = join(SRC_COMPONENTS_ROOT, folderInfo.componentName, 'test', folderInfo.componentTest, 'app-module.ts'); + } + + try { + accessSync(e2eTestPath, F_OK); + } catch (e) { + done(new Error(`Could not find e2e test: ${e2eTestPath}`)); + return; + } + + if (e2eComponentsExists()) { + // already generated the e2e directory + e2eWatch(folderInfo.componentName, folderInfo.componentTest); + + } else { + // generate the e2e directory + console.log('Generated e2e builds first...'); + e2eBuild(() => { + e2eWatch(folderInfo.componentName, folderInfo.componentTest); + }); + } +}); + +function e2eWatch(componentName: string, componentTest: string) { + // If any tests change within components then run e2e.resources. + watch([ + 'src/components/*/test/**/*' + ], + function (file) { + console.log('start e2e.resources - ' + JSON.stringify(file.history, null, 2)); + start('e2e.copyAndCompile'); + }); + + // If any src files change except for tests then transpile only the source ionic files + watch([ + 'src/**/*.ts', + '!src/components/*/test/**/*', + '!src/util/test/*' + ], + function (file) { + console.log('start e2e.ngcSource - ' + JSON.stringify(file.history, null, 2)); + start('e2e.copyAndCompile'); + }); + + // If any scss files change then recompile all sass + watch(['src/**/*.scss'], (file) => { + console.log('start sass - ' + JSON.stringify(file.history, null, 2)); + start('e2e.sass'); + }); + + console.log(`http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${E2E_NAME}/components/${componentName}/test/${componentTest}/`); + + start('e2e.serve'); +} + +function e2eComponentsExists(): boolean { + try { + accessSync(DIST_E2E_COMPONENTS_ROOT, F_OK); + } catch (e) { + return false; + } + return true; +} + +function getFolderInfo() { + let componentName: string = null; + let componentTest: string = null; + const folder: string = argv.folder || argv.f; + if (folder && folder.length) { + const folderSplit = folder.split('/'); + componentName = folderSplit[0]; + componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic'); + } + return { + componentName: componentName, + componentTest: componentTest + }; +} \ No newline at end of file diff --git a/scripts/gulp/tasks/e2e.ts b/scripts/gulp/tasks/e2e.ts index cb52140d04..25644c5b0b 100644 --- a/scripts/gulp/tasks/e2e.ts +++ b/scripts/gulp/tasks/e2e.ts @@ -1,32 +1,11 @@ -import { accessSync, F_OK, readFileSync, writeFileSync } from 'fs'; -import { dirname, join } from 'path'; - -import * as glob from 'glob'; -import {dest, src, start, task} from 'gulp'; +import { dest, src, task } from 'gulp'; import * as connect from 'gulp-connect'; -import * as gulpif from 'gulp-if'; -import * as open from 'gulp-open'; -import * as watch from 'gulp-watch'; -import { template } from 'lodash'; -import * as rollup from 'rollup'; -import * as nodeResolve from 'rollup-plugin-node-resolve'; -import * as commonjs from 'rollup-plugin-commonjs'; import * as del from 'del'; import * as runSequence from 'run-sequence'; -import { obj } from 'through2'; -import * as VinylFile from 'vinyl'; -import { argv } from 'yargs'; -import { DIST_E2E_COMPONENTS_ROOT, DIST_E2E_ROOT, DIST_NAME, E2E_NAME, ES5, ES_2015, LOCAL_SERVER_PORT, PROJECT_ROOT, SCRIPTS_ROOT, SRC_COMPONENTS_ROOT, SRC_ROOT } from '../constants'; -import { compileSass, copyFonts, createTempTsConfig, createTimestamp, deleteFiles, runNgc, setSassIonicVersion, writePolyfills } from '../util'; +import { DIST_E2E_ROOT, LOCAL_SERVER_PORT, SCRIPTS_ROOT } from '../constants'; +import { compileSass, copyFonts, createTimestamp, setSassIonicVersion, writePolyfills } from '../util'; -task('e2e', e2eBuild); - -function e2eBuild(done: (err: any) => void) { - runSequence('e2e.polyfill', 'e2e.copySource', 'e2e.compileTests', 'e2e.copyExternalDependencies', 'e2e.sass', 'e2e.fonts', 'e2e.bundle', done); -} - -// TODO this should run when building all of e2e, not when folder passed task('e2e.clean', (done: Function) => { del(['dist/e2e/**']).then(() => { done(); @@ -44,110 +23,13 @@ task('e2e.polyfill', (done: Function) => { }); task('e2e.copyAndCompile', (done: (err: any) => void) => { - runSequence('e2e.copySource', 'e2e.compileTests', 'e2e.bundle', done); + runSequence( + 'e2e.copySource', + 'e2e.compileTests', + 'e2e.bundle', + done); }); -task('e2e.copySource', (done: Function) => { - - const buildConfig = require('../../build/config'); - - const stream = src([`${SRC_ROOT}/**/*`, `!${SRC_ROOT}/**/*.spec.ts`]) - .pipe(gulpif(/app-module.ts$/, createIndexHTML())) - .pipe(gulpif(/e2e.ts$/, createPlatformTests())) - .pipe(dest(DIST_E2E_ROOT)); - - stream.on('end', done); - - function createIndexHTML() { - const indexTemplate = readFileSync('scripts/e2e/index.html'); - const indexTs = readFileSync('scripts/e2e/entry.ts'); - - return obj(function(file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: join(dirname(file.path), 'index.html'), - })); - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTs), - path: join(dirname(file.path), 'entry.ts'), - })); - next(null, file); - }); - } - - function createPlatformTests() { - let platforms = [ - 'android', - 'ios', - 'windows' - ]; - - let testTemplate = template(readFileSync('scripts/e2e/e2e.template.js').toString()); - - return obj(function(file, enc, next) { - let self = this; - let relativePath = dirname(file.path.replace(/^.*?src(\/|\\)components(\/|\\)/, '')); - - let contents = file.contents.toString(); - platforms.forEach(function(platform) { - let platformContents = testTemplate({ - contents: contents, - buildConfig: buildConfig, - relativePath: relativePath, - platform: platform - }); - self.push(new VinylFile({ - base: file.base, - contents: new Buffer(platformContents), - path: file.path.replace(/e2e.ts$/, platform + '.e2e.js') - })); - }); - next(); - }); - } -}); - -task('e2e.compileTests', (done: Function) => { - let folderInfo = getFolderInfo(); - buildE2ETests(folderInfo, done); -}); - -function buildE2ETests(folderInfo: any, done: Function) { - let includeGlob = ['./components/*/test/*/app-module.ts', './components/*/test/*/entry.ts']; - if (folderInfo.componentName && folderInfo.componentTest) { - includeGlob = [ - `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app-module.ts`, - `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/entry.ts`, - ]; - } - createTempTsConfig(includeGlob, ES5, ES_2015, `${DIST_E2E_ROOT}/tsconfig.json`); - runNgc(`${DIST_E2E_ROOT}/tsconfig.json`, (err) => { - if (err) { - done(err); - return; - } - // clean up any .ts files that remain - deleteFiles([`${DIST_E2E_ROOT}/**/*.ts`, `!${DIST_E2E_ROOT}/**/*.ngfactory.ts`, `!${DIST_E2E_ROOT}/**/*.d.ts`], done); - }); -} - -function getFolderInfo() { - let componentName: string = null; - let componentTest: string = null; - const folder: string = argv.folder || argv.f; - if (folder && folder.length) { - const folderSplit = folder.split('/'); - componentName = folderSplit[0]; - componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic'); - } - return { - componentName: componentName, - componentTest: componentTest - }; -} - task('e2e.copyExternalDependencies', () => { src([`${SCRIPTS_ROOT}/e2e/*.css`]).pipe(dest(`${DIST_E2E_ROOT}/css`)); }); @@ -162,154 +44,12 @@ task('e2e.fonts', () => { return copyFonts(`${DIST_E2E_ROOT}/fonts`); }); -task('e2e.bundle', (done) => { - let includeGlob = `${DIST_E2E_ROOT}/components/*/test/*/entry.js`; - let folderInfo = getFolderInfo(); - if (folderInfo.componentName && folderInfo.componentTest) { - includeGlob = `${DIST_E2E_ROOT}/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/entry.js`; - } - glob(includeGlob, {}, function(er, files) { - var directories = files.map(function(file) { - return dirname(file); - }); - - let indexFileContents = directories.map(function(dir) { - let testName = dir.replace(`${DIST_E2E_ROOT}/components/`, ''); - let fileName = dir.replace(`${PROJECT_ROOT}`, ''); - return `

${testName}

`; - }, []); - - writeFileSync(`${DIST_E2E_ROOT}/index.html`, - '\n' + - indexFileContents.join('\n') + - '' - ); - - createBundles(files).then(() => { - done(); - }).catch(err => { - done(err); - }); - }); -}); - -function createBundles(files: string[]) { - let start; - if (!files) { - return Promise.reject(new Error('list of files is null')); - } else if ( files.length === 0) { - return Promise.resolve(); - } else { - const outputFileName = join(dirname(files[0]), 'app.bundle.js'); - start = Date.now(); - return bundle(files[0], outputFileName).then(() => { - const end = Date.now(); - const seconds = (end - start) / 1000; - console.log(`Took ${seconds} seconds to process ${files[0]}`); - const remainingFiles = files.concat(); - remainingFiles.shift(); - return createBundles(remainingFiles); - }).catch(err => { - return Promise.reject(err); - }); - } -} - -function bundle(inputFile: string, outputFile: string): Promise { - console.log(`Starting rollup on ${inputFile} ... writing to ${outputFile}`); - return rollup.rollup({ - entry: inputFile, - plugins: [ - commonjs(), - nodeResolve({ - module: true, - jsnext: true, - main: true, - extensions: ['.js'] - }) - ] - }).then(bundle => { - return bundle.write({ - format: 'iife', - dest: outputFile, - }); - }); -} - -task('e2e.watch', ['e2e.copyExternalDependencies', 'e2e.sass', 'e2e.fonts'], (done: Function) => { - const folderInfo = getFolderInfo(); - if (! folderInfo.componentName || ! folderInfo.componentTest) { - done(new Error('Passing in a folder to watch is required for this command. Use the --folder or -f option.')); - return; - } - - const e2eTestPath = join(SRC_COMPONENTS_ROOT, folderInfo.componentName, 'test', folderInfo.componentTest, 'app-module.ts'); - - try { - accessSync(e2eTestPath, F_OK); - } catch (e) { - done(new Error(`Could not find e2e test: ${e2eTestPath}`)); - return; - } - - if (e2eComponentsExists()) { - // already generated the e2e directory - e2eWatch(folderInfo.componentName, folderInfo.componentTest); - - } else { - // generate the e2e directory - console.log('Generated e2e builds first...'); - e2eBuild(() => { - e2eWatch(folderInfo.componentName, folderInfo.componentTest); - }); - } -}); - -function e2eWatch(componentName: string, componentTest: string) { - // If any tests change within components then run e2e.resources. - watch([ - 'src/components/*/test/**/*' - ], - function(file) { - console.log('start e2e.resources - ' + JSON.stringify(file.history, null, 2)); - start('e2e.copyAndCompile'); - }); - - // If any src files change except for tests then transpile only the source ionic files - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function(file) { - console.log('start e2e.ngcSource - ' + JSON.stringify(file.history, null, 2)); - start('e2e.copyAndCompile'); - }); - - // If any scss files change then recompile all sass - watch(['src/**/*.scss'], (file) => { - console.log('start sass - ' + JSON.stringify(file.history, null, 2)); - start('e2e.sass'); - }); - - console.log(`http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${E2E_NAME}/components/${componentName}/test/${componentTest}/`); - +task('e2e.serve', function() { connect.server({ root: './', port: LOCAL_SERVER_PORT, - livereload: true + livereload: { + port: 35700 + } }); - - src('dist').pipe( - open({uri: `http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${E2E_NAME}`}) - ); -} - -function e2eComponentsExists(): boolean { - try { - accessSync(DIST_E2E_COMPONENTS_ROOT, F_OK); - } catch (e) { - return false; - } - return true; -} +}); diff --git a/scripts/gulp/tasks/snapshot.ts b/scripts/gulp/tasks/snapshot.ts index bb14e3ea82..f487a76736 100644 --- a/scripts/gulp/tasks/snapshot.ts +++ b/scripts/gulp/tasks/snapshot.ts @@ -7,23 +7,27 @@ import { task } from 'gulp'; import * as serveStatic from 'serve-static'; import { argv } from 'yargs'; -import { DIST_E2E_COMPONENTS_ROOT, PROJECT_ROOT, SCRIPTS_ROOT } from '../constants'; +import { DIST_E2E_ROOT, DIST_E2E_COMPONENTS_ROOT, PROJECT_ROOT, SCRIPTS_ROOT } from '../constants'; import { mergeObjects } from '../util'; -task('snapshot', ['e2e'], (done: Function) => { - snapshot(false, done); +task('snapshot', ['e2e.clean', 'e2e.prod'], (done: Function) => { + snapshot(false, false, done); }); task('snapshot.skipBuild', ['e2e.sass'], (done: Function) => { - snapshot(false, done); + snapshot(false, false, done); +}); + +task('snapshot.dev', ['e2e.clean', 'e2e'], (done: Function) => { + snapshot(false, true, done); }); task('snapshot.quick', ['e2e.sass'], (done: Function) => { - snapshot(true, done); + snapshot(true, true, done); }); -function snapshot(quickMode: boolean, callback: Function) { +function snapshot(quickMode: boolean, devMode: boolean, callback: Function) { const snapshotConfig = require('../../snapshot/snapshot.config').config; const protractorConfigFile = resolve(SCRIPTS_ROOT, 'snapshot/protractor.config.js'); @@ -45,7 +49,10 @@ function snapshot(quickMode: boolean, callback: Function) { e2eSpecs = folderArgPaths[1]; } } - const specs = join(DIST_E2E_COMPONENTS_ROOT, component, 'test', e2eSpecs, '*e2e.js'); + var specs = join(DIST_E2E_COMPONENTS_ROOT, component, 'test', e2eSpecs, '*e2e.js'); + if (devMode) specs = join(DIST_E2E_ROOT, component, e2eSpecs, '*e2e.js'); + + console.log('[snapshot] Running with', devMode ? 'Development' : 'Production', 'build'); console.log(`[snapshot] Specs: ${specs}`); const testId = generateTestId(); @@ -64,6 +71,7 @@ function snapshot(quickMode: boolean, callback: Function) { '--params.height=' + snapshotValues.params.height, '--params.test_id=' + snapshotValues.params.test_id, '--params.upload=' + snapshotValues.params.upload, + '--params.dev=' + devMode, '--specs=' + specs ]; diff --git a/scripts/snapshot/ionic.snapshot.js b/scripts/snapshot/ionic.snapshot.js index 61eabbdc3f..0ab8d9d7e8 100644 --- a/scripts/snapshot/ionic.snapshot.js +++ b/scripts/snapshot/ionic.snapshot.js @@ -15,7 +15,8 @@ var IonicSnapshot = function(options) { self.domain = options.domain || 'ionic-snapshot-go.appspot.com'; self.groupId = options.groupId || 'test_group'; self.appId = options.appId || 'test_app'; - self.sleepBetweenSpecs = options.sleepBetweenSpecs || 500; + self.build = (browser.params.dev === 'true') ? 'Development' : 'Production'; + self.sleepBetweenSpecs = self.build === 'Development' ? 3000 : (options.sleepBetweenSpecs || 500); self.testId = browser.params.test_id || 'test_id'; self.shouldUpload = browser.params.upload !== 'false'; self.platformId = browser.params.platform_id; @@ -83,7 +84,7 @@ var IonicSnapshot = function(options) { } }); - log(colors.green('Start Snapshot:'), + log(colors.green('Start ' + self.build + ' Snapshot - ' + self.sleepBetweenSpecs + ' between Specs:'), self.groupId, self.appId, self.platformId, self.testId, '(' + self.width + 'x' + self.height + ')'); }; @@ -114,10 +115,9 @@ var IonicSnapshot = function(options) { var specIdString = '[' + (spec.id+1) + '/' + self.testData.total_specs + ']'; self.testData.spec_index = spec.id; - self.testData.description = spec.getFullName().replace('/test/', '/'); self.testData.highest_mismatch = self.highestMismatch; self.testData.png_base64 = pngBase64; - self.testData.description = spec.getFullName().replace('test/', ''); + self.testData.description = spec.getFullName().replace('components/', '').replace('test/', ''); self.testData.url = currentUrl.replace('dist', '').replace('components/', '').replace('test/', '').replace('&ionicanimate=false', ''); pngBase64 = null;