Merged master
diff --git a/views/ngXosLib/bower.json b/views/ngXosLib/bower.json
index 228a082..08a02e3 100644
--- a/views/ngXosLib/bower.json
+++ b/views/ngXosLib/bower.json
@@ -17,7 +17,6 @@
     "angular": "1.4.7",
     "angular-ui-router": "0.2.15",
     "angular-resource": "1.4.7",
-    "ng-lodash": "0.3.0",
     "angular-cookies": "1.4.7",
     "angular-animate": "1.4.7",
     "lodash": "~4.11.1",
diff --git a/views/ngXosLib/generator-xos/app/templates/gulp/build.js b/views/ngXosLib/generator-xos/app/templates/gulp/build.js
index 663a4cf..b66cdbc 100644
--- a/views/ngXosLib/generator-xos/app/templates/gulp/build.js
+++ b/views/ngXosLib/generator-xos/app/templates/gulp/build.js
@@ -98,8 +98,8 @@
   gulp.task('copyHtml', function(){
     return gulp.src(options.src + 'index.html')
       // remove dev dependencies from html
-      .pipe(replace(/<!-- bower:css -->(\n.*)*\n<!-- endbower --><!-- endcss -->/, ''))
-      .pipe(replace(/<!-- bower:js -->(\n.*)*\n<!-- endbower --><!-- endjs -->/, ''))
+      .pipe(replace(/<!-- bower:css -->(\n^<link.*)*\n<!-- endbower -->/gmi, ''))
+      .pipe(replace(/<!-- bower:js -->(\n^<script.*)*\n<!-- endbower -->/gmi, ''))
       // injecting minified files
       .pipe(
         inject(
@@ -150,6 +150,7 @@
   gulp.task('build', function() {
     runSequence(
       'clean',
+      'sass',
       'templates',
       'babel',
       'scripts',
diff --git a/views/ngXosLib/generator-xos/package.json b/views/ngXosLib/generator-xos/package.json
old mode 100755
new mode 100644
index 58b5a33..0098496
--- a/views/ngXosLib/generator-xos/package.json
+++ b/views/ngXosLib/generator-xos/package.json
@@ -4,7 +4,7 @@
   "description": "View generator for XOS",
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "mocha test"
   },
   "author": "Matteo Scandolo",
   "license": "ISC",
@@ -13,5 +13,13 @@
   },
   "files": [
     "app"
-  ]
+  ],
+  "devDependencies": {
+    "mocha": "^2.4.5",
+    "mockery": "^1.7.0",
+    "rimraf": "^2.5.2",
+    "wiredep": "^4.0.0",
+    "yeoman-assert": "^2.2.1",
+    "yeoman-test": "^1.4.0"
+  }
 }
diff --git a/views/ngXosLib/generator-xos/test/build.spec.js b/views/ngXosLib/generator-xos/test/build.spec.js
new file mode 100644
index 0000000..dd49b03
--- /dev/null
+++ b/views/ngXosLib/generator-xos/test/build.spec.js
@@ -0,0 +1,182 @@
+'use strict';
+var path = require('path');
+var helpers = require('yeoman-test');
+var assert = require('yeoman-assert');
+var exec = require('child_process').exec;
+var fs = require('fs');
+const rimraf = require('rimraf');
+
+const getMillisec = min => min * 60 * 1000;
+const deleteFile = file => {
+  if(fs.existsSync(file)){
+    // console.log(`deleting: ${file}`);
+    fs.unlinkSync(file);
+  }
+}
+
+// source files
+const viewName = 'testDashboard';
+const fileName = viewName.replace(/^./, viewName[0].toUpperCase());
+const sourcePath = path.join(__dirname, `../../../ngXosViews/${viewName}/`);
+
+// dest files
+const basePath = '../../../../xos/core/xoslib';
+const destHtml = path.join(__dirname, basePath + '/dashboards/xosTestDashboard.html');
+const destJs = path.join(__dirname, basePath + '/static/js/xosTestDashboard.js');
+const destVendor = path.join(__dirname, basePath + '/static/js/vendor/xosTestDashboardVendor.js');
+const destCss = path.join(__dirname, basePath + '/static/css/xosTestDashboard.css');
+
+describe('The XOS Build script', function(){
+  const buildCmd = 'gulp build';
+  
+  this.timeout(getMillisec(5));
+
+  before(done => {
+    console.log('Running generator');
+    this.generator = helpers
+      .run(require.resolve('../app'))
+      .inDir(sourcePath)
+      .withOptions({ 'skip-install': false })
+      .withPrompts({
+        name: viewName,
+        host: 'test-host',
+        token: 'test-token',
+        session: 'test-session'
+      })
+      .on('end', () => {
+        process.stdout.write('Installing Node Modules');
+        let npmInstall = setInterval(() => {
+          process.stdout.write('.');
+        }, 1000);
+        exec('npm install', {
+          cwd: sourcePath
+        }, (err) => {
+          clearInterval(npmInstall);
+          process.stdout.write('\nInstalling Bower Components');
+          let bowerInstall = setInterval(() => {
+            process.stdout.write('.');
+          }, 1000);
+          exec('bower install', {
+            cwd: sourcePath
+          }, (err) => {
+            clearInterval(bowerInstall);
+            done(err);
+          });
+        });
+      });
+  });
+
+  describe('when no styles or vendors are added', () => {
+    
+    before((done) => {
+      process.stdout.write('\nBuilding App');
+      let appBuild = setInterval(() => {
+        process.stdout.write('.');
+      }, 1000);
+      exec(buildCmd, {
+        cwd: sourcePath
+      }, (err) => {
+        console.log(err);
+        clearInterval(appBuild);
+        done(err);
+      });
+    });
+
+    it('should have build the app', () => {
+      assert.file([destHtml, destJs]);
+    });
+
+    it('should include only minified files in the index', () => {
+      assert.fileContent(destHtml, `<script src="/static/js/xos${fileName}.js"></script>`);
+      assert.noFileContent(destHtml, `<!-- bower:css -->`);
+      assert.noFileContent(destHtml, `<!-- bower:js -->`);
+    });
+  });
+
+  describe('when a third party library is added', () => {
+    before((done) => {
+    process.stdout.write('\nInstalling 3rd party library');
+      let bowerInstall = setInterval(() => {
+        process.stdout.write('.');
+      }, 1000);
+      exec('bower install d3 --save', {
+        cwd: sourcePath
+      }, (err, out) => {
+        clearInterval(bowerInstall);
+        process.stdout.write('\nBuilding App');
+        let appBuild = setInterval(() => {
+          process.stdout.write('.');
+        }, 1000);
+        exec(buildCmd, {
+          cwd: sourcePath
+        }, (err) => {
+          console.log(err);
+          clearInterval(appBuild);
+          done(err);
+        }); 
+      });
+    });
+
+    it('should have build the app with a vendor file', () => {
+      assert.file([destHtml, destJs, destVendor]);
+    });
+
+    it('should include only minified files and minified deps in the index', () => {
+      assert.fileContent(destHtml, `<script src="/static/js/xos${fileName}.js"></script>`);
+      assert.fileContent(destHtml, `<script src="/static/js/vendor/xos${fileName}Vendor.js"></script>`);
+      assert.noFileContent(destHtml, `<!-- bower:css -->`);
+      assert.noFileContent(destHtml, `<!-- bower:js -->`);
+    });
+  });
+
+  describe('when some styles are added', () => {
+    before((done) => {
+      let styleContent = `
+        @import '../../../../style/sass/lib/_variables.scss';
+
+        #xosTestDashboard {
+          background: $brand-primary;
+        }
+      `;
+
+      fs.writeFile(`${sourcePath}src/sass/main.scss`, styleContent, function(err) {
+        process.stdout.write('\nBuilding the Application');
+        let appBuild = setInterval(() => {
+          process.stdout.write('.');
+        }, 1000);
+        exec('bower uninstall d3 --save', {
+          cwd: sourcePath
+        }, (err, out) => {
+          exec(buildCmd, {
+            cwd: sourcePath
+          }, (err, out) => {
+            clearInterval(appBuild);
+            done();
+          })
+        })
+      });
+    });
+
+    it('should have build the app with a css file', () => {
+      assert.file([destHtml, destJs, destCss]);
+    });
+
+    it('should include only minified files and minified deps in the index', () => {
+      assert.fileContent(destHtml, `<script src="/static/js/xos${fileName}.js"></script>`);
+      assert.fileContent(destHtml, `<link rel="stylesheet" href="/static/css/xos${fileName}.css">`);
+      assert.noFileContent(destHtml, `<!-- bower:css -->`);
+      assert.noFileContent(destHtml, `<!-- bower:js -->`);
+
+      assert.fileContent(destCss, `background:#337ab7`);
+    });
+  });
+
+  after(done => {
+    // deleting the folder used for test
+    deleteFile(destHtml);
+    deleteFile(destJs);
+    deleteFile(destVendor);
+    deleteFile(destCss);
+    rimraf(sourcePath, {}, done);
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosLib/generator-xos/test/generator.spec.js b/views/ngXosLib/generator-xos/test/generator.spec.js
new file mode 100644
index 0000000..f75f444
--- /dev/null
+++ b/views/ngXosLib/generator-xos/test/generator.spec.js
@@ -0,0 +1,117 @@
+'use strict';
+
+const path = require('path');
+const helpers = require('yeoman-test');
+const assert = require('yeoman-assert');
+const rimraf = require('rimraf');
+const mockery = require('mockery');
+const wiredep = require('wiredep');
+
+const firstCharTouppercase = string => string.replace(/^./, string[0].toUpperCase())
+
+// get bower deps installed in ngXosLib
+let bowerDeps = wiredep({
+  cwd: path.join(__dirname, '../../'), // pretending to be in the ngXosLib root
+  exclude: ['Chart.js']
+});
+bowerDeps = bowerDeps.js.map(d => d.match(/bower_components\/([a-zA-Z\-`.]+)\//)[1]);
+
+// test values
+const viewName = 'testDashboard';
+const fileName = firstCharTouppercase(viewName);
+const testPath = path.join(__dirname, `../../../ngXosViews/${viewName}/`);
+
+const getDefaultFiles = () => {
+  return [
+    '.bowerrc',
+    '.eslintrc',
+    '.gitignore',
+    'bower.json',
+    'gulpfile.js',
+    'karma.conf.js',
+    'package.json',
+    'src/index.html',
+  ].map(i => `${testPath}${i}`);
+};
+
+const yeomanUserMock = {
+  git: {
+    name: () => 'Test User',
+    email: () => 'test@mail.org'
+  }
+}
+
+mockery.enable({
+  warnOnReplace: false,
+  warnOnUnregistered: false,
+  useCleanCache: true,
+});
+mockery.resetCache();
+mockery.registerMock('../node_modules/yeoman-generator/lib/actions/user', yeomanUserMock);
+
+describe('Yeoman XOS generator', function () {
+
+  beforeEach(() => {
+  });
+
+  before(done => {
+    this.generator = helpers
+      .run(require.resolve('../app'))
+      .inDir(testPath)
+      .withOptions({ 'skip-install': true })
+      .withPrompts({
+        name: viewName,
+        host: 'test-host',
+        token: 'test-token',
+        session: 'test-session'
+      })
+      .on('end', done);
+  });
+
+
+  it('should generate base files in the correct directory', () => {
+    assert.file(getDefaultFiles());
+  });
+
+  it('should create the env file with correct params', () => {
+    assert.fileContent(`${testPath}env/default.js`, 'host: \'test-host\'');
+    assert.fileContent(`${testPath}env/default.js`, 'xoscsrftoken: \'test-token\'');
+    assert.fileContent(`${testPath}env/default.js`, 'xossessionid: \'test-session\'');
+  });
+
+  it('should write username in package & bower json', () => {
+    assert.fileContent(`${testPath}package.json`, '"author": "Test User"');
+    assert.fileContent(`${testPath}bower.json`, '"Test User <test@mail.org>"')
+  });
+
+  it('should add all xosLib dependencies in the dev section of bower.json', () => {
+    bowerDeps.forEach(d => {
+      assert.fileContent(`${testPath}bower.json`, d);
+    });
+  });
+
+  it('should set the right module name in all the files', () => {
+    assert.fileContent(`${testPath}src/index.html`, `ng-app="xos.${viewName}"`)
+    assert.fileContent(`${testPath}src/index.html`, `id="xos${fileName}"`)
+    assert.fileContent(`${testPath}src/js/main.js`, `angular.module('xos.${viewName}', [`)
+    assert.fileContent(`${testPath}src/sass/main.scss`, `#xos${fileName}`)
+  });
+
+  it('should set correct paths in build file', () => {
+    assert.fileContent(`${testPath}gulp/build.js`, `angular.module('xos.${viewName}')`)
+    assert.fileContent(`${testPath}gulp/build.js`, `options.dashboards + 'xos${fileName}.html'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `options.static + 'css/xos${fileName}.css'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `.pipe(concat('xos${fileName}.css'))`)
+    assert.fileContent(`${testPath}gulp/build.js`, `.pipe(concat('xos${fileName}.js'))`)
+    assert.fileContent(`${testPath}gulp/build.js`, `module: 'xos.${viewName}'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `options.static + 'js/vendor/xos${fileName}Vendor.js'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `options.static + 'js/xos${fileName}.js'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `options.static + 'css/xos${fileName}.css'`)
+    assert.fileContent(`${testPath}gulp/build.js`, `.pipe(concat('xos${fileName}Vendor.js'))`)
+  });
+
+  after(done => {
+    // deleting the folder used for test
+    rimraf(testPath, {}, done)
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/js/main.js b/views/ngXosViews/serviceGrid/src/js/main.js
index 5c41bd4..c52ef9f 100644
--- a/views/ngXosViews/serviceGrid/src/js/main.js
+++ b/views/ngXosViews/serviceGrid/src/js/main.js
@@ -60,6 +60,10 @@
       .then((services) => {
         this.services = _.map(services, s => {
           // parse backend_status string in a boolean for display
+          // NOTE they are not boolean:
+          // - start with 0 = provisioning
+          // - start with 1 = good
+          // - start with 2 = error
           s.status = parseInt(s.backend_status.match(/^[0-9]/)[0]) === 0 ? false : true;
           return s;
         })