From 4b236a2a6456342692b71c6f8813a0c5be2b18a2 Mon Sep 17 00:00:00 2001 From: martinmicunda Date: Wed, 1 Oct 2014 10:26:42 +0100 Subject: [PATCH] add gulp file --- .bowerrc | 4 +- .editorconfig | 9 +- .gitignore | 35 +- .jshintignore | 3 +- .jshintrc | 7 +- README.md | 107 ++- config.tpl.ejs | 11 + gulp-workflow.png | Bin 0 -> 75713 bytes gulpfile.js | 924 ++++++++++---------- package.json | 155 ++-- src/app/{app_test.js => app.spec.js} | 2 +- src/styles/_variables.scss | 32 + src/styles/base/_base.scss | 29 + src/styles/main.scss | 6 + src/styles/modules/_footer.scss | 12 + src/styles/modules/_header.scss | 110 +++ test/config/karma.conf.js | 108 +-- test/config/protractor.conf.js | 69 +- test/e2e/{example_e2e.js => example.e2e.js} | 12 +- 19 files changed, 891 insertions(+), 744 deletions(-) create mode 100644 config.tpl.ejs create mode 100644 gulp-workflow.png rename src/app/{app_test.js => app.spec.js} (98%) mode change 100755 => 100644 create mode 100644 src/styles/_variables.scss create mode 100644 src/styles/base/_base.scss create mode 100644 src/styles/main.scss create mode 100644 src/styles/modules/_footer.scss create mode 100644 src/styles/modules/_header.scss rename test/e2e/{example_e2e.js => example.e2e.js} (89%) diff --git a/.bowerrc b/.bowerrc index 55456609..44a1c2b7 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "client/src/vendor" -} \ No newline at end of file + "directory": "src/vendor" +} diff --git a/.editorconfig b/.editorconfig index fe66af08..75a0881d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,12 +2,19 @@ root = true [*] +# change these settings to your own preference indent_style = space indent_size = 4 + +# it's recommend to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false + +[{package,bower}.json] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index e8ed2bfc..8ff0cad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,37 @@ -lib-cov -*.seed +# Logs +logs *.log -*.csv -*.dat -*.out -*.pid -*.gz +# Runtime data +pids +*.pid +*.seed .DS_Store -pids -logs -results +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +# Dependency directory npm-debug.log node_modules # IDEs .idea/ -# 3rd dependencies +# Bower 3rd dependencies src/vendor/ -# build and tmp folders for production +# Build and tmp folders for production build/ src/.tmp/ -# ignore angular env file -src/app/core/config/env/config-env.js +# Ignore angular env file +src/app/core/config/core.env.js + +# Test reports +test/test-reports/ -gh-pages/ +.sass-cache diff --git a/.jshintignore b/.jshintignore index a80a3c52..3fc490c5 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,3 +1,2 @@ -src/app/config/config-env.js node_modules/**/* -build +src/vendor/**/* diff --git a/.jshintrc b/.jshintrc index 75e96f0c..adfe7d3b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,5 +1,5 @@ { - "node": false, + "node": true, "browser": true, "esnext": true, "bitwise": true, @@ -12,13 +12,12 @@ "newcap": true, "noarg": true, "quotmark": "single", - "regexp": true, "undef": true, "unused": true, "strict": true, "trailing": true, "smarttabs": true, - "maxlen": 80, + "jquery": true, "globals": { "ApplicationConfiguration": false, "angular": false, @@ -32,6 +31,6 @@ "inject": false, "it": false, "spyOn": false, - "_": false + "protractor": false } } diff --git a/README.md b/README.md index 355e9f53..056694ad 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,109 @@ employee-scheduling-ui ====================== [![Build Status](https://secure.travis-ci.org/martinmicunda/employee-scheduling-ui.png)](http://travis-ci.org/martinmicunda/employee-scheduling-ui) [![Dependency Status](https://david-dm.org/martinmicunda/employee-scheduling-ui.png)](https://david-dm.org/martinmicunda/employee-scheduling-ui) [![devDependency Status](https://david-dm.org/martinmicunda/employee-scheduling-ui/dev-status.png)](https://david-dm.org/martinmicunda/employee-scheduling-ui#info=devDependencies) [![Coverage Status](https://coveralls.io/repos/martinmicunda/employee-scheduling-ui/badge.png?branch=master)](https://coveralls.io/r/martinmicunda/employee-scheduling-ui?branch=master) -An UI component for Employee Scheduling application. +An UI component for [Employee Scheduling](https://github.com/martinmicunda/employee-scheduling) application. + +## Gulp Tasks +The best way to learn about the gulp tasks is by familiarizing yourself with [Gulp](http://gulpjs.com/) and then reading through the documented [gulpfile.js](gulpfile.js) script. + +![Gulp Workflow](gulp-workflow.png "Gulp Workflow") + +###Sub Tasks:### +| Command | Description | +| -------------------------------- | ----------- | +| `gulp clean` | will delete `build` and `.tmp` directories | +| `gulp extras` | will copy project files that haven't been copied by `compile` task e.g. (bower.json, favicon, etc.) into the `build/dist` directory | +| `gulp bower-install` | will install all bower components specify in [`bower.json`](bower.json) from bower repository and inject bower components into the [`index.html`](src/index.html) | +| `gulp config` | will configuration Angular app for development or production environment | +| `gulp jshint` | will run linter against javascript files | +| `gulp htmlhint` | will run linter against html files | +| `gulp styles` | will compile sass files into the `src/.tmp/main.css` | +| `gulp templatecache` | will replace local links with CDN links for images inside of templates, minify html templates and then put all templates into strings in a JavaScript file that will add the template to AngularJS's [`$templateCache`](http://docs.angularjs.org/api/ng.$templateCache) so template files are part of the initial JavaScript payload and do not require any future XHR. The template cache files are `src/.tmp/scripts/templates.js` | +| `gulp watch` | will watching for file changes, in addition browser will be refreshed with every change | +| `gulp fonts` | will copies fonts to `build/dist` directory | +| `gulp images` | will minifies and copies images to `build/dist` directory | +| `gulp compile` | will concatenate, minify, cdnize and revesion sources and place them by default into the `build/dist` directory | +| `gulp setup` | will configure environment, compile SASS to CSS, install bower dependencies and inject installed bower dependencies into the `index.html` | +| `gulp help` | will print all tasks with description of each task | + +###Main Tasks:### +The main gulp tasks are descriptive more in `development`, `test`, `build` and `release` sections. + +| Command | Description | +| -------------------------------- | ----------- | +| `gulp` | will launch the browser, install bower dependencies, build dev environment, monitor the source files and run styles, jshint and htmlhint tasks every time a file changes. | +| `gulp test:unit` | will run unit tests | +| `gulp test:e2e` | will run e2e tests | +| `gulp build` | will create build that is ready for uploading to the server | +| `gulp bump` | will increment version number in [`package.json`](package.json) and [`bower.json`](bower.json)| +| `gulp changelog` | will generate changelog in [`CHANGELOG.md`](CHANGELOG.md) | +| `gulp release` | will release and push [`package.json`](package.json) and [`CHANGELOG.md`](CHANGELOG.md) to GitHub repo | + +## Development +Whenever you're working on project, start with: + +```bash +$ gulp +``` +> **NOTE:** The `gulp` task is alias for `gulp:serve`. + +This default gulp task will launch the browser, install bower dependencies, build dev environment, monitor the source files and run `styles`, `jshint` and `htmlhint` tasks every time a file changes. The default gulp task also includes [Live Reload](http://livereload.com/), so you no longer have to refresh your page after making changes! The following code must be add to the end of the `body` tag in [index.html](src/index.html): + +```html + +``` + +## Test +The `test:unit` and `test:e2e` tasks have optional arguments `--browsers=(PhantomJS|Chrome|Firefox|Safari)` and `--env=(development|production)`. If you don't specify these optional arguments then the default value for `--browsers` is `PhantomJS` and default value for `--env` is `development`. + +App test-reports (coverage, failure screenshots etc.) can be found under `src/test/test-reports/` directory. + +* **Unit test examples:** + + This will run test against `PhantomJS` and code specify in `src/` folder: + ```bash + $ gulp test:unit + ``` + This will run test against `Chrome`: + ```bash + $ gulp test:unit --browsers=Chrome + ``` + To run test against multiple browsers at the same time run follow task: + **TODO: (martin)** this task is not supported yet! + ```bash + $ gulp test:unit --browsers=Chrome,Firefox,Safari + ``` + This will watch for file changes and re-run tests on each change: + ```bash + $ gulp test:unit --watch + ``` + > **NOTE:** Verify that the browsers you want to run test against are installed on your local machine. The `PhantomJS` should be already installed after you run `npm install`. + +* **E2E test examples:** + + This will run test against `PhantomJS` and code specify in `src/ `folder: + **TODO (martin)** at the moment default browser is set to Chrome because there is an issue with PhantomJS and protractor + ```bash + $ gulp test:e2e + ``` + + This will run test against `Chrome` and code specify in `build/dist` folder: + ```bash + $ gulp test:e2e --browsers=Chrome --env=production + ``` + + > **NOTE:** Verify that you are running application before executing the above command. On first run this might take a while as Protractor's webdriver must be downloaded and installed. + +## Build +The build task get app ready for production. The build task include concatenation, minification, compression, cdn etc. If there have been no errors when executing the build command, the build should be located in `build/dist` directory and this build is ready for uploading to the server!. To initiate a full build, you simply run the follow task: +```bash +$ gulp build +``` + +If you want use CDN add your CDN url to `PRODUCTION_CDN_URL` variable in [`gulpfile.js`](gulpfile.js) file and then run the build task with argument `--cdn` to replace local path with CDN url: +```bash +$ gulp build --cdn +``` + +## Release +TODO: add release steps diff --git a/config.tpl.ejs b/config.tpl.ejs new file mode 100644 index 00000000..69533f0a --- /dev/null +++ b/config.tpl.ejs @@ -0,0 +1,11 @@ +/*jshint -W109 */ +(function () { +'use strict'; + +angular.module('<%- moduleName %>') + +<% constants.forEach(function(constant) { %> +.constant('<%- constant.name %>', <%= constant.value %>) +<% }) %>; + +})(); diff --git a/gulp-workflow.png b/gulp-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..3a74bcc22c3b6ad034f772f4f7849cb05e1f97f5 GIT binary patch literal 75713 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℘? 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%0DA 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 literal 0 HcmV?d00001 diff --git a/gulpfile.js b/gulpfile.js index 3c44ef15..c1056d83 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,147 +1,121 @@ +/** + * The MIT License + * + * Copyright (c) 2014 Martin Micunda + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/*jshint camelcase: false */ 'use strict'; -// TODO: check this gulpfile https://github.com/angular/material/blob/master/gulpfile.js and also John pappa gulp file!!! //============================================= -// DECLARE CORE VARIABLES +// DEPENDENCIES //============================================= /** - * Load required core dependencies. These are installed based on the versions listed - * in 'package.json' when you do 'npm install' in this directory. + * Load required dependencies. */ -var fs = require('fs'); -var argv = require('minimist')(process.argv.slice(2)); -var gulp = require('gulp'); -var semver = require('semver'); -var browser = require('tiny-lr')(); -var wiredep = require('wiredep').stream; -var changelog = require('conventional-changelog'); -var runSequence = require('run-sequence'); - - -//============================================= -// DECLARE GULP VARIABLES -//============================================= +var fs = require('fs'); +var pkg = require('./package.json'); +var path = require('path'); +var gulp = require('gulp'); +var bower = require('bower'); +var karma = require('karma').server; +var semver = require('semver'); +var wiredep = require('wiredep').stream; +var changelog = require('conventional-changelog'); +var runSequence = require('run-sequence'); +var mainBowerFiles = require('main-bower-files'); /** - * Load required Gulp tasks. These are installed based on the versions listed - * in 'package.json' when you do 'npm install' in this directory. + * Load Gulp plugins listed in 'package.json' and attaches + * them to the `$` variable. */ -var pkg = require('./package.json'); -var nib = require('nib'); -var rev = require('gulp-rev'); -var exec = require('gulp-exec'); -var size = require('gulp-size'); -var bump = require('gulp-bump'); -var help = require('gulp-help')(gulp); -var cache = require('gulp-cache'); -var gutil = require('gulp-util'); -var bower = require('gulp-bower'); -var clean = require('gulp-clean'); -var karma = require('gulp-karma'); -var watch = require('gulp-watch'); -var inject = require('gulp-inject'); -var stylus = require('gulp-stylus'); -var gulpif = require('gulp-if'); -var concat = require('gulp-concat'); -var jshint = require('gulp-jshint'); -var header = require('gulp-header'); -var uglify = require('gulp-uglify'); -var usemin = require('gulp-usemin'); -var nodemon = require('gulp-nodemon'); -var ghPages = require("gulp-gh-pages"); -var refresh = require('gulp-livereload'); -var cdnizer = require("gulp-cdnizer"); -var bytediff = require('gulp-bytediff'); -var htmlhint = require("gulp-htmlhint"); -var imagemin = require('gulp-imagemin'); -var minifyCss = require('gulp-minify-css'); -var preprocess = require('gulp-preprocess'); -var protractor = require("gulp-protractor").protractor; -var ngConstant = require('gulp-ng-constant'); -var minifyHtml = require('gulp-minify-html'); -var sourcemaps = require('gulp-sourcemaps'); -var ngAnnotate = require('gulp-ng-annotate'); -var autoprefixer = require('gulp-autoprefixer'); -var templateCache = require('gulp-angular-templatecache'); -var coverageEnforcer = require("gulp-istanbul-enforcer"); -var webdriver_update = require('gulp-protractor').webdriver_update; -var webdriver_standalone = require('gulp-protractor').webdriver_standalone; +/*jshint -W079 */ +var $ = require('gulp-load-plugins')(); //============================================= -// DECLARE CONSTANTS +// !!!FEEL FREE TO EDIT THESE VARIABLES!!! //============================================= -/** - * Declare environment constants that are use in gulpfile.js - */ -var PRODUCTION_URL = 'http://rightdestinations.com'; -var DEVELOPMENT_URL = 'http://127.0.0.1:3000'; -var PRODUCTION_CDN_URL = 'http://127.0.0.1:5000'; - -/** - * Declare constants that are use in gulpfile.js or angular app - */ -var ENV = !!argv.env ? argv.env : 'development'; -var COLORS = gutil.colors; -var BROWSERS = !!argv.browsers ? argv.browsers : 'PhantomJS'; -var CDN_BASE = !!argv.nocdn ? DEVELOPMENT_URL : PRODUCTION_CDN_URL; -var MODULE_NAME = pkg.name; +var MODULE_NAME = 'locationManager'; var API_VERSION = '1.0'; -var GIT_REMOTE_URL = 'https://'+ process.env.GH_TOKEN +'@github.com/martinmicunda/employee-scheduling.git'; // git@github.com:martinmicunda/employee-scheduling.git -var LIVERELOAD_PORT = 35729; +var PRODUCTION_URL = 'http://your-production-url.com'; +var DEVELOPMENT_URL = 'http://127.0.0.1:3000'; +var PRODUCTION_CDN_URL = 'http://cdn.your-production-url.com'; var TEMPLATE_BASE_PATH = 'app'; -var BUILD_WITHOUT_TEST = !!argv.notest ? true : false; -var APPLICATION_BASE_URL = ENV === 'development' ? DEVELOPMENT_URL : PRODUCTION_URL; //============================================= // DECLARE VARIABLES //============================================= + /** - * Declare variables that are use in gulpfile.js + * Declare variables that are use in gulpfile.js or angular app */ -var hasGitChanges = ''; -var isWatching = false; +var log = $.util.log; +var argv = $.util.env; +var ENV = !!argv.env ? argv.env : 'development'; +var COLORS = $.util.colors; +var BROWSERS = !!argv.browsers ? argv.browsers : 'PhantomJS'; +var CDN_BASE = !!argv.cdn ? PRODUCTION_CDN_URL : DEVELOPMENT_URL; +var APPLICATION_BASE_URL = ENV ? PRODUCTION_URL : DEVELOPMENT_URL; //============================================= -// COMMAND LINE ERROR HANDLING +// COMMAND LINE ERROR HANDLING //============================================= if(!ENV.match(new RegExp(/production|development/))) { - gutil.log(COLORS.red('Error: The argument \'env\' has incorrect value \'' + ENV +'\'! Usage: gulp test:unit --env=(development|production)')); - return process.exit(1); + log(COLORS.red('Error: The argument \'env\' has incorrect value \'' + ENV +'\'! Usage: gulp test:e2e --env=(development|production)')); + return process.exit(1); } if(!BROWSERS.match(new RegExp(/PhantomJS|Chrome|Firefox|Safari/))) { - gutil.log(COLORS.red('Error: The argument \'browsers\' has incorrect value \'' + BROWSERS +'\'! Usage: gulp test:unit --env=(PhantomJS|Chrome|Firefox|Safari)')); - return process.exit(1); + log(COLORS.red('Error: The argument \'browsers\' has incorrect value \'' + BROWSERS +'\'! Usage: gulp test:unit --browsers=(PhantomJS|Chrome|Firefox|Safari)')); + return process.exit(1); } //============================================= // PRINT INFO MESSAGE //============================================= -gutil.log(gutil.colors.blue('********** RUNNING IN ' + ENV + ' ENVIROMENT **********')); +log(COLORS.blue('********** RUNNING IN ' + ENV + ' ENVIROMENT **********')); //============================================= // UTILS FUNCTIONS //============================================= -function bytediffFormatter(data) { - var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; - return COLORS.yellow(data.fileName + ' went from ' - + (data.startSize / 1000).toFixed(2) + ' kB to ' + (data.endSize / 1000).toFixed(2) + ' kB' - + ' and is ' + formatPercent(1-data.percent, 2) + '%' + difference); -} - function formatPercent(num, precision){ return (num*100).toFixed(precision); } +function bytediffFormatter(data) { + var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; + return COLORS.yellow(data.fileName + ' went from ' + + (data.startSize / 1000).toFixed(2) + ' kB to ' + (data.endSize / 1000).toFixed(2) + ' kB' + + ' and is ' + formatPercent(1-data.percent, 2) + '%' + difference); +} + //============================================= // DECLARE PATHS @@ -153,8 +127,8 @@ var paths = { */ gulpfile: 'gulpfile.js', /** - * This is a collection of file patterns that refer to our client code (the - * stuff in `client/`). These file paths are used in the configuration of + * This is a collection of file patterns that refer to our app code (the + * stuff in `src/`). These file paths are used in the configuration of * build tasks. * * - 'styles' contains all project css styles @@ -163,70 +137,67 @@ var paths = { * - 'scripts' contains all project javascript except config-env.js and unit test files * - 'html' contains main html files * - 'templates' contains all project html templates - * - 'config' contains all client env config files - * - 'test' contains all project unit and e2e test code + * - 'config' contains Angular app config files */ - client: { - basePath: 'client/src/', - styles: 'client/src/styles/**/*.styl', - images: 'client/src/images/**/*.{png,gif,jpg,jpeg}', - fonts: 'client/src/fonts/**/*', - scripts: ['client/src/app/**/*.js', '!client/src/app/core/config/env/config-env.js', '!client/src/app/**/*_test.js'], - html: 'client/src/*.html', - templates: 'client/src/app/**/*.html', + app: { + basePath: 'src/', + fonts: 'src/fonts/**/*.{eot,svg,ttf,woff}', + styles: 'src/styles/**/*.scss', + images: 'src/images/**/*.{png,gif,jpg,jpeg}', + config: 'src/app/core/config/', + scripts: ['src/app/**/*.js', '!src/app/**/*.spec.js'], + html: 'src/index.html', + templates: 'src/app/**/*.html' + }, + /** + * This is a collection of file patterns that refer to our app unit and e2e tests code. + * + * 'config' contains karma and protractor config files + * 'testReports' contains unit and e2e test reports + * 'unit' contains all project unit test code + * 'e2e' contains all project e2e test code + */ + test: { + basePath: 'test/', config: { - basePath: 'client/src/app/core/config/env/', - file: 'client/src/app/core/config/env/config-env.json', - template: 'client/src/app/core/config/env/config.tpl.ejs' + karma: 'test/config/karma.conf.js', + protractor: 'test/config/protractor.conf.js' }, - test: { - unit: 'client/src/app/**/*_test.js', - e2e: 'client/test/e2e/**/*_e2e.js' - } + testReports: { + coverage: 'test/test-reports/coverage/' + }, + unit: 'src/app/**/*.spec.js', + e2e: 'test/e2e/**/*.e2e.js' }, - server: 'server/src/**/*', /** * The 'vendor' folder is where our bower dependencies are hold. */ - vendor: 'client/src/vendor/', + vendor: 'src/vendor/', /** * The 'tmp' folder is where our html templates are compiled to JavaScript during * the build process and then they are concatenating with all other js files and * copy to 'dist' folder. */ tmp: { - basePath: 'client/src/.tmp', - scripts: 'client/src/.tmp/scripts/', - styles: 'client/src/.tmp/styles/' + basePath: 'src/.tmp/', + styles: 'src/.tmp/styles/', + scripts: 'src/.tmp/scripts/' }, /** * The 'build' folder is where our app resides once it's * completely built. * * - 'dist' application distribution source code - * - 'testReports' application test reports (coverage, failure screenshots etc.) * - 'docs' application documentation */ build: { basePath: 'build/', dist: { - basePath: 'build/dist/', - client: { - basePath: 'build/dist/client/', - images: 'build/dist/client/assets/images/', - fonts: 'build/dist/client/assets/fonts/' - }, - server: { - basePath: 'build/dist/server/' - } - }, - testReports: { - client: { - coverage: 'build/test-reports/client/coverage/' - }, - server: { - coverage: '' - } + basePath: 'build/dist/', + fonts: 'build/dist/fonts/', + images: 'build/dist/images/', + styles: 'build/dist/styles/', + scripts: 'build/dist/scripts/' }, docs: 'build/docs/' } @@ -239,189 +210,187 @@ var paths = { /** * The banner is the comment that is placed at the top of our compiled - * source files. It is first processed as a Grunt template, where the `<%=` + * source files. It is first processed as a Gulp template, where the `<%=` * pairs are evaluated based on this very configuration object. */ -var banner = gutil.template('/**\n' + +var banner = $.util.template( + '/**\n' + ' * <%= pkg.description %>\n' + ' * @version v<%= pkg.version %> - <%= today %>\n' + ' * @link <%= pkg.homepage %>\n' + ' * @author <%= pkg.author.name %>\n' + ' * @copyright <%= year %>(c) <%= pkg.author.name %>\n' + - ' * @license <%= pkg.licenses.type %>, <%= pkg.licenses.url %>\n' + + ' * @license <%= pkg.license.type %>, <%= pkg.license.url %>\n' + ' */\n', {file: '', pkg: pkg, today: new Date().toISOString().substr(0, 10), year: new Date().toISOString().substr(0, 4)}); +//============================================= +// HELPER +//============================================= + +/** + * Add the ability to provide help text to custom gulp tasks. Usage: `gulp help` + */ +$.help(gulp); + + //============================================= // SUB TASKS //============================================= -gulp.task('develop', function () { - var options = { - script: 'server/src/server.js', - ext: 'js json', - ignore: ['client/**', 'node_modules/**'], - stdout: false, - stderr: false, - nodeArgs: ['--debug'], - watch: ["server/**/*"] - }; +/** + * The 'clean' task delete 'build' and '.tmp' directories. + */ +gulp.task('clean', 'Delete \'build\' and \'.tmp\' directories', function () { + return gulp.src([paths.build.basePath, paths.tmp.basePath], {read: false}) // Not necessary to read the files (will speed up things), we're only after their paths + .pipe($.rimraf({force: true})); +}); - nodemon(options) - .on('change', ['jshint:server']) - .on('restart', function (files) { - gutil.log('[server] App restarted due to: ', COLORS.cyan(files)); - }).on('stdout', function(raw) { - var msg = raw.toString('utf8'); - gutil.log('[server]', COLORS.green(msg)); - if(msg.indexOf('avisi-website has started') !== -1) { - refresh(browser) - } - }).on('stderr', function(err) { - var msg = err.toString('utf8'); - - // For some reason debugger attachment gets logged on 'stderr', so we catch it here... - if (msg.indexOf('debugger listening on port') === 0) { - gutil.log('[server]', COLORS.green(msg)); - } else { - gutil.log('Node server ' + COLORS.red(err)); - } +/** + * The 'bower-install' task install all bower components specify in `bower.json` + * from bower repository and inject bower components into the `index.html`. + */ +gulp.task('bower-install', 'Install all bower dependencies specify in bower.json and inject them into the index.html', function () { + bower.commands.install([], {}, {}) + .on('end', function(){ + return gulp.src(paths.app.html) + .pipe(wiredep({ + directory: paths.vendor, + ignorePath: paths.app.basePath + })) + .pipe(gulp.dest(paths.app.basePath)) + .pipe($.size({title: 'bower'})); + }) + .on('error', function (error) { + log(COLORS.red(error)); + return process.exit(1); }); }); /** - * The 'clean' task delete 'build' and 'client/src/.tmp' directories. + * The 'config' task configuration Angular app for development or production environment. */ -gulp.task('clean', 'Delete \'build\' and \'client/src/.tmp\' directories', function () { - return gulp.src([paths.build.basePath, paths.tmp.basePath], {read: false}) // Not necessary to read the files (will speed up things), we're only after their paths - .pipe(clean()); +gulp.task('config', 'Configuration Angular app for development or production environment', function () { + return gulp.src('') + .pipe($.ngConstant({ + name: MODULE_NAME, + templatePath: './config.tpl.ejs', + constants: { + env: { + name: ENV, + apiVersion: API_VERSION + } + } + })) + .pipe($.rename('core.env.js')) + .pipe(gulp.dest(paths.app.config)); }); /** - * The 'copy' task just copies files from A to B. We use it here to copy - * our files that haven't been copied by 'compile' task e.g. (fonts, etc.) into 'build`. + * The 'jshint' task defines the rules of our hinter as well as which files + * we should check. It helps to detect errors and potential problems in our + * JavaScript code. */ -gulp.task('copy', 'Copy project files that haven\'t been copied by \'compile\' task e.g. (fonts, etc.) into \'build\' folder.', function () { - gulp.src([ - 'client/src/*.{ico,png,txt}' - ]) - .pipe(gulp.dest(paths.build.dist.client.basePath)); - gulp.src(paths.client.fonts) - .pipe(gulp.dest(paths.build.dist.client.fonts)); - gulp.src(paths.server) - .pipe(gulp.dest(paths.build.dist.server.basePath)); - gulp.src(['package.json', 'Procfile']) - .pipe(gulp.dest(paths.build.dist.basePath)); +gulp.task('jshint', 'Hint JavaScripts files', function () { + return gulp.src(paths.app.scripts.concat(paths.gulpfile)) + .pipe($.jshint('.jshintrc')) + .pipe($.jshint.reporter('jshint-stylish')) + .pipe($.jshint.reporter('fail')); }); /** - * The 'stylus' compile and compress stylus files to main.css. + * The 'htmlhint' task defines the rules of our hinter as well as which files we + * should check. It helps to detect errors and potential problems in our + * HTML code. */ -gulp.task('stylus', 'Compile and compress stylus files to main.css', function () { - gulp.src(paths.client.styles) - .pipe(stylus({errors: true, compress: true, use: [nib()]})) - .pipe(concat('main.css')) - .pipe(gulp.dest(paths.tmp.styles)) - .pipe(refresh(browser)) - .pipe(size()); +gulp.task('htmlhint', 'Hint HTML files', function () { + return gulp.src([paths.app.html, paths.app.templates]) + .pipe($.htmlhint('.htmlhintrc')) + .pipe($.htmlhint.failReporter()); }); /** - * The 'jshint' task defines the rules of our hinter for client as well as which files we - * should check. This file, all javascript sources. + * Compile SASS files into the main.css. */ -gulp.task('jshint:client', 'Hint client JavaScripts files', function () { - return gulp.src(paths.client.scripts) - .pipe(jshint('client/.jshintrc')) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(gulpif(!isWatching, jshint.reporter('fail'))) - .pipe(refresh(browser)) - .pipe(size()); +gulp.task('styles', 'Compile sass files into the main.css', function () { + // if it's set to `true` the gulp.watch will keep gulp from stopping + // every time we mess up sass files + var errLogToConsole = ENV === 'development'; + return gulp.src(paths.app.styles) + .pipe($.changed(paths.tmp.styles, {extension: '.scss'})) + .pipe($.sass({style: 'compressed', errLogToConsole: errLogToConsole})) + .pipe($.autoprefixer('last 2 version')) + .pipe($.concat('main.css')) + .pipe(gulp.dest(paths.tmp.styles)); }); /** - * The 'jshint' task defines the rules of our hinter for server as well as which files we - * should check. This file, all javascript sources. + * The 'watch' task set up the checks to see if any of the files listed below + * change, and then to execute the listed tasks when they do. */ -gulp.task('jshint:server', 'Hint server JavaScripts files', function () { - return gulp.src(paths.server + 'js') - .pipe(jshint('server/.jshintrc')) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(gulpif(!isWatching, jshint.reporter('fail'))) - .pipe(refresh(browser)) - .pipe(size()); +gulp.task('watch', 'Watch files for changes', function () { + // Listen on port 35729 + $.livereload.listen(); + + // Watch images and fonts files + gulp.watch([paths.app.images, paths.app.fonts]).on('change', $.livereload.changed); + + // Watch css files + gulp.watch(paths.app.styles, ['styles']).on('change', $.livereload.changed); + + // Watch js files + gulp.watch(paths.app.scripts, ['jshint']).on('change', $.livereload.changed); + + // Watch html files + gulp.watch([paths.app.html, paths.app.templates], ['htmlhint']).on('change', $.livereload.changed); + + // Watch bower file + gulp.watch('bower.json', ['bower-install']).on('change', $.livereload.changed); }); /** - * The 'htmlhint' task defines the rules of our hinter as well as which files we - * should check. This file, all html sources. + * The 'copy' task just copies files from A to B. We use it here + * to copy our files that haven't been copied by other tasks + * e.g. (bower.json, favicon, etc.) into the `build/dist` directory. */ -gulp.task('htmlhint', 'Hint HTML files', function () { - var hasHtmlHintError = false; - - var errorReporter = function() { - if(!isWatching && hasHtmlHintError) { -// return process.exit(1); - } - }; +gulp.task('extras', 'Copy project files that haven\'t been copied by \'compile\' task e.g. (bower.json, favicon, etc.) into the \'build/dist\' directory', function () { + return gulp.src([paths.app.basePath + '*.{ico,png,txt}', 'bower.json']) + .pipe(gulp.dest(paths.build.dist.basePath)); +}); - return gulp.src([paths.client.html, paths.client.templates]) - .pipe(htmlhint('.htmlhintrc')) - .pipe(htmlhint.reporter(function(file) { - if(!file.htmlhint.success) { - var errorCount = file.htmlhint.errorCount; - var plural = errorCount === 1 ? '' : 's'; - gutil.log(COLORS.cyan(errorCount) + ' error' + plural + ' found in ' + COLORS.magenta(file.path)); - - file.htmlhint.messages.forEach(function(result){ - var message = result.error, - evidence = message.evidence, - line = message.line, - col = message.col, - detail = typeof message.line !== 'undefined' ? - COLORS.red('htmlhint L' + line) + COLORS.red(':') + COLORS.red('C' + col) : COLORS.yellow('GENERAL]'); - - if (col === 0) { - evidence = COLORS.yellow('?') + evidence; - } else if (col > evidence.length) { - evidence = COLORS.yellow(evidence + ' '); - } else { - evidence = evidence.slice(0, col - 1) + evidence[col - 1] + evidence.slice(col); - } - - gutil.log(COLORS.red('[') + detail + COLORS.red(']') + COLORS.red(' ' + message.message) + ' (' + message.rule.id + ')'); - gutil.log(COLORS.yellow(evidence)); - }); - hasHtmlHintError = true; - } - })) - .pipe(refresh(browser)) - .pipe(size()) - .on('end', errorReporter); +/** + * The 'fonts' task copies fonts to `build/dist` directory. + */ +gulp.task('fonts', 'Copy fonts to `build/dist` directory', function () { + return gulp.src(mainBowerFiles().concat(paths.app.fonts)) + .pipe($.filter('**/*.{eot,svg,ttf,woff}')) + .pipe($.flatten()) + .pipe(gulp.dest(paths.build.dist.fonts)) + .pipe($.size({title: 'fonts'})); }); /** - * The 'images' task minify all project images. + * The 'images' task minifies and copies images to `build/dist` directory. */ -gulp.task('images', 'Minify the images', function () { - return gulp.src(paths.client.images) - .pipe(cache(imagemin({ - optimizationLevel: 5, +gulp.task('images', 'Minifies and copies images to `build/dist` directory', function () { + return gulp.src(paths.app.images) + .pipe($.cache($.imagemin({ progressive: true, interlaced: true }))) - .pipe(gulp.dest(paths.build.dist.client.images)) - .pipe(size()); + .pipe(gulp.dest(paths.build.dist.images)) + .pipe($.size({title: 'images'})); }); /** - * The 'templates' task replace local links with CDN links, minify all project templates and create template cache js file. + * The 'templates' task replace local links with CDN links, minify + * all project html templates and create template cache js file. */ -gulp.task('templates', 'Minify html templates and create template cache js file', function() { - gulp.src(paths.client.templates) - .pipe(cdnizer({defaultCDNBase: CDN_BASE, relativeRoot: '/', files: ['**/*.{gif,png,jpg,jpeg}']})) - .pipe(minifyHtml({empty:true})) - .pipe(templateCache({ +gulp.task('templatecache', 'Minify html templates and create template cache js file', function() { + return gulp.src(paths.app.templates) + .pipe($.if(!!argv.cdn, $.cdnizer({defaultCDNBase: CDN_BASE, relativeRoot: '/', files: ['**/*.{gif,png,jpg,jpeg}']}))) + .pipe($.minifyHtml({empty:true})) + .pipe($.angularTemplatecache({ module: MODULE_NAME, root: TEMPLATE_BASE_PATH })) @@ -438,165 +407,135 @@ gulp.task('templates', 'Minify html templates and create template cache js file' * js_libs - minify, add revision number * html - replace local path with CDN url, minify */ -gulp.task('compile', 'Does the same as \'stylus\', \'jshint:client\', \'jshint:server\', \'htmlhint\', \'images\', \'templates\' tasks but also compile all JS, CSS and HTML files', - ['stylus', 'jshint:client', 'jshint:server', 'htmlhint', 'images', 'templates'], function () { - var projectHeader = header(banner); - - return gulp.src(paths.client.html) - .pipe(inject(gulp.src(paths.tmp.scripts + 'templates.js', {read: false}), - { - starttag: '', - ignorePath: [paths.tmp.basePath] - } - )) - .pipe(preprocess({context: { NODE_ENV: 'production'}})) // remove livereload library for production - .pipe(usemin({ - css: [cdnizer({defaultCDNBase: CDN_BASE, relativeRoot: 'assets/styles', files: ['**/*.{gif,png,jpg,jpeg}']}), autoprefixer('last 2 version'), minifyCss(), rev(), projectHeader], - css_libs: [minifyCss(), rev()], - js: [sourcemaps.init(), ngAnnotate({add: true, single_quotes: true, stats: true}), bytediff.start(), uglify(), bytediff.stop(bytediffFormatter), sourcemaps.write(), rev(), projectHeader], - js_libs: [bytediff.start(), uglify(), bytediff.stop(bytediffFormatter), rev()], - html: [cdnizer({defaultCDNBase: CDN_BASE, files: ['**/*.{js,css}']}), minifyHtml({empty:true})] - })) - .pipe(gulp.dest(paths.build.dist.client.basePath)) - .pipe(size({showFiles: true})); -}); - -/** - * The 'bower' task install all bower components specify in bower.json from bower repository. - */ -gulp.task('bower', 'Install all bower dependencies specify in bower.json from bower repository', function () { - return bower(); -}); - -/** - * The 'bower-install' does the same as 'bower' task but also inject bower components to index.html. - */ -gulp.task('bower-install', 'Does the same as \'bower\' task but also inject bower components to index.html', ['bower'], function () { - return gulp.src(paths.client.html) - .pipe(wiredep({ - directory: paths.vendor, - ignorePath: paths.client.basePath - })) - .pipe(gulp.dest(paths.client.basePath)) - .pipe(size()); -}); - -/** - * For rapid development, we have a watch set up that checks to see if - * any of the files listed below change, and then to execute the listed - * tasks when they do. This just saves us from having to type "gulp" into - * the command-line every time we want to see what we're working on; we can - * instead just leave "gulp watch" running in a background terminal. - */ -gulp.task('watch', 'Watch client files for changes', function () { - - // Listen on port 35729 - browser.listen(LIVERELOAD_PORT, function (err) { - if (err) { - return console.log(err) - }; - - gulp.watch([ - paths.client.html, - paths.client.templates, - paths.client.images, - paths.client.fonts - ], function (event) { - return gulp.src(event.path) - .pipe(refresh(browser)); - }); - - // Watch css files - gulp.watch(paths.client.styles, ['stylus']); - - // Watch js files - gulp.watch(paths.client.scripts, ['jshint:client']); - - // Watch js files - gulp.watch([paths.client.html, paths.client.templates], ['htmlhint']); - - // Watch bower file - gulp.watch('bower.json', ['bower-install']); - }); - -}); - -/** - * Configuration Angular app for development environment. - */ -gulp.task('config:dev', 'Configuration Angular app for development environment (pass env constants to angular app)', function () { - return gulp.src(paths.client.config.file) - .pipe(ngConstant({ - templatePath: paths.client.config.template, - constants: { ENV: {'name': 'development', 'apiVersion': API_VERSION, templateBasePath: 'app/'} } +gulp.task('compile', 'Does the same as \'jshint\', \'htmlhint\', \'images\', \'templates\' tasks but also compile all JS, CSS and HTML files', + ['jshint', 'htmlhint', 'templatecache', 'styles'], function () { + var projectHeader = $.header(banner); + + return gulp.src(paths.app.html) + .pipe($.inject(gulp.src(paths.tmp.scripts + 'templates.js', {read: false}), { + starttag: '', + ignorePath: [paths.app.basePath] })) - .pipe(gulp.dest(paths.client.config.basePath)); -}); - -/** - * Configuration Angular app for production environment. - */ -gulp.task('config:prod', 'Configuration Angular app for production environment (pass env constants to angular app)', function () { - return gulp.src(paths.client.config.file) - .pipe(ngConstant({ - templatePath: paths.client.config.template, - constants: { ENV: {'name': 'production', 'apiVersion': API_VERSION, templateBasePath: TEMPLATE_BASE_PATH + '/', 'cdnBaseUrl': CDN_BASE} } + .pipe($.usemin({ + css: [ + $.if(!!argv.cdn, $.cdnizer({defaultCDNBase: CDN_BASE, relativeRoot: 'styles', files: ['**/*.{gif,png,jpg,jpeg}']})), + $.bytediff.start(), + $.minifyCss(), + $.bytediff.stop(bytediffFormatter), + $.rev(), + projectHeader + ], + css_libs: [ + $.bytediff.start(), + $.minifyCss(), + $.bytediff.stop(bytediffFormatter), + $.rev() + ], + js: [ + $.sourcemaps.init(), + $.ngAnnotate({add: true, single_quotes: true, stats: true}), + $.bytediff.start(), + $.uglify(), + $.bytediff.stop(bytediffFormatter), + $.rev(), + projectHeader, + $.sourcemaps.write('../dist/maps', {sourceMappingURLPrefix: 'https://asset-host.example.com/assets'}) + ], + js_libs: [ + $.bytediff.start(), + $.uglify(), + $.bytediff.stop(bytediffFormatter), + $.rev() + ], + html: [ + $.if(!!argv.cdn, $.cdnizer({defaultCDNBase: CDN_BASE, files: ['**/*.{js,css}']})), + $.bytediff.start(), + $.minifyHtml({empty:true}), + $.bytediff.stop(bytediffFormatter) + ] })) - .pipe(gulp.dest(paths.client.config.basePath)); + .pipe(gulp.dest(paths.build.dist.basePath)) + .pipe($.size({title: 'compile', showFiles: true})); }); /** - * Update/install the selenium webdriver. - */ -gulp.task('webdriver_update', 'Update/install the selenium webdriver', webdriver_update); - -/** - * Check if there are any changes to commit. + * The 'karma' task run unit tests without coverage check. */ -gulp.task('check', 'Check if there are any changes to commit', function (cb) { - require('child_process').exec('git status --porcelain', function (err, stdout) { - hasGitChanges = stdout; - cb(err); - }); +//TODO: (martin) should I merge this with `gulp test:unit` task and then just pass argument --coverage (it will run unit test with coverage e.g. gulp test:unit --coverage) +gulp.task('karma', 'Run unit tests without coverage check', function (cb) { + // remove 'coverage' directory before each test + gulp.src(paths.test.testReports.coverage, {read: false}) + .pipe($.rimraf({force: true})) + .on('finish', function() { + // run the karma test + karma.start({ + configFile: path.join(__dirname, paths.test.config.karma), + browsers: [BROWSERS], + singleRun: !argv.watch, + autoWatch: !!argv.watch + }, function(code) { + // make sure failed karma tests cause gulp to exit non-zero + if(code === 1) { + log(COLORS.red('Error: unit test failed ')); + return process.exit(1); + } + cb(); + }); + }); }); /** - * Publish 'build' folder to GitHub 'gh-pages' branch. + * The 'setup' task is to configure environment, compile SASS to CSS, + * install bower dependencies and inject installed bower dependencies + * into the `index.html`. */ -gulp.task('gh-pages', 'Publish \'build\' folder to GitHub \'gh-pages\' branch', function () { - gulp.src(paths.build.basePath + '**/*') - .pipe(ghPages(GIT_REMOTE_URL)); +gulp.task('setup', 'Configure environment, compile SASS to CSS and install bower dependencies', function () { + return gulp.start( + 'bower-install', + 'config', + 'styles' + ); }); - //============================================= // MAIN TASKS //============================================= +//--------------------------------------------- +// DEVELOPMENT TASKS +//--------------------------------------------- + /** - * The 'install' task is to build env and install bower. + * The 'serve' task serve the dev environment. */ -gulp.task('install', 'Build env and install bower', function (cb) { - runSequence(['bower-install', 'config:dev'], cb); +gulp.task('serve', 'Serve for the dev environment', ['setup', 'watch'], function() { + gulp.src(paths.app.basePath) + .pipe($.webserver({ + fallback: 'index.html', + open: true + })); }); +gulp.task('default', 'Watch files and build environment', ['serve']); /** - * The 'default' task is to build env, install bower dependencies and run watch. + * The 'serve:dist' task serve the prod environment. */ -gulp.task('default', 'Build env, install bower dependencies and run watch', function (cb) { - // set to 'true' to avoid process exit on error for jshint and htmlhint - isWatching = true; - - runSequence(['bower-install', 'config:dev'], - ['stylus', 'jshint:client', 'jshint:server', 'htmlhint', 'watch'], - cb); +gulp.task('serve:dist', 'Serve the prod environment', ['setup'], function() { + gulp.src(paths.build.dist.basePath) + .pipe($.webserver({ + fallback: 'index.html', + open: true + })); }); +//--------------------------------------------- +// TEST TASKS +//--------------------------------------------- + /** - * Run unit tests. + * The 'test:unit' task run unit tests with coverage check. */ -// TODO (martin): there is an issue in 'gulp-karma' when this plugin doesn't pull file list from karma.conf.js https://github.com/lazd/gulp-karma/issues/9 and './idontexist' it's just workaround for now -gulp.task('test:unit', 'Run unit tests', function () { +gulp.task('test:unit', 'Run unit tests with coverage check', ['karma'], function () { var options = { thresholds : { statements : 95, @@ -604,140 +543,155 @@ gulp.task('test:unit', 'Run unit tests', function () { lines : 95, functions : 95 }, - coverageDirectory : paths.build.testReports.client.coverage, - rootDirectory : 'build/' + coverageDirectory: paths.test.testReports.coverage, + rootDirectory : paths.test.basePath // keep root `test/` so enforce plugin is not searching in other directories }; - - // remove 'coverage' directory before each test - gulp.src(paths.build.testReports.client.coverage, {read: false}) - .pipe(clean()); - - return gulp.src('./idontexist') - .pipe(karma({ - configFile: 'client/test/config/karma.conf.js', - action: 'run', - browsers: [BROWSERS], - env: ENV - })) -// .pipe(coverageEnforcer(options)) - .on('error', function (error) { - gutil.log(COLORS.red('Error: Unit test failed ' + error)); + return gulp.src('.') + .pipe($.istanbulEnforcer(options)) + .on('error', function(error) { + log(error.toString()); return process.exit(1); }); +}, { + options: { + 'browsers=' : 'run test against specific browser (PhantomJS|Chrome|Firefox|Safari)', + 'watch': 'watch for file changes and re-run tests on each change' + } }); /** - * Run e2e tests. + * The 'test:e2e' task run e2e tests. */ +// update/install webdriver +gulp.task('webdriver_update', false, $.protractor.webdriver_update); gulp.task('test:e2e', 'Run e2e tests', ['webdriver_update'], function () { - //TODO: (martin) remove this code once the issue with PhantomJS is resolved. This code is already declared at the top of this file. + //TODO: (martin) remove this line once the issue with PhantomJS is resolved. This code is already declared at the top of this file. var BROWSERS = !!argv.browsers ? argv.browsers : 'chrome'; -// if(!BROWSERS.match(new RegExp(/phantomjs|chrome|firefox|safari/))) { -// gutil.log(COLORS.red('Error: The argument \'browsers\' has incorrect value \'' + BROWSERS +'\'! Usage: gulp test:unit --env=(phantomjs|chrome|firefox|safari)')); -// return process.exit(1); -// } - - //TODO: (martin) might also use this plugin https://www.npmjs.org/package/gulp-protractor-qa - gulp.src('./idontexist') - .pipe(protractor({ - configFile: 'client/test/config/protractor.conf.js', - args: ['--baseUrl', APPLICATION_BASE_URL, '--capabilities.browserName', BROWSERS.toLowerCase(), '--env', ENV] + + gulp.src(paths.test.e2e) + .pipe($.protractor.protractor({ + configFile: path.join(__dirname, paths.test.config.protractor), + args: ['--baseUrl', APPLICATION_BASE_URL, '--capabilities.browserName', BROWSERS.toLowerCase()] // protractor only accept lowercase browser name })).on('error', function () { - // Make sure failed tests cause gulp to exit non-zero - gutil.log(COLORS.red('Error: E2E test failed')); + log(COLORS.red('Error: E2E test failed')); + // make sure failed tests cause gulp to exit non-zero return process.exit(1); }); +}, { + options: { + 'browsers=' : 'run test against specific browser (PhantomJS|Chrome|Firefox|Safari)', + 'production': 'run test against production server' + } +}); + +/** + * The 'test' task run unit and e2e tests. + */ +gulp.task('test', 'Run unit and e2e tests', ['karma'], function () { + return gulp.start( + 'test:unit', + 'test:e2e' + ); }); +//--------------------------------------------- +// BUILD TASKS +//--------------------------------------------- + /** - * The 'compile' task gets app ready for deployment by concatenating, - * minifying etc. + * The 'build' task gets app ready for deployment by processing files + * and put them into directory ready for production. */ gulp.task('build', 'Build application for deployment', function (cb) { - if(BUILD_WITHOUT_TEST) { - gutil.log(COLORS.red('********** BUILDING RELEASE VERSION WITHOUT TEST **********')); - // this task should run when user doesn't want to run test (this task is also running in CI server as CI server specify against what browsers the test should be running) - runSequence(['clean', 'bower-install', 'config:prod'], - ['compile', 'copy'], - // the reason why config:dev is running at the end is because when we generate build on local env it create production config-env.js file - // and if developers want to run app in dev env after gulp build they are getting error because they forgot run gulp install to generate development config-env.js file - ['config:dev'], - cb); - } else { - gutil.log(COLORS.blue('********** BUILDING RELEASE VERSION **********')); - // this task should run when user has decide to manually upload files to production server as this task run all unit and e2e test - runSequence(['clean', 'bower-install', 'config:prod'], - ['test:unit'], - ['test:e2e'], - ['compile', 'copy'], - // the reason why config:dev is running at the end is because when we generate build on local env it create production config-env.js file - // and if developers want to run app in dev env after gulp build they are getting error because they forgot run gulp install to generate development config-env.js file - ['config:dev'], - cb); + runSequence( + ['clean', 'bower-install', 'config'], + ['compile', 'extras', 'images', 'fonts'], + cb + ); +}, { + options: { + 'env=': 'environment flag (production|development)', + 'cdn': 'replace local path with CDN url' } }); +//--------------------------------------------- +// RELEASE TASKS +//--------------------------------------------- + /** - * Bump version number in package.json & bower.json. + * The 'bump' task bump version number in package.json & bower.json. */ -gulp.task('bump', 'Bump version number in package.json & bower.json', ['stylus', 'jshint:client', 'jshint:server', 'htmlhint', 'test:unit'], function () { +gulp.task('bump', 'Bump version number in package.json & bower.json', ['jshint', 'htmlhint', 'test:unit'], function () { var HAS_REQUIRED_ATTRIBUTE = !!argv.type ? !!argv.type.match(new RegExp(/major|minor|patch/)) : false; if (!HAS_REQUIRED_ATTRIBUTE) { - gutil.log(COLORS.red('Error: Required bump \'type\' is missing! Usage: gulp bump --type=(major|minor|patch)')); + log(COLORS.red('Error: Required bump \'type\' is missing! Usage: gulp bump --type=(major|minor|patch)')); return process.exit(1); } if (!semver.valid(pkg.version)) { - gutil.log(COLORS.red('Error: Invalid version number - ' + pkg.version)); + log(COLORS.red('Error: Invalid version number - ' + pkg.version)); return process.exit(1); } - if(process.env.TRAVIS === 'true') { - return gulp.src(['build/dist/package.json']) - .pipe(bump({type: argv.type})) - .pipe(gulp.dest('./build/dist/')); - } else { - return gulp.src(['package.json', 'bower.json']) - .pipe(bump({type: argv.type})) - .pipe(gulp.dest('./')); - } + return gulp.src(['package.json', 'bower.json']) + .pipe($.bump({type: argv.type})) + .pipe(gulp.dest('./')); }); /** - * Generate changelog. + * The 'changelog' task generate changelog. */ -gulp.task('changelog', 'Generate changelog', function(callback) { +gulp.task('changelog', 'Generate changelog', function(cb) { changelog({ - version: pkg.version, - repository: 'https://github.com/martinmicunda/' + pkg.name + version: pkg.version }, function(err, data) { if (err) { - gutil.log(COLORS.red('Error: Failed to generate changelog ' + err)); + log(COLORS.red('Error: Failed to generate changelog ' + err)); return process.exit(1); } - fs.writeFileSync('CHANGELOG.md', data, callback()); + fs.writeFileSync('CHANGELOG.md', data, cb()); }); }); /** - * The 'release' task push package.json and CHANGELOG.md to GitHub. + * The 'release' task push bower.json, package.json and CHANGELOG.md to GitHub. */ -gulp.task('release', 'Release bumped version number to GitHub repo', ['check'], function () { - if (!semver.valid(pkg.version)) { - gutil.log(COLORS.red('Error: Invalid version number - ' + pkg.version + '. Please fix the the version number in package.json and run \'gulp publish\' command again.')); +gulp.task('release', 'Release bumped version number to GitHub repo', function (cb) { + var exec = require('child_process').exec; + + if(!semver.valid(pkg.version)) { + log(COLORS.red('Error: Invalid version number - ' + pkg.version + '. Please fix the the version number in package.json and run \'gulp publish\' command again.')); return process.exit(1); } - if (hasGitChanges === '') { - gutil.log(COLORS.red('Error: No changes detected in this repo. Aborting release.')); + if(!semver.valid(require('./bower.json').version)) { + log(COLORS.red('Error: Invalid version number - ' + pkg.version + '. Please fix the the version number in bower.json and run \'gulp publish\' command again.')); return process.exit(1); } - gutil.log(COLORS.blue('Pushing to GitHub ...')); - var commitMsg = 'chore(release): v' + pkg.version; - return gulp.src('package.json') - .pipe(exec('git add CHANGELOG.md package.json')) - .pipe(exec('git commit -m "' + commitMsg + '" --no-verify')) - .pipe(exec('git push origin master')); + exec('git status --porcelain', function (err, stdout) { + if (err) {return cb(err);} + if (stdout === '') { + return cb(new Error('No changes detected in this repo. Aborting release.')); + } + + log(COLORS.blue('Pushing to GitHub ...')); + var commitMsg = 'chore(release): v' + pkg.version; + + exec('git add CHANGELOG.md package.json bower.json', childProcessCompleted); + exec('git commit -m "' + commitMsg + '" --no-verify', childProcessCompleted); + exec('git push origin master', childProcessCompleted); + + cb(); + }); + + function childProcessCompleted(error, stdout, stderr) { + log('stdout: ' + stdout); + log('stderr: ' + stderr); + if (error !== null) { + return cb(error); + } + } }); diff --git a/package.json b/package.json index 565bef8d..05d8e867 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "employee-scheduling", - "description": "MEAN (Mongo, Express, Angular, Node) - An employee scheduling application", "version": "0.0.0", + "description": "An UI component for [Employee Scheduling](https://github.com/martinmicunda/employee-scheduling) application.", "private": true, "author": { "name": "Martin Micunda", @@ -11,121 +11,90 @@ "main": "server/src/server.js", "homepage": "http://employee-scheduling.herokuapp.com", "scripts": { - "jshint:client": "gulp jshint:client", - "jshint:server": "gulp jshint:server", + "jshint": "gulp jshint", "htmlhint": "gulp htmlhint", "test": "gulp test:unit", "dep-check": "david" }, "pre-commit": [ - "jshint:client", - "jshint:server", + "jshint", "htmlhint", "test", "dep-check" ], "repository": { "type": "git", - "url": "https://github.com/martinmicunda/employee-scheduling" + "url": "https://github.com/martinmicunda/employee-scheduling-ui" }, "bugs": { - "url": "https://github.com/martinmicunda/employee-scheduling/issues" + "url": "https://github.com/martinmicunda/employee-scheduling-ui/issues" }, "keywords": [ "employee scheduling" ], - "dependencies": { - "body-parser": "^1.0.2", - "errorhandler": "^1.0.0", - "express": "4.4.4", - "method-override": "^1.0.0", - "mm-mongoose-connection": "0.0.1", - "morgan": "^1.0.0", - "nconf": "^0.6.9" - }, "devDependencies": { - "gulp": "^3.6.0", - "bower": "^1.3.1", - "wiredep": "^1.3.0", - "tiny-lr": "^0.0.7", - "gulp-bower": "^0.0.5", - "gulp-jshint": "^1.5.0", - "gulp-htmlhint": "^0.0.7", - "gulp-clean": "^0.3.0", - "gulp-usemin": "^0.3.3", - "gulp-rev": "^0.4.0", - "gulp-uglify": "^0.3.0", - "gulp-size": "^0.3.1", - "gulp-minify-css": "^0.3.0", - "gulp-watch": "^0.6.4", - "gulp-livereload": "^1.2.0", - "gulp-autoprefixer": "^0.0.7", - "gulp-imagemin": "^0.5.0", - "gulp-header": "^1.0.2", - "gulp-minify-html": "^0.1.1", - "gulp-bump": "^0.1.6", - "gulp-exec": "^2.0.1", - "gulp-stylus": "^1.0.1", - "gulp-util": "^2.2.14", - "gulp-if": "^1.2.0", - "gulp-concat": "^2.2.0", - "conventional-changelog": "0.0.11", - "gulp-karma": "^0.0.4", - "gulp-ng-constant": "^0.1.0", - "gulp-ng-annotate": "0.2.0", - "gulp-sourcemaps": "1.1.0", - "gulp-help": "^0.1.0", - "gulp-cdnizer": "^0.2.4", - "gulp-inject": "^0.4.1", - "gulp-gh-pages": "^0.1.4", - "gulp-nodemon": "^1.0.2", - "gulp-preprocess": "1.1.1", - "gulp-bytediff": "0.2.0", - "gulp-cache": "0.2.0", - "gulp-angular-templatecache": "^1.1.1", - "gulp-istanbul-enforcer": "^1.0.2", - "gulp-protractor": "^0.0.7", - "protractor": "^0.22.0", - "protractor-screenshot-reporter": "^0.0.5", - "karma": "^0.12.0", - "karma-firefox-launcher": "^0.1.3", - "karma-chrome-launcher": "^0.1.2", + "gulp": "^3.8.8", + "bower": "^1.3.11", + "david": "^5.0.0", + "semver": "^4.0.0", + "wiredep": "^1.8.5", + "run-sequence": "^0.3.7", + "jshint-stylish": "^1.0.0", + "main-bower-files": "^2.0.0", + "you-shall-not-commit": "^0.0.1", + "conventional-changelog": "^0.0.11", + "gulp-if": "^1.2.4", + "gulp-rev": "^1.1.0", + "gulp-util": "^3.0.1", + "gulp-help": "^1.1.0", + "gulp-size": "^1.1.0", + "gulp-bump": "^0.1.11", + "gulp-sass": "^1.0.0", + "gulp-cache": "^0.2.2", + "gulp-uglify": "^1.0.1", + "gulp-concat": "^2.4.1", + "gulp-rename": "1.2.0", + "gulp-jshint": "^1.8.4", + "gulp-rimraf": "^0.1.0", + "gulp-filter": "^1.0.2", + "gulp-header": "^1.1.1", + "gulp-inject": "^1.0.2", + "gulp-usemin": "^0.3.8", + "gulp-changed": "^1.0.0", + "gulp-cdnizer": "^1.0.1", + "gulp-flatten": "^0.0.3", + "gulp-htmlhint": "^0.0.9", + "gulp-imagemin": "^1.0.1", + "gulp-bytediff": "^0.2.0", + "gulp-webserver": "^0.8.1", + "gulp-livereload": "^2.1.1", + "gulp-minify-css": "^0.3.10", + "gulp-sourcemaps": "^1.2.2", + "gulp-protractor": "^0.0.11", + "gulp-ng-annotate": "^0.2.0", + "gulp-ng-constant": "^0.1.1", + "gulp-minify-html": "^0.1.5", + "gulp-load-plugins": "^0.6.0", + "gulp-autoprefixer": "^1.0.1", + "gulp-istanbul-enforcer": "^1.0.3", + "gulp-angular-templatecache": "^1.4.1", + "protractor": "^1.3.0", + "jasmine-reporters": "^1.0.0", + "protractor-html-screenshot-reporter": "^0.0.17", + "karma": "^0.12.23", + "karma-jasmine": "^0.1.5", + "karma-coverage": "^0.2.6", + "karma-coveralls": "^0.1.4", + "karma-junit-reporter": "^0.2.2", + "karma-chrome-launcher": "^0.1.4", "karma-safari-launcher": "^0.1.1", + "karma-firefox-launcher": "^0.1.3", "karma-phantomjs-launcher": "^0.1.4", - "karma-junit-reporter": "^0.2.1", - "karma-jasmine": "^0.2.2", - "karma-coverage": "^0.2.1", - "karma-coveralls": "^0.1.4", - "karma-ng-html2js-preprocessor": "^0.1.0", - "run-sequence": "^0.3.6", - "jasmine-reporters": "^0.4.0", - "jshint-stylish": "^0.2.0", - "minimist": "^0.1.0", - "nib": "^1.0.2", - "you-shall-not-commit": "^0.0.1", - "david": "^3.1.0", - "semver": "^2.3.0", - "lodash": "^2.4.1", - "glob": "^4.0.0", - "compression": "1.0.2", - "helmet": "0.2.1", - "express-jwt": "0.2.1", - "jsonwebtoken": "0.4.0", - "mongoose": "3.8.12", - "redis": "0.10.3", - "bcryptjs": "1.0.2", - "colors": "0.6.2", - "passport": "0.2.0", - "passport-local": "0.1.0", - "mm-node-logger": "0.0.*", - "mm-mongoose-connection": "0.0.*", - "dgeni": "0.3.0", - "dgeni-packages": "0.9.5", - "canonical-path": "0.0.2" + "karma-ng-html2js-preprocessor": "^0.1.0" }, "licenses": [ { - "type": "Apache License, Version 2.0", + "type": "GNU GPL v3 license", "url": "https://github.com/martinmicunda/employee-scheduling/master/LICENSE" } ], diff --git a/src/app/app_test.js b/src/app/app.spec.js old mode 100755 new mode 100644 similarity index 98% rename from src/app/app_test.js rename to src/app/app.spec.js index 3619614f..70ab380c --- a/src/app/app_test.js +++ b/src/app/app.spec.js @@ -15,4 +15,4 @@ describe('filter', function() { expect(false).toBe(false); })); }); -}); \ No newline at end of file +}); diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss new file mode 100644 index 00000000..0baae1a3 --- /dev/null +++ b/src/styles/_variables.scss @@ -0,0 +1,32 @@ +$blue: #006699; +$lightBlue: #2479a4; +$blue3: #39c; + +$red: #cc3333; + +$darkGrey: #333; +$mediumGrey: #ebebeb; +$lightGrey: #999; + +// Formal Theme: +$primaryColor: $blue; +$secondaryColor: $darkGrey; + +$fontWeightLight: 200; +$fontWeightMedium: 400; +$fontWeightHeavy: 600; + +$fontSizeBase: 14px; +$lineHeightBase: 1.4; + +$inputHeightBase: 40px; +$inputColor: $lightGrey; +$inputBg: #fff; +$inputBgDisabled: #eee; +$inputBorder: #ccc !default; +$inputBorderRadius: 3px; + + +$borderRadius: 4px; +$btnBorderRadius: $borderRadius; +$authNavHeight: 32px; diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss new file mode 100644 index 00000000..5ad16d37 --- /dev/null +++ b/src/styles/base/_base.scss @@ -0,0 +1,29 @@ +html, +body { + width: 100%; + height: 100%; +} + +body { + overflow-x: hidden; +} + +.wrapper-main { + position: relative; + width: 100%; + height: auto; + min-height: 100%; +} + +.wrapper-main:before { + position: absolute; + top: 0; + bottom: 0; + z-index: -1; + display: block; + width: inherit; + background-color: #f0f3f4; + border: inherit; + content: ""; +} + diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 00000000..999b8743 --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1,6 @@ +@import "variables"; + +@import "base/base"; + +@import "modules/header"; +@import "modules/footer"; diff --git a/src/styles/modules/_footer.scss b/src/styles/modules/_footer.scss new file mode 100644 index 00000000..7d5077d5 --- /dev/null +++ b/src/styles/modules/_footer.scss @@ -0,0 +1,12 @@ +.footer { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 9999; + height: 50px; + padding: 15px; + background-color: #fff; + text-align: center; + box-shadow: 0 -2px 2px rgba(0, 0, 0, 0.05), 0 -1px 0 rgba(0, 0, 0, 0.05); +} diff --git a/src/styles/modules/_header.scss b/src/styles/modules/_header.scss new file mode 100644 index 00000000..a5ebd4e2 --- /dev/null +++ b/src/styles/modules/_header.scss @@ -0,0 +1,110 @@ +.header-fixed { + padding-top: 50px; +} +.header-fixed .header { + position: fixed; + top: 0; + width: 100%; +} +.header { + z-index: 9999; + border-radius: 0; + background-color: #fff; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05); +} +.navbar { + margin: 0; + border-width: 0; + border-radius: 0; +} +@media (min-width: 768px) { + .navbar-header { + width: 200px; + } +} +.navbar-header > button { + padding: 10px 17px; + font-size: 16px; + line-height: 30px; + text-decoration: none; + background-color: transparent; + border: none; +} +.navbar-brand { + display: inline-block; + float: none; + height: auto; + padding: 0 0 0 15px; + font-size: 20px; + font-weight: 700; + line-height: 50px; + text-align: center; +} +.navbar-brand img { + margin-top: -4px; + vertical-align: middle; +} + + + + +.thumb-sm { + display: inline-block; + width: 40px; +} +.thumb-sm img{ + height: auto; + max-width: 100%; + vertical-align: middle; +} + +.avatar { + position: relative; + display: block; + white-space: nowrap; + border-radius: 500px; +} + +.avatar img { + width: 100%; + border-radius: 500px; +} + + + + + + +.avatar.thumb-sm i { + margin: 1px; +} + +.avatar.thumb-xs i { + margin: 0; +} +.m-t-n-sm { + margin-top: -10px; +} +.m-b-n-sm { + margin-bottom: -10px; +} +.m-r-sm { + margin-right: 10px; +} + +@media (max-width: 767px) { + .navbar-nav { + margin-top: 0; + margin-bottom: 0; + } + .navbar-nav > li > a { + box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.1); + } + .navbar-nav > li > a .up { + top: 0; + } + .navbar-nav > li > a .avatar { + width: 30px; + margin-top: -5px; + } +} diff --git a/test/config/karma.conf.js b/test/config/karma.conf.js index 0b2ab1b9..18c2efb4 100644 --- a/test/config/karma.conf.js +++ b/test/config/karma.conf.js @@ -1,96 +1,59 @@ -"use strict"; +'use strict'; module.exports = function (config) { - var files, preprocessors; - - // 'env' argument is specify in gulpfile.js. The reason for argv[2] is because the first element is 'node', the second element - // is be the name of the JavaScript file. The next elements will be any additional command line arguments. - var isProduction = JSON.parse(process.argv[2]).env === 'production' ? true : false; - - if(isProduction) { - files = [ - // libraries - 'build/dist/scripts/lib.min-*.js', - - // test libraries - 'client/src/vendor/angular-mocks/angular-mocks.js', - - // our app - 'build/dist/scripts/app.min-*.js', + config.set({ + // base path, that will be used to resolve files and exclude + basePath: '../../', - // tests - 'client/src/app/**/*_test.js' - ], + frameworks: ['jasmine'], - preprocessors = { - // source files, that you wanna generate coverage for - do not include tests or libraries - // (these files will be instrumented by Istanbul) - '**/build/dist/scripts/app.min-*.js': 'coverage' - } - } else { - files = [ + // list of files / patterns to load in the browser + files: [ // libraries - 'client/src/vendor/jquery/dist/jquery.js', - 'client/src/vendor/angular/angular.js', - 'client/src/vendor/angular-ui-router/release/angular-ui-router.js', - 'client/src/vendor/lodash/dist/lodash.compat.js', - 'client/src/vendor/restangular/dist/restangular.js', - 'client/src/vendor/angular-ui-router/release/angular-ui-router.js', - 'client/src/vendor/bootstrap/dist/js/bootstrap.js', - 'client/src/vendor/angular-bootstrap/ui-bootstrap-tpls.js', + 'src/vendor/jquery/dist/jquery.js', + 'src/vendor/angular/angular.js', + 'src/vendor/angular-animate/angular-animate.js', + 'src/vendor/bootstrap/dist/js/bootstrap.js', + 'src/vendor/angular-bootstrap/ui-bootstrap-tpls.js', + 'src/vendor/angular-ui-router/release/angular-ui-router.js', // test libraries - 'client/src/vendor/angular-mocks/angular-mocks.js', + 'src/vendor/angular-mocks/angular-mocks.js', // our app - 'client/src/app/config.js', - 'client/src/app/app.js', - 'client/src/app/**/*-module.js', - 'client/src/app/**/!(*_test).js', + 'src/app/**/!(*.spec).js', // tests - 'client/src/app/**/*_test.js', + 'src/app/**/*.spec.js' // templates // 'public/app/**/*.html' ], - preprocessors = { - // source files, that you wanna generate coverage for - do not include tests or libraries - // (these files will be instrumented by Istanbul) - '**/client/src/app/**/!(*_test).js': 'coverage' - // TODO: (martin) this might be good for testing templates - // generate js files from html templates - // '**/src/app/**/*.html': ['ng-html2js'] - } - } - - config.set({ - // base path, that will be used to resolve files and exclude - basePath: '../../../', - - frameworks: ['jasmine'], - - // list of files / patterns to load in the browser - files: files, - // list of files to exclude exclude: [ ], - // use dots reporter, as travis terminal does not support escaping sequences - // possible values: 'dots', 'progress', 'junit' - // CLI --reporters progress - reporters: ['dots', 'junit', 'coverage', 'coveralls'], + // use dots reporter, as Travis terminal does not support escaping sequences; + // when using Travis publish coverage to coveralls + reporters: process.env.TRAVIS ? ['dots', 'junit', 'coverage', 'coveralls'] : ['dots', 'junit', 'coverage'], junitReporter: { // will be resolved to basePath (in the same way as files/exclude patterns) - outputFile: 'build/test-reports/client/unit-test-report.xml', + outputFile: 'test/test-reports/unit-test-report.xml', suite: 'unit' }, - preprocessors: preprocessors, + preprocessors: { + // source files, that you wanna generate coverage for - do not include tests or libraries + // (these files will be instrumented by Istanbul) + '**/src/app/**/!(*.spec).js': 'coverage' + // TODO: (martin) this might be good for testing templates + // generate js files from html templates + // '**/src/app/**/*.html': ['ng-html2js'] + }, + // TODO: (martin) this might be good for testing templates ngHtml2JsPreprocessor: { // strip this from the file path // stripPrefix: 'public/app/', @@ -101,12 +64,12 @@ module.exports = function (config) { // moduleName: 'st-templates' }, - // optionally, configure the reporter coverageReporter: { reporters: [ - {type: 'html', dir: 'build/test-reports/client/coverage/'}, // will generate html report - {type: 'lcov', dir: 'build/test-reports/client/coverage/'}, // will generate json report file and this report is loaded to make sure failed coverage cause gulp to exit non-zero - {type: 'text-summary', dir: 'build/test-reports/client/coverage/'} // it does not generate any file but it will print coverage to console + {type: 'html', dir: 'test/test-reports/coverage/'}, // will generate html report + {type: 'json', dir: 'test/test-reports/coverage/'}, // will generate json report file and this report is loaded to make sure failed coverage cause gulp to exit non-zero + {type: 'lcov', dir: 'test/test-reports/coverage/'}, // will generate Icov report file and this report is published to coveralls + {type: 'text-summary', dir: 'test/test-reports/coverage/'} // it does not generate any file but it will print coverage to console ] }, @@ -136,8 +99,9 @@ module.exports = function (config) { // - PhantomJS // - IE (only Windows) // CLI --browsers Chrome,Firefox,Safari - browsers: [process.env.TRAVIS ? 'Firefox' : 'Firefox'], -// browsers: ['Chrome', 'Safari', 'Firefox', 'PhantomJS'], + + // (martin) the browser option comes from gulp that's the place where browsers should be specify + browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], // If browser does not capture in given timeout [ms], kill it // CLI --capture-timeout 5000 diff --git a/test/config/protractor.conf.js b/test/config/protractor.conf.js index eebfb03a..45998a06 100644 --- a/test/config/protractor.conf.js +++ b/test/config/protractor.conf.js @@ -1,47 +1,12 @@ -var argvIndex; -process.argv.forEach(function (val, index, array) { - if(val == '--env') { - argvIndex = index; - } -}); -var isProduction = process.argv[argvIndex + 1] === 'production' ? true : false; +'use strict'; + +var path = require('path'); +var ScreenShotReporter = require('protractor-html-screenshot-reporter'); exports.config = { // The address of a running selenium server. - seleniumServerJar: '../../../node_modules/protractor/selenium/selenium-server-standalone-2.41.0.jar', + seleniumServerJar: '../../node_modules/protractor/selenium/selenium-server-standalone-2.43.1.jar', seleniumArgs: ['-browserTimeout=60'], -// seleniumPort: 4444, -// seleniumAddress: 'http://localhost:4444/wd/hub', - - // ----- What tests to run ----- - // - // Spec patterns are relative to the location of this config. - specs: [ - '../e2e/**/*.js' - ], - - // A base URL for your application under test. Calls to protractor.get() - // with relative paths will be prepended with this. -// baseUrl: 'http://127.0.0.1:3000', - -// multiCapabilities: [ -//// { -//// 'browserName': 'chrome' -//// }, -//// { -//// 'browserName': 'safari' -//// }, -// { -// 'browserName': 'firefox' -// }, -// // TODO: (martin) There is issue with phantomJS in Protractor https://github.com/angular/protractor/issues/557 -//// { -//// -//// 'browserName': 'phantomjs' -//// 'phantomjs.binary.path': './node_modules/karma-phantomjs-launcher/node_modules/phantomjs/bin/phantomjs', -//// 'phantomjs.cli.args': ['--debug=true', '--webdriver-logfile=webdriver.log', '--webdriver-loglevel=DEBUG'] -//// } -// ], /** * A callback function called once protractor is ready and available, @@ -51,10 +16,7 @@ exports.config = { * the filename string. */ onPrepare: function() { - // browser.driver.manage().window().setSize(1024,768); - - // Launch the Express server we will run tests against. - exports.server = require('../../../server/src/server.js'); + browser.driver.manage().window().setSize(1024,768); /** * At this point, global 'protractor' object will be set up, and @@ -65,13 +27,13 @@ exports.config = { * this until inside the onPrepare function. */ require('jasmine-reporters'); - jasmine.getEnv().addReporter(new jasmine.JUnitXmlReporter(null, true, true, './build/test-reports/client/e2e-test-report')); + jasmine.getEnv().addReporter( + new jasmine.JUnitXmlReporter('xmloutput', true, true, './test/test-reports/e2e-test-report') + ); - var ScreenShotReporter = require('protractor-screenshot-reporter'); - var path = require('path'); - // Add a screenshot reporter and store screenshots to `/tmp/screnshots`: + // Add a screenshot reporter and store screenshots to `/test/test-reports/screenshots`: jasmine.getEnv().addReporter(new ScreenShotReporter({ - baseDirectory: './build/test-reports/client/screenshots', + baseDirectory: './test/test-reports/screenshots', pathBuilder: function pathBuilder(spec, descriptions, results, capabilities) { // Return '/' as path for screenshots: // Example: 'firefox/list-should work'. @@ -84,13 +46,6 @@ exports.config = { // ----- Options to be passed to minijasminenode ----- jasmineNodeOpts: { - /** - * onComplete will be called just before the driver quits. - */ - onComplete: function () { - // Shut down the test Express server. - exports.server.close(); - }, // If true, display spec names. isVerbose: true, // If true, print colors to the terminal. @@ -100,4 +55,4 @@ exports.config = { // Default time to wait in ms before a test fails. defaultTimeoutInterval: 30000 } -}; \ No newline at end of file +}; diff --git a/test/e2e/example_e2e.js b/test/e2e/example.e2e.js similarity index 89% rename from test/e2e/example_e2e.js rename to test/e2e/example.e2e.js index 1da7643f..8fd40b57 100644 --- a/test/e2e/example_e2e.js +++ b/test/e2e/example.e2e.js @@ -1,13 +1,3 @@ -/*global - beforeEach: false, - browser: false, - by: false, - describe: false, - expect: false, - it: false, - protractor: false - */ - describe('angularjs homepage', function() { it('should greet the named user', function() { browser.get('http://www.angularjs.org'); @@ -44,4 +34,4 @@ describe('angularjs homepage', function() { expect(todoList.get(2).getText()).toEqual('write a protractor test'); }); }); -}); \ No newline at end of file +});