ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From atk...@apache.org
Subject ambari git commit: AMBARI-18379 Cover host views with unit tests. (atkach)
Date Wed, 14 Sep 2016 13:22:32 GMT
Repository: ambari
Updated Branches:
  refs/heads/trunk 5beed88e1 -> 2ad42074f


AMBARI-18379 Cover host views with unit tests. (atkach)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/2ad42074
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/2ad42074
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/2ad42074

Branch: refs/heads/trunk
Commit: 2ad42074f1633c5c6f56cf979bdaa49440457566
Parents: 5beed88
Author: Andrii Tkach <atkach@apache.org>
Authored: Tue Sep 13 20:28:41 2016 +0300
Committer: Andrii Tkach <atkach@apache.org>
Committed: Wed Sep 14 16:22:17 2016 +0300

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   3 +
 .../app/views/main/host/combo_search_box.js     | 294 ++++--
 .../views/main/host/hosts_table_menu_view.js    |  14 +-
 ambari-web/app/views/main/host/log_metrics.js   |   2 +-
 .../views/main/host/combo_search_box_test.js    | 957 ++++++++++++++++++-
 .../views/main/host/host_alerts_view_test.js    |  23 +-
 .../main/host/hosts_table_menu_view_test.js     | 304 ++++++
 .../test/views/main/host/log_metrics_test.js    | 116 +++
 .../test/views/main/host/logs_view_test.js      | 178 ++++
 .../views/main/host/stack_versions_view_test.js |  66 ++
 ambari-web/test/views/main/host/summary_test.js |  91 ++
 11 files changed, 1942 insertions(+), 106 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index d2f3637..d918900 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -296,6 +296,9 @@ var files = [
   'test/views/main/host/combo_search_box_test',
   'test/views/main/host/config_service_test',
   'test/views/main/host/add_view_test',
+  'test/views/main/host/logs_view_test',
+  'test/views/main/host/hosts_table_menu_view_test',
+  'test/views/main/host/log_metrics_test',
   'test/views/main/host/config_service_menu_test',
   'test/views/main/host/details/host_component_view_test',
   'test/views/main/host/details/host_component_views/decommissionable_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/app/views/main/host/combo_search_box.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/combo_search_box.js b/ambari-web/app/views/main/host/combo_search_box.js
index f417cc6..2f9900a 100644
--- a/ambari-web/app/views/main/host/combo_search_box.js
+++ b/ambari-web/app/views/main/host/combo_search_box.js
@@ -60,7 +60,7 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     if (invalidFacet) {
       this.showErrMsg(invalidFacet);
     }
-    var tableView = this.get('parentView').get('parentView');
+    var tableView = this.get('parentView.parentView');
     App.db.setComboSearchQuery(tableView.get('controller.name'), query);
     var filterConditions = this.createFilterConditions(searchCollection);
     tableView.updateComboFilter(filterConditions);
@@ -87,7 +87,7 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     // Add host component facets only when there isn't any component filter
     // with value other than ALL yet
     var currentComponentFacets = this.getComponentStateFacets(hostComponentList, false);
-    if (currentComponentFacets.length == 0) {
+    if (currentComponentFacets.length === 0) {
       list = list.concat(hostComponentList);
     }
     list = this.filterOutOneFacetOnlyOptions(list);
@@ -105,7 +105,6 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     var controller = App.router.get('mainHostComboSearchBoxController');
     this.showHideClearButton();
     var map = App.router.get('mainHostController.labelValueMap');
-    var serviceMap = this.get('serviceMap')
     var facetValue = map[facet] || facet;
     if (controller.isComponentStateFacet(facetValue)) {
       facetValue = 'componentState'
@@ -113,76 +112,153 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     switch (facetValue) {
       case 'hostName':
       case 'ip':
-        controller.getPropertySuggestions(facetValue, searchTerm).done(function() {
-          callback(controller.get('currentSuggestion').reject(function (item) {
-            return visualSearch.searchQuery.values(facet).indexOf(item) >= 0; // reject the ones already in search
-          }), {preserveMatches: true});
-        });
+        this.searchByHostname(facetValue, searchTerm, facet, callback);
         break;
       case 'rack':
-        callback(App.Host.find().toArray().mapProperty('rack').uniq().reject(function (item) {
-          return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
-        }));
+        this.searchByRack(facet, callback);
         break;
       case 'version':
-        callback(App.HostStackVersion.find().toArray()
-          .filterProperty('isVisible', true).mapProperty('displayName').uniq().reject(function (item) {
-            return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
-          }));
+        this.searchByVersion(facet, callback);
         break;
       case 'versionState':
-        callback(App.HostStackVersion.statusDefinition.map(function (status) {
-          map[App.HostStackVersion.formatStatus(status)] = status;
-          return App.HostStackVersion.formatStatus(status);
-        }).reject(function (item) {
-          return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
-        }));
+        this.searchByVersionState(facet, callback, map);
         break;
       case 'healthClass':
-        var category_mocks = this.healthStatusCategories;
-        callback(category_mocks.slice(1).map(function (category) {
-          map[category.value] = category.healthStatus;
-          return category.value;
-        }).reject(function (item) {
-          return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
-        }), {preserveOrder: true});
+        this.searchByHealthClass(facet, callback, map);
         break;
       case 'services':
-        callback(App.Service.find().toArray().map(function (service) {
-          serviceMap[App.format.role(service.get('serviceName'), true)] = service.get('serviceName');
-          return App.format.role(service.get('serviceName'), true);
-        }).reject(function (item) {
-          return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
-        }), {preserveOrder: true});
+        this.searchByServices(facet, callback);
         break;
       case 'componentState':
-        var list = [ "All" ];
-        // client only have "ALL" state
-        if (facet.toLowerCase().indexOf("client") == -1)
-        {
-          var currentComponentFacets = this.getComponentStateFacets(null, true);
-          if (currentComponentFacets.length == 0) {
-            list = list.concat(App.HostComponentStatus.getStatusesList()
-              .reject(function(status){return status == "UPGRADE_FAILED"}) // take out 'UPGRADE_FAILED'
-              .map(function (status) {
-                map[App.HostComponentStatus.getTextStatus(status)] = status;
-                return App.HostComponentStatus.getTextStatus(status);
-              }))
-              .concat([
-                "Inservice",
-                "Decommissioned",
-                "Decommissioning",
-                "RS Decommissioned",
-                "Maintenance Mode On",
-                "Maintenance Mode Off"
-              ]);
-          }
-        }
-        callback(list, {preserveOrder: true});
+        this.searchByComponentState(facet, callback, map);
         break;
     }
   },
 
+  /**
+   *
+   * @param {string} facetValue
+   * @param {string} searchTerm
+   * @param {string} facet
+   * @param {Function} callback
+   */
+  searchByHostname: function(facetValue, searchTerm, facet, callback) {
+    var controller = App.router.get('mainHostComboSearchBoxController');
+
+    controller.getPropertySuggestions(facetValue, searchTerm).done(function() {
+      callback(controller.get('currentSuggestion').reject(function (item) {
+        return visualSearch.searchQuery.values(facet).indexOf(item) >= 0; // reject the ones already in search
+      }), {preserveMatches: true});
+    });
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   */
+  searchByRack: function(facet, callback) {
+    callback(App.Host.find().toArray().mapProperty('rack').uniq().reject(function (item) {
+      return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
+    }));
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   */
+  searchByVersion: function(facet, callback) {
+    callback(App.HostStackVersion.find().toArray()
+      .filterProperty('isVisible', true).mapProperty('displayName').uniq().reject(function (item) {
+        return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
+      }));
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   * @param {object} map
+   */
+  searchByVersionState: function(facet, callback, map) {
+    callback(App.HostStackVersion.statusDefinition.map(function (status) {
+      map[App.HostStackVersion.formatStatus(status)] = status;
+      return App.HostStackVersion.formatStatus(status);
+    }).reject(function (item) {
+      return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
+    }));
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   * @param {object} map
+   */
+  searchByHealthClass: function(facet, callback, map) {
+    //exclude "All" category
+    var category_mocks = this.get('healthStatusCategories').slice(1);
+
+    callback(category_mocks.map(function (category) {
+      map[category.value] = category.healthStatus;
+      return category.value;
+    }).reject(function (item) {
+      return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
+    }), {preserveOrder: true});
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   */
+  searchByServices: function(facet, callback) {
+    var serviceMap = this.get('serviceMap');
+
+    callback(App.Service.find().toArray().map(function (service) {
+      serviceMap[App.format.role(service.get('serviceName'), true)] = service.get('serviceName');
+      return App.format.role(service.get('serviceName'), true);
+    }).reject(function (item) {
+      return visualSearch.searchQuery.values(facet).indexOf(item) >= 0;
+    }), {preserveOrder: true});
+  },
+
+  /**
+   *
+   * @param {string} facet
+   * @param {Function} callback
+   * @param {object} map
+   */
+  searchByComponentState: function (facet, callback, map) {
+    var list = ["All"];
+    // client only have "ALL" state
+    if (facet.toLowerCase().indexOf("client") === -1) {
+      var currentComponentFacets = this.getComponentStateFacets(null, true);
+      if (currentComponentFacets.length === 0) {
+        list = list.concat(
+          App.HostComponentStatus.getStatusesList()
+          .reject(function (status) {
+            return status === "UPGRADE_FAILED"
+          }) // take out 'UPGRADE_FAILED'
+          .map(function (status) {
+            map[App.HostComponentStatus.getTextStatus(status)] = status;
+            return App.HostComponentStatus.getTextStatus(status);
+          })
+        )
+        .concat([
+          "Inservice",
+          "Decommissioned",
+          "Decommissioning",
+          "RS Decommissioned",
+          "Maintenance Mode On",
+          "Maintenance Mode Off"
+        ]);
+      }
+    }
+    callback(list, {preserveOrder: true});
+  },
+
   findInvalidFacet: function(searchCollection) {
     var result = null;
     var map = App.router.get('mainHostController.labelValueMap');
@@ -204,8 +280,8 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     this.set('errMsg', '')
   },
 
-  showHideClearButton: function() {
-    if(visualSearch.searchQuery.toJSON().length > 0) {
+  showHideClearButton: function () {
+    if (visualSearch.searchQuery.toJSON().length > 0) {
       $('.VS-cancel-search-box').removeClass('hide');
     } else {
       $('.VS-cancel-search-box').addClass('hide');
@@ -234,25 +310,34 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     return hostComponentList;
   },
 
-  getComponentStateFacets: function(hostComponentList, includeAllValue) {
+  /**
+   *
+   * @param {Array} hostComponentList
+   * @param {boolean} includeAllValue
+   * @returns {Array}
+   */
+  getComponentStateFacets: function (hostComponentList, includeAllValue) {
     if (!hostComponentList) {
       hostComponentList = this.getHostComponentList();
     }
-    var currentComponentFacets = visualSearch.searchQuery.toJSON().filter(function (facet) {
-      var result = !!(hostComponentList.findProperty('label', facet.category) && facet.value);
+    return visualSearch.searchQuery.toJSON().filter(function (facet) {
+      var result = Boolean(hostComponentList.findProperty('label', facet.category) && facet.value);
       if (!includeAllValue) {
-        result &= (facet.value != 'All');
+        result &= (facet.value !== 'All');
       }
       return result;
     });
-    return currentComponentFacets;
   },
 
+  /**
+   *
+   * @param {string} name
+   * @returns {Array}
+   */
   getFacetsByName: function(name) {
-    var facets = visualSearch.searchQuery.toJSON().filter(function(facet) {
+    return visualSearch.searchQuery.toJSON().filter(function(facet) {
       return facet.category === name;
     });
-    return facets;
   },
 
   filterOutOneFacetOnlyOptions: function(options) {
@@ -288,59 +373,80 @@ App.MainHostComboSearchBoxView = Em.View.extend({
     map['Maintenance Mode Off'] = 'OFF';
   },
 
-  createFilterConditions: function(searchCollection) {
-    var self = this;
-    var mainHostController = App.router.get('mainHostController');
-    var map = mainHostController.get('labelValueMap');
+  createFilterConditions: function (searchCollection) {
+    var map = App.router.get('mainHostController.labelValueMap');
     var serviceMap = this.get('serviceMap');
     var filterConditions = Em.A();
+
     searchCollection.models.forEach(function (model) {
       var tag = model.attributes;
       var category = map[tag.category] || tag.category;
-      var value = (category == 'services')? (serviceMap[tag.value] || tag.value) : (map[tag.value] || tag.value);
-
-      var iColumn = self.getFilterColumn(category, value);
-      var filterValue = self.getFilterValue(category, value);
+      var value = (category === 'services') ? (serviceMap[tag.value] || tag.value) : (map[tag.value] || tag.value);
+      var iColumn = this.getFilterColumn(category, value);
+      var filterValue = this.getFilterValue(category, value);
       var condition = filterConditions.findProperty('iColumn', iColumn);
+
       if (condition) {
         // handle multiple facets with same category
-        if (typeof condition.value == 'string') {
+        if (typeof condition.value === 'string') {
           condition.value = [condition.value, filterValue];
-        } else if (Em.isArray(condition.value) && condition.value.indexOf(filterValue) == -1) {
+        } else if (Em.isArray(condition.value) && condition.value.indexOf(filterValue) === -1) {
           condition.value.push(filterValue);
         }
       } else {
-        var type = 'string';
-        if (category === 'cpu') {
-          type = 'number';
-        }
-        if (category === 'memoryFormatted') {
-          type = 'ambari-bandwidth';
-        }
-        condition = {
-          skipFilter: false,
-          iColumn: iColumn,
-          value: filterValue,
-          type: type
-        };
-        filterConditions.push(condition);
+        filterConditions.push(this.createCondition(category, iColumn, filterValue));
       }
-    });
+    }, this);
     return filterConditions;
   },
 
+  /**
+   *
+   * @param {string} category
+   * @param {number} iColumn
+   * @param {string} filterValue
+   * @returns {{skipFilter: boolean, iColumn: number, value: string, type: string}}
+   */
+  createCondition: function(category, iColumn, filterValue) {
+    var type = 'string';
+    if (category === 'cpu') {
+      type = 'number';
+    }
+    if (category === 'memoryFormatted') {
+      type = 'ambari-bandwidth';
+    }
+    return {
+      skipFilter: false,
+      iColumn: iColumn,
+      value: filterValue,
+      type: type
+    };
+  },
+
+  /**
+   *
+   * @param {string} category
+   * @param {string} value
+   * @returns {number}
+   */
   getFilterColumn: function(category, value) {
-    var iColumn = -1;
+    var iColumn;
     if (this.get('controller').isComponentStateFacet(category)) {
-      iColumn = App.router.get('mainHostController').get('colPropAssoc').indexOf('componentState');
+      iColumn = App.router.get('mainHostController.colPropAssoc').indexOf('componentState');
     } else if (this.get('controller').isComplexHealthStatusFacet(value)) {
       iColumn = this.get('healthStatusCategories').findProperty('healthStatus', value).column;
     } else {
-      iColumn = App.router.get('mainHostController').get('colPropAssoc').indexOf(category);
+      iColumn = App.router.get('mainHostController.colPropAssoc').indexOf(category);
     }
     return iColumn;
   },
 
+  /**
+   *
+   * @param {string} category
+   * @param {string} value
+   * @returns {string}
+   */
   getFilterValue: function(category, value) {
     var filterValue = value;
     if (this.get('controller').isComponentStateFacet(category)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/app/views/main/host/hosts_table_menu_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/hosts_table_menu_view.js b/ambari-web/app/views/main/host/hosts_table_menu_view.js
index 8849244..f820c70 100644
--- a/ambari-web/app/views/main/host/hosts_table_menu_view.js
+++ b/ambari-web/app/views/main/host/hosts_table_menu_view.js
@@ -44,10 +44,10 @@ App.HostTableMenuView = Em.View.extend({
     });
   }.property(),
 
-  getBulkMenuItemsPerServiceComponent: function(){
+  getBulkMenuItemsPerServiceComponent: function () {
     var menuItems = [];
     App.StackServiceComponent.find().forEach(function (stackComponent) {
-      if(stackComponent.get('hasBulkCommandsDefinition')){
+      if (stackComponent.get('hasBulkCommandsDefinition')) {
         var menuItem = O.create({
           serviceName: stackComponent.get('serviceName'),
           componentName: stackComponent.get('componentName'),
@@ -208,16 +208,18 @@ App.HostTableMenuView = Em.View.extend({
       }.property('App.router.mainServiceController.content.@each', 'content'),
 
       tooltipMsg: function () {
-        return (this.get('disabledElement') == 'disabled') ?
-          Em.I18n.t('hosts.decommission.tooltip.warning').format(this.get('content.message'), App.format.role(this.get('content.componentName'), false)) : '';
+        var displayName = App.format.role(this.get('content.componentName'), false);
+        return (this.get('disabledElement') === 'disabled')
+          ? Em.I18n.t('hosts.decommission.tooltip.warning').format(this.get('content.message'), displayName)
+          : '';
       }.property('disabledElement', 'content.componentName'),
 
       disabledElement: function () {
-        return this.get('service.workStatus') == 'STARTED' ? '' : 'disabled';
+        return this.get('service.workStatus') === 'STARTED' ? '' : 'disabled';
       }.property('service.workStatus'),
 
       click: function () {
-        if (this.get('disabledElement') == 'disabled') {
+        if (this.get('disabledElement') === 'disabled') {
           return;
         }
         this.get('controller').bulkOperationConfirm(this.get('content'), this.get('selection'));

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/app/views/main/host/log_metrics.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/log_metrics.js b/ambari-web/app/views/main/host/log_metrics.js
index 20f5ec6..92bd86e 100644
--- a/ambari-web/app/views/main/host/log_metrics.js
+++ b/ambari-web/app/views/main/host/log_metrics.js
@@ -36,7 +36,7 @@ App.MainHostLogMetrics = Em.View.extend({
    * @type {ServiceLogMetricsObject[]}
    */
   logsData: function() {
-    var services = this.get('content').get('hostComponents').mapProperty('service').uniq();
+    var services = this.get('content.hostComponents').mapProperty('service').uniq();
     var logLevels = ['fatal', 'critical', 'error', 'warning', 'info', 'debug'];
     return services.map(function(service) {
       var levels = logLevels.map(function(level) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/combo_search_box_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/combo_search_box_test.js b/ambari-web/test/views/main/host/combo_search_box_test.js
index f6ff040..c82167c 100644
--- a/ambari-web/test/views/main/host/combo_search_box_test.js
+++ b/ambari-web/test/views/main/host/combo_search_box_test.js
@@ -22,7 +22,24 @@ var view;
 describe('App.MainHostComboSearchBoxView', function () {
 
   beforeEach(function () {
-    view = App.MainHostComboSearchBoxView.create();
+    view = App.MainHostComboSearchBoxView.create({
+      parentView: Em.Object.create(),
+      controller: Em.Object.create({
+        isComponentStateFacet: Em.K,
+        isComplexHealthStatusFacet: Em.K
+      })
+    });
+    window.visualSearch = {
+      searchQuery: {
+        values: function() {
+          return [];
+        },
+        toJSON: Em.K
+      },
+      searchBox: {
+        setQuery: Em.K
+      }
+    }
   });
 
   describe("#didInsertElement()", function() {
@@ -30,14 +47,950 @@ describe('App.MainHostComboSearchBoxView', function () {
     beforeEach(function() {
       sinon.stub(view, 'initVS');
       sinon.stub(view, 'showHideClearButton');
+      sinon.stub(view, 'restoreComboFilterQuery');
+      view.didInsertElement();
     });
     afterEach(function() {
       view.initVS.restore();
+      view.showHideClearButton.restore();
+      view.restoreComboFilterQuery.restore();
     });
 
     it("initVS should be called", function() {
-      view.didInsertElement();
       expect(view.initVS.calledOnce).to.be.true;
     });
+
+    it("showHideClearButton should be called", function() {
+      expect(view.showHideClearButton.calledOnce).to.be.true;
+    });
+
+    it("restoreComboFilterQuery should be called", function() {
+      expect(view.restoreComboFilterQuery.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#setupLabelMap", function () {
+
+    beforeEach(function() {
+      sinon.stub(view, 'setupLabelMap');
+      sinon.stub(VS, 'init').returns({init: 'init'});
+      view.initVS();
+    });
+
+    afterEach(function() {
+      view.setupLabelMap.restore();
+      VS.init.restore();
+    });
+
+    it("setupLabelMap should be called", function() {
+      expect(view.setupLabelMap.calledOnce).to.be.true;
+    });
+
+    it("window.visualSearch should be set", function() {
+      expect(window.visualSearch).to.be.eql({init: 'init'});
+    });
+  });
+
+  describe("#search()", function () {
+
+    beforeEach(function() {
+      view.set('parentView.parentView', Em.Object.create({
+        updateComboFilter: Em.K,
+        controller: {name: 'ctrl1'}
+      }));
+      sinon.stub(view.get('parentView.parentView'), 'updateComboFilter');
+      sinon.stub(view, 'createFilterConditions').returns([{}]);
+      sinon.stub(view, 'clearErrMsg');
+      sinon.stub(view, 'showErrMsg');
+      sinon.stub(App.db, 'setComboSearchQuery');
+      this.mockFacet = sinon.stub(view, 'findInvalidFacet');
+    });
+
+    afterEach(function() {
+      App.db.setComboSearchQuery.restore();
+      view.clearErrMsg.restore();
+      view.showErrMsg.restore();
+      this.mockFacet.restore();
+      view.createFilterConditions.restore();
+      view.get('parentView.parentView').updateComboFilter.restore();
+    });
+
+    it("clearErrMsg should be called", function() {
+      view.search('query', {});
+      expect(view.clearErrMsg.calledOnce).to.be.true;
+    });
+
+    it("showErrMsg should be called", function() {
+      this.mockFacet.returns({});
+      view.search('query', {});
+      expect(view.showErrMsg.calledWith({})).to.be.true;
+    });
+
+    it("App.db.setComboSearchQuery should be called", function() {
+      view.search('query', {});
+      expect(App.db.setComboSearchQuery.calledWith('ctrl1', 'query')).to.be.true;
+    });
+
+    it("updateComboFilter should be called", function() {
+      view.search('query', {});
+      expect(view.get('parentView.parentView').updateComboFilter.calledWith([{}])).to.be.true;
+    });
+  });
+
+  describe("#facetMatches()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(view, 'showHideClearButton');
+      sinon.stub(view, 'getHostComponentList').returns([
+        Em.Object.create({componentName: 'C1'})
+      ]);
+      this.mock = sinon.stub(view, 'getComponentStateFacets').returns([]);
+      sinon.stub(view, 'filterOutOneFacetOnlyOptions', function(list) {
+        return list;
+      });
+      sinon.stub(container, 'callback');
+    });
+
+    afterEach(function() {
+      view.showHideClearButton.restore();
+      view.getHostComponentList.restore();
+      this.mock.restore();
+      view.filterOutOneFacetOnlyOptions.restore();
+      container.callback.restore();
+    });
+
+    it("showHideClearButton should be called", function() {
+      view.facetMatches(container.callback);
+      expect(view.showHideClearButton.calledOnce).to.be.true;
+    });
+
+    it("callback should be called", function() {
+      view.facetMatches(container.callback);
+      expect(container.callback.calledWith(
+        [
+          {label: 'Host Name', category: 'Host'},
+          {label: 'IP', category: 'Host'},
+          {label: 'Host Status', category: 'Host'},
+          {label: 'Cores', category: 'Host'},
+          {label: 'RAM', category: 'Host'},
+          {label: 'Stack Version', category: 'Host'},
+          {label: 'Version State', category: 'Host'},
+          {label: 'Rack', category: 'Host'},
+          {label: 'Service', category: 'Service'},
+          Em.Object.create({componentName: 'C1'})
+        ],
+        {preserveOrder: true}
+      )).to.be.true;
+    });
+
+    it("callback should be called, facets not empty", function() {
+      this.mock.returns([{}]);
+      view.facetMatches(container.callback);
+      expect(container.callback.calledWith(
+        [
+          {label: 'Host Name', category: 'Host'},
+          {label: 'IP', category: 'Host'},
+          {label: 'Host Status', category: 'Host'},
+          {label: 'Cores', category: 'Host'},
+          {label: 'RAM', category: 'Host'},
+          {label: 'Stack Version', category: 'Host'},
+          {label: 'Version State', category: 'Host'},
+          {label: 'Rack', category: 'Host'},
+          {label: 'Service', category: 'Service'}
+        ],
+        {preserveOrder: true}
+      )).to.be.true;
+    });
+  });
+
+  describe("#valueMatches()", function () {
+    var controller = {
+      isComponentStateFacet: Em.K
+    };
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(view, 'showHideClearButton');
+      this.mockRouter = sinon.stub(App.router, 'get');
+      this.mockRouter.withArgs('mainHostComboSearchBoxController').returns(controller);
+      this.mockRouter.withArgs('mainHostController.labelValueMap').returns({
+        key1: 'hostName'
+      });
+      this.mockIsComp = sinon.stub(controller, 'isComponentStateFacet').returns(false);
+      sinon.stub(container, 'callback');
+      sinon.stub(view, 'searchByHostname');
+      sinon.stub(view, 'searchByRack');
+      sinon.stub(view, 'searchByVersion');
+      sinon.stub(view, 'searchByVersionState');
+      sinon.stub(view, 'searchByHealthClass');
+      sinon.stub(view, 'searchByServices');
+      sinon.stub(view, 'searchByComponentState');
+    });
+
+    afterEach(function() {
+      view.showHideClearButton.restore();
+      this.mockRouter.restore();
+      this.mockIsComp.restore();
+      container.callback.restore();
+      view.searchByHostname.restore();
+      view.searchByRack.restore();
+      view.searchByVersion.restore();
+      view.searchByVersionState.restore();
+      view.searchByHealthClass.restore();
+      view.searchByServices.restore();
+      view.searchByComponentState.restore();
+    });
+
+    it("showHideClearButton should be called", function() {
+      view.valueMatches('', '', container.callback);
+      expect(view.showHideClearButton.calledOnce).to.be.true;
+    });
+
+    it("searchByHostname should be called", function() {
+      view.valueMatches('key1', 'term1', container.callback);
+      expect(view.searchByHostname.calledWith(
+        'hostName', 'term1', 'key1', container.callback
+      )).to.be.true;
+    });
+
+    it("searchByRack should be called", function() {
+      view.valueMatches('rack', 'term1', container.callback);
+      expect(view.searchByRack.calledWith('rack', container.callback)).to.be.true;
+    });
+
+    it("searchByVersion should be called", function() {
+      view.valueMatches('version', 'term1', container.callback);
+      expect(view.searchByVersion.calledWith('version', container.callback)).to.be.true;
+    });
+
+    it("searchByVersionState should be called", function() {
+      view.valueMatches('versionState', 'term1', container.callback);
+      expect(view.searchByVersionState.calledWith('versionState', container.callback, {
+        key1: 'hostName'
+      })).to.be.true;
+    });
+
+    it("searchByHealthClass should be called", function() {
+      view.valueMatches('healthClass', 'term1', container.callback);
+      expect(view.searchByHealthClass.calledWith('healthClass', container.callback, {
+        key1: 'hostName'
+      })).to.be.true;
+    });
+
+    it("searchByServices should be called", function() {
+      view.valueMatches('services', 'term1', container.callback);
+      expect(view.searchByServices.calledWith('services', container.callback)).to.be.true;
+    });
+
+    it("searchByComponentState should be called", function() {
+      this.mockIsComp.returns(true);
+      view.valueMatches('componentState', 'term1', container.callback);
+      expect(view.searchByComponentState.calledWith('componentState', container.callback, {
+        key1: 'hostName'
+      })).to.be.true;
+    });
+  });
+
+  describe("#searchByHostname()", function () {
+    var controller = Em.Object.create({
+      getPropertySuggestions: Em.K,
+      currentSuggestion: ['sg1', 'sg2']
+    });
+    var container = {
+      callback: Em.K
+    };
+
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(controller);
+      sinon.stub(controller, 'getPropertySuggestions').returns({
+        done: function(callback) {
+          callback();
+        }
+      });
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['sg1']);
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+      controller.getPropertySuggestions.restore();
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByHostname('fv1', 'term1', 'f1', container.callback);
+      expect(container.callback.calledWith(['sg2'], {preserveMatches: true})).to.be.true;
+    });
+  });
+
+  describe("#searchByRack()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['r1']);
+      sinon.stub(App.Host, 'find').returns([{rack: 'r1'}, {rack: 'r2'}]);
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+      App.Host.find.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByRack('f1', container.callback);
+      expect(container.callback.calledWith(['r2'])).to.be.true;
+    });
+  });
+
+  describe("#searchByVersion()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['v1']);
+      sinon.stub(App.HostStackVersion, 'find').returns([
+        {
+          isVisible: true,
+          displayName: 'v1'
+        },
+        {
+          isVisible: true,
+          displayName: 'v2'
+        },
+        {
+          isVisible: false,
+          displayName: 'v3'
+        }
+      ]);
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+      App.HostStackVersion.find.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByVersion('f1', container.callback);
+      expect(container.callback.calledWith(['v2'])).to.be.true;
+    });
+  });
+
+  describe("#searchByVersionState()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['INSTALLED']);
+      sinon.stub(App.HostStackVersion, 'formatStatus', function(status) {
+        return status;
+      });
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+      App.HostStackVersion.formatStatus.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByVersionState('f1', container.callback, {});
+      expect(container.callback.getCall(0).args[0]).to.be.eql([
+        "INSTALLING",
+        "INSTALL_FAILED",
+        "OUT_OF_SYNC",
+        "CURRENT",
+        "UPGRADING",
+        "UPGRADE_FAILED"
+      ]);
+    });
+  });
+
+  describe("#searchByHealthClass()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['c1']);
+      view.set('healthStatusCategories', [
+        {value: 'All'},
+        {value: 'c1'},
+        {value: 'c2'}
+      ]);
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByHealthClass('f1', container.callback, {});
+      expect(container.callback.getCall(0).args).to.be.eql([
+        ['c2'], {preserveOrder: true}
+      ]);
+    });
+  });
+
+  describe("#searchByServices()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      sinon.stub(visualSearch.searchQuery, 'values').returns(['s1']);
+      sinon.stub(App.Service, 'find').returns([
+        Em.Object.create({serviceName: 'S1'}),
+        Em.Object.create({serviceName: 'S2'})
+      ]);
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      visualSearch.searchQuery.values.restore();
+      App.Service.find.restore();
+    });
+
+    it("callback should be called", function() {
+      view.searchByServices('f1', container.callback);
+      expect(container.callback.getCall(0).args).to.be.eql([
+        ['S2'], {preserveOrder: true}
+      ]);
+    });
+  });
+
+
+  describe("#searchByComponentState()", function () {
+    var container = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(container, 'callback');
+      this.mock = sinon.stub(view, 'getComponentStateFacets').returns(['cf1']);
+      sinon.stub(App.HostComponentStatus, 'getStatusesList').returns([
+        'UPGRADE_FAILED', 'ST1'
+      ]);
+      sinon.stub(App.HostComponentStatus, 'getTextStatus', function(status) {
+        return status;
+      });
+    });
+
+    afterEach(function() {
+      container.callback.restore();
+      this.mock.restore();
+      App.HostComponentStatus.getStatusesList.restore();
+      App.HostComponentStatus.getTextStatus.restore();
+    });
+
+    it("callback should be called, client", function() {
+      view.searchByComponentState('client', container.callback, {});
+      expect(container.callback.getCall(0).args).to.be.eql([
+        ['All'], {preserveOrder: true}
+      ]);
+    });
+
+    it("callback should be called", function() {
+      view.searchByComponentState('f1', container.callback, {});
+      expect(container.callback.getCall(0).args).to.be.eql([
+        ['All'], {preserveOrder: true}
+      ]);
+    });
+
+    it("callback should be called, empty state facets", function() {
+      this.mock.returns([]);
+      view.searchByComponentState('f1', container.callback, {});
+      expect(container.callback.getCall(0).args).to.be.eql([
+        [
+          "All",
+          "ST1",
+          "Inservice",
+          "Decommissioned",
+          "Decommissioning",
+          "RS Decommissioned",
+          "Maintenance Mode On",
+          "Maintenance Mode Off"
+        ], {preserveOrder: true}
+      ]);
+    });
+  });
+
+  describe("#findInvalidFacet()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns({});
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+    });
+
+    it("empty searchCollection", function() {
+      expect(view.findInvalidFacet({models: []})).to.be.null;
+    });
+
+    it("searchCollection has values", function() {
+      expect(view.findInvalidFacet({models: [{
+        attributes: {
+          category: 'cat1'
+        }
+      }]})).to.be.eql({
+          attributes: {
+            category: 'cat1'
+          }
+        });
+    });
+  });
+
+  describe("#showErrMsg()", function () {
+
+    it("errMsg should be set", function() {
+      view.showErrMsg({attributes: {value: 'val1'}});
+      expect(view.get('errMsg')).to.be.equal('val1 ' + Em.I18n.t('hosts.combo.search.invalidCategory'));
+    });
+  });
+
+  describe("#clearErrMsg()", function () {
+
+    it("errMsg should be empty", function() {
+      view.clearErrMsg();
+      expect(view.get('errMsg')).to.be.empty;
+    });
+  });
+
+  describe("#showHideClearButton()", function () {
+    var container = {
+      removeClass: Em.K,
+      addClass: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(window, '$').returns(container);
+      sinon.spy(container, 'removeClass');
+      sinon.spy(container, 'addClass');
+      this.mock = sinon.stub(visualSearch.searchQuery, 'toJSON');
+    });
+
+    afterEach(function() {
+      window.$.restore();
+      container.removeClass.restore();
+      container.addClass.restore();
+      visualSearch.searchQuery.toJSON.restore();
+    });
+
+    it("class should be added", function() {
+      this.mock.returns([]);
+      view.showHideClearButton();
+      expect(container.addClass.calledWith('hide')).to.be.true;
+    });
+
+    it("class should be removed", function() {
+      this.mock.returns(['f']);
+      view.showHideClearButton();
+      expect(container.removeClass.calledWith('hide')).to.be.true;
+    });
+  });
+
+  describe("#restoreComboFilterQuery()", function () {
+
+    beforeEach(function() {
+      this.mockQuery = sinon.stub(App.db, 'getComboSearchQuery');
+      sinon.stub(visualSearch.searchBox, 'setQuery');
+    });
+
+    afterEach(function() {
+      this.mockQuery.restore();
+      visualSearch.searchBox.setQuery.restore();
+    });
+
+    it("query is empty", function() {
+      this.mockQuery.returns('');
+      view.restoreComboFilterQuery();
+      expect(visualSearch.searchBox.setQuery.called).to.be.false;
+    });
+
+    it("query has value", function() {
+      this.mockQuery.returns('query');
+      view.restoreComboFilterQuery();
+      expect(visualSearch.searchBox.setQuery.calledWith('query')).to.be.true;
+    });
+  });
+
+  describe("#getHostComponentList()", function () {
+    var labelValueMap = {};
+
+    beforeEach(function() {
+      sinon.stub(App.MasterComponent, 'find').returns([
+        Em.Object.create({totalCount: 0}),
+        Em.Object.create({totalCount: 1}),
+        Em.Object.create({totalCount: 1, displayName: 'mc1', componentName: 'MC1'})
+      ]);
+      sinon.stub(App.SlaveComponent, 'find').returns([
+        Em.Object.create({totalCount: 0}),
+        Em.Object.create({totalCount: 1}),
+        Em.Object.create({totalCount: 1, displayName: 'sc1', componentName: 'SC1'})
+      ]);
+      sinon.stub(App.ClientComponent, 'find').returns([
+        Em.Object.create({totalCount: 0}),
+        Em.Object.create({totalCount: 1}),
+        Em.Object.create({totalCount: 1, displayName: 'cc1', componentName: 'CC1'})
+      ]);
+      sinon.stub(App.router, 'get').returns(labelValueMap);
+    });
+
+    afterEach(function() {
+      App.MasterComponent.find.restore();
+      App.SlaveComponent.find.restore();
+      App.ClientComponent.find.restore();
+      App.router.get.restore();
+    });
+
+    it("should return host-component list", function() {
+      expect(view.getHostComponentList()).to.be.eql([
+        {label: 'mc1', category: 'Component'},
+        {label: 'sc1', category: 'Component'},
+        {label: 'cc1', category: 'Component'}
+      ]);
+      expect(labelValueMap).to.be.eql({
+        mc1: 'MC1',
+        sc1: 'SC1',
+        cc1: 'CC1'
+      });
+    });
+  });
+
+  describe("#getComponentStateFacets()", function () {
+
+    beforeEach(function() {
+      sinon.stub(visualSearch.searchQuery, 'toJSON').returns([
+        {category: 'cat1', value: 'val'},
+        {category: 'cat2', value: null},
+        {category: 'cat3', value: 'All'}
+      ]);
+      sinon.stub(view, 'getHostComponentList').returns([{label: ''}]);
+    });
+
+    afterEach(function() {
+      visualSearch.searchQuery.toJSON.restore();
+      view.getHostComponentList.restore();
+    });
+
+    it("label='', includeAllValue=true", function() {
+      expect(view.getComponentStateFacets(null, true)).to.be.empty;
+    });
+
+    it("label=cat2, includeAllValue=true", function() {
+      expect(view.getComponentStateFacets([{label: 'cat2'}], true)).to.be.empty;
+    });
+
+    it("label=cat1, includeAllValue=true", function() {
+      expect(view.getComponentStateFacets([{label: 'cat1'}], true)).to.be.eql([
+        {category: 'cat1', value: 'val'}
+      ]);
+    });
+
+    it("label=cat1, includeAllValue=false", function() {
+      expect(view.getComponentStateFacets([{label: 'cat1'}], false)).to.be.eql([
+        {category: 'cat1', value: 'val'}
+      ]);
+    });
+
+    it("label=cat3, includeAllValue=false", function() {
+      expect(view.getComponentStateFacets([{label: 'cat3'}], false)).to.be.empty;
+    });
+  });
+
+  describe("#getFacetsByName()", function () {
+
+    beforeEach(function() {
+      sinon.stub(visualSearch.searchQuery, 'toJSON').returns([
+        {category: 'f1'},
+        {category: 'f2'}
+      ]);
+    });
+
+    afterEach(function() {
+      visualSearch.searchQuery.toJSON.restore();
+    });
+
+    it("should return facets", function() {
+      expect(view.getFacetsByName('f1')).to.be.eql([{category: 'f1'}]);
+    });
+  });
+
+  describe("#filterOutOneFacetOnlyOptions()", function () {
+
+    beforeEach(function() {
+      this.mock = sinon.stub(view, 'getFacetsByName');
+    });
+
+    afterEach(function() {
+      view.getFacetsByName.restore();
+    });
+
+    it("should return empty, no facets", function() {
+      this.mock.returns([]);
+      expect(view.filterOutOneFacetOnlyOptions([])).to.be.empty;
+    });
+
+    it("should return empty", function() {
+      this.mock.returns([{}]);
+      expect(view.filterOutOneFacetOnlyOptions([{label: 'Cores'}])).to.be.empty;
+    });
+
+    it("should return options", function() {
+      this.mock.returns([{}]);
+      expect(view.filterOutOneFacetOnlyOptions([{label: 'f1'}])).to.be.eql([{label: 'f1'}]);
+    });
+  });
+
+  describe("#setupLabelMap()", function () {
+    var map = {
+      'init': 'init'
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(map);
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+    });
+
+    it("should set map", function() {
+      view.setupLabelMap();
+      expect(map).to.be.eql({
+        'init': 'init',
+        'All': 'ALL',
+        'Host Name': 'hostName',
+        'IP': 'ip',
+        'Host Status': 'healthClass',
+        'Cores': 'cpu',
+        'RAM': 'memoryFormatted',
+        'Stack Version': 'version',
+        'Version State': 'versionState',
+        'Rack': 'rack',
+        'Service': 'services',
+        'Inservice': 'INSERVICE',
+        'Decommissioned': 'DECOMMISSIONED',
+        'Decommissioning': 'DECOMMISSIONING',
+        'RS Decommissioned': 'RS_DECOMMISSIONED',
+        'Maintenance Mode On': 'ON',
+        'Maintenance Mode Off': 'OFF'
+      });
+    });
+  });
+
+  describe("#createFilterConditions()", function () {
+
+    beforeEach(function() {
+      sinon.stub(view, 'getFilterColumn').returns(1);
+      sinon.stub(view, 'getFilterValue').returns('filterValue');
+      sinon.stub(App.router, 'get').returns({'k1': 'services'});
+      sinon.stub(view, 'createCondition').returns({
+        skipFilter: false,
+        iColumn: 1,
+        value: 'val1',
+        type: 't1'
+      });
+      view.set('serviceMap', {k2: 'val2'});
+    });
+
+    afterEach(function() {
+      view.getFilterColumn.restore();
+      view.getFilterValue.restore();
+      App.router.get.restore();
+      view.createCondition.restore();
+    });
+
+    it("empty models", function() {
+      var searchCollection = {
+        models: []
+      };
+      expect(view.createFilterConditions(searchCollection)).to.be.empty;
+    });
+
+    it("should return conditions, string value", function() {
+      var searchCollection = {
+        models: [
+          {
+            attributes: {
+              category: 'k1',
+              value: 'k2'
+            }
+          },
+          {
+            attributes: {
+              category: 'k1',
+              value: 'k2'
+            }
+          }
+        ]
+      };
+      expect(JSON.stringify(view.createFilterConditions(searchCollection))).to.be.equal(JSON.stringify([
+        {
+          "skipFilter": false,
+          "iColumn": 1,
+          "value": [
+            "val1",
+            "filterValue"
+          ],
+          "type": "t1"
+        }
+      ]));
+    });
+
+
+    it("should return conditions, array value", function() {
+      var searchCollection = {
+        models: [
+          {
+            attributes: {
+              category: 'cat1',
+              value: ['val1']
+            }
+          },
+          {
+            attributes: {
+              category: 'cat2',
+              value: 'val2'
+            }
+          }
+        ]
+      };
+      expect(JSON.stringify(view.createFilterConditions(searchCollection))).to.be.equal(JSON.stringify([
+        {
+          "skipFilter": false,
+          "iColumn": 1,
+          "value": [
+            "val1",
+            "filterValue"
+          ],
+          "type": "t1"
+        }
+      ]));
+    });
+  });
+
+  describe("#createCondition()", function () {
+    var testCases = [
+      {
+        category: 'cpu',
+        iColumn: 1,
+        filterValue: 'val1',
+        expected: {
+          skipFilter: false,
+          iColumn: 1,
+          value: 'val1',
+          type: 'number'
+        }
+      },
+      {
+        category: 'memoryFormatted',
+        iColumn: 2,
+        filterValue: 'val2',
+        expected: {
+          skipFilter: false,
+          iColumn: 2,
+          value: 'val2',
+          type: 'ambari-bandwidth'
+        }
+      },
+      {
+        category: 'cat1',
+        iColumn: 3,
+        filterValue: ['val2'],
+        expected: {
+          skipFilter: false,
+          iColumn: 3,
+          value: ['val2'],
+          type: 'string'
+        }
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("category = " + test.category +
+         " iColumn = " + test.iColumn +
+         " filterValue = " + test.filterValue, function() {
+        expect(view.createCondition(test.category, test.iColumn, test.filterValue)).to.be.eql(test.expected);
+      });
+    });
+  });
+
+  describe("#getFilterColumn()", function () {
+
+    beforeEach(function() {
+      this.mockComponentState = sinon.stub(view.get('controller'), 'isComponentStateFacet');
+      this.mockComplexHealth = sinon.stub(view.get('controller'), 'isComplexHealthStatusFacet');
+      sinon.stub(App.router, 'get').returns(['componentState', 'cat1']);
+      view.set('healthStatusCategories', [{healthStatus: 'val1', column: 2}]);
+    });
+
+    afterEach(function() {
+      this.mockComponentState.restore();
+      this.mockComplexHealth.restore();
+      App.router.get.restore();
+    });
+
+    it("should return componentState column", function() {
+      this.mockComponentState.returns(true);
+      expect(view.getFilterColumn('cat1', 'val1')).to.be.equal(0);
+    });
+
+    it("should return healthStatus column", function() {
+      this.mockComplexHealth.returns(true);
+      expect(view.getFilterColumn('cat1', 'val1')).to.be.equal(2);
+    });
+
+    it("should return custom column", function() {
+      expect(view.getFilterColumn('cat1', 'val1')).to.be.equal(1);
+    });
+  });
+
+  describe("#getFilterValue()", function () {
+
+    beforeEach(function() {
+      this.mockComponentState = sinon.stub(view.get('controller'), 'isComponentStateFacet');
+      this.mockComplexHealth = sinon.stub(view.get('controller'), 'isComplexHealthStatusFacet');
+      view.set('healthStatusCategories', [{healthStatus: 'val1', filterValue: 'filterValue'}]);
+    });
+
+    afterEach(function() {
+      this.mockComponentState.restore();
+      this.mockComplexHealth.restore();
+    });
+
+    it("should return componentState filter value", function() {
+      this.mockComponentState.returns(true);
+      expect(view.getFilterValue('cat1', 'val1')).to.be.equal('cat1:val1');
+    });
+
+    it("should return health filter value", function() {
+      this.mockComplexHealth.returns(true);
+      expect(view.getFilterValue('cat1', 'val1')).to.be.equal('filterValue');
+    });
+
+    it("should return other filter value", function() {
+      expect(view.getFilterValue('cat1', 'val1')).to.be.equal('val1');
+    });
   });
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/host_alerts_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/host_alerts_view_test.js b/ambari-web/test/views/main/host/host_alerts_view_test.js
index 38af1bd..2c0d59a 100644
--- a/ambari-web/test/views/main/host/host_alerts_view_test.js
+++ b/ambari-web/test/views/main/host/host_alerts_view_test.js
@@ -132,26 +132,43 @@ describe('App.MainHostAlertsView', function () {
       sinon.stub(App.db, 'getSortingStatuses').returns([
         {
           name: "state",
-          status: "sorting_asc"
+          status: "sorting"
         }
       ]);
+      sinon.stub(App.db, 'setSortingStatuses');
     });
     afterEach(function() {
       mock.loadAlertInstancesByHost.restore();
       App.router.get.restore();
       App.router.set.restore();
       App.db.getSortingStatuses.restore();
+      App.db.setSortingStatuses.restore();
     });
 
     it("loadAlertInstancesByHost should be called", function() {
       view.willInsertElement();
-      expect(App.router.set.calledWith('mainAlertInstancesController.isUpdating', true)).to.be.true;
+      expect(mock.loadAlertInstancesByHost.calledWith('host1')).to.be.true;
     });
 
-    it("App.router.set should be called", function() {
+    it("isUpdating should be true", function() {
       view.willInsertElement();
       expect(App.router.set.calledWith('mainAlertInstancesController.isUpdating', true)).to.be.true;
     });
+
+    it("App.db.setSortingStatuses should be called", function() {
+      view.set('controller.name', 'ctrl1');
+      view.willInsertElement();
+      expect(App.db.setSortingStatuses.calledWith('ctrl1', [
+        {
+          name: "state",
+          status: "sorting"
+        },
+        {
+          name: "state",
+          status: "sorting_asc"
+        }
+      ])).to.be.true;
+    });
   });
 
   describe("#didInsertElement()", function() {

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/hosts_table_menu_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/hosts_table_menu_view_test.js b/ambari-web/test/views/main/host/hosts_table_menu_view_test.js
new file mode 100644
index 0000000..0536c8c
--- /dev/null
+++ b/ambari-web/test/views/main/host/hosts_table_menu_view_test.js
@@ -0,0 +1,304 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.HostTableMenuView', function() {
+  var view;
+
+  beforeEach(function() {
+    view = App.HostTableMenuView.create();
+  });
+
+  describe("#components", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.Service, 'find').returns([Em.Object.create({serviceName: 'S1'})]);
+      this.mockMenu = sinon.stub(view, 'getBulkMenuItemsPerServiceComponent');
+    });
+
+    afterEach(function() {
+      App.Service.find.restore();
+      this.mockMenu.restore();
+    });
+
+    it("should return components", function() {
+      this.mockMenu.returns([
+        {serviceName: 'S1'},
+        {serviceName: 'S2'}
+      ]);
+      view.propertyDidChange('components');
+      expect(view.get('components')).to.be.eql([
+        {serviceName: 'S1'}
+      ]);
+    });
+  });
+
+  describe("#getBulkMenuItemsPerServiceComponent()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns([
+        Em.Object.create({
+          serviceName: 'S1',
+          componentName: 'C1',
+          bulkCommandsMasterComponentName: 'm1',
+          bulkCommandsDisplayName: 'c1',
+          hasBulkCommandsDefinition: {}
+        }),
+        Em.Object.create({
+          serviceName: 'S2',
+          componentName: 'C2',
+          bulkCommandsMasterComponentName: 'm2',
+          bulkCommandsDisplayName: 'c2',
+          hasBulkCommandsDefinition: null
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+    });
+
+    it("should return bulk menu items", function() {
+      expect(view.getBulkMenuItemsPerServiceComponent()).to.be.eql([
+        Em.Object.create({
+          serviceName: 'S1',
+          componentName: 'C1',
+          masterComponentName: 'm1',
+          componentNameFormatted: 'c1'
+        })
+      ]);
+    });
+  });
+
+  describe("#slaveItemView", function () {
+    var slaveItemView;
+
+    beforeEach(function () {
+      slaveItemView = view.get('slaveItemView').create();
+    });
+
+
+    describe("#commonOperationView", function () {
+      var commonOperationView;
+
+      beforeEach(function () {
+        commonOperationView = slaveItemView.get('commonOperationView').create({
+          controller: Em.Object.create({
+            bulkOperationConfirm: Em.K
+          })
+        });
+      });
+
+      describe("#click()", function () {
+
+        beforeEach(function () {
+          sinon.spy(commonOperationView.get('controller'), 'bulkOperationConfirm');
+        });
+
+        afterEach(function () {
+          commonOperationView.get('controller').bulkOperationConfirm.restore();
+        });
+
+        it("bulkOperationConfirm should be called", function () {
+          commonOperationView.setProperties({
+            content: {},
+            selection: 'selection'
+          });
+          commonOperationView.click();
+          expect(commonOperationView.get('controller').bulkOperationConfirm.calledWith(
+            {}, 'selection'
+          )).to.be.true;
+        });
+      });
+    });
+
+    describe("#advancedOperationView", function () {
+      var advancedOperationView;
+
+      beforeEach(function() {
+        advancedOperationView = slaveItemView.get('advancedOperationView').create({
+          content: Em.Object.create(),
+          controller: Em.Object.create({
+            bulkOperationConfirm: Em.K
+          })
+        });
+      });
+
+      describe("#service", function () {
+
+        beforeEach(function() {
+          sinon.stub(App.router, 'get').returns([
+            {serviceName: 'S1'}
+          ]);
+        });
+
+        afterEach(function() {
+          App.router.get.restore();
+        });
+
+        it("should return service", function() {
+          advancedOperationView.set('content.serviceName', 'S1');
+          advancedOperationView.propertyDidChange('service');
+          expect(advancedOperationView.get('service')).to.be.eql({serviceName: 'S1'});
+        });
+      });
+
+      describe("#tooltipMsg", function () {
+
+        it("disabled", function() {
+          advancedOperationView.reopen({
+            disabledElement: 'disabled'
+          });
+          advancedOperationView.set('content.componentName', 'C1');
+          advancedOperationView.set('content.message', 'msg');
+          expect(advancedOperationView.get('tooltipMsg')).to.be.equal(
+            Em.I18n.t('hosts.decommission.tooltip.warning').format('msg', 'c1')
+          );
+        });
+
+        it("not disabled", function() {
+          advancedOperationView.reopen({
+            disabledElement: ''
+          });
+          advancedOperationView.set('content.componentName', 'C1');
+          expect(advancedOperationView.get('tooltipMsg')).to.be.empty;
+        });
+      });
+
+      describe("#disabledElement", function () {
+
+        it("workStatus=STARTED", function() {
+          advancedOperationView.reopen({
+            service: Em.Object.create({
+              workStatus: 'STARTED'
+            })
+          });
+          advancedOperationView.propertyDidChange('disabledElement');
+          expect(advancedOperationView.get('disabledElement')).to.be.empty;
+        });
+
+        it("workStatus=INSTALLED", function() {
+          advancedOperationView.reopen({
+            service: Em.Object.create({
+              workStatus: 'INSTALLED'
+            })
+          });
+          advancedOperationView.propertyDidChange('disabledElement');
+          expect(advancedOperationView.get('disabledElement')).to.be.equal('disabled');
+        });
+      });
+
+      describe("#click()", function () {
+
+        beforeEach(function () {
+          sinon.spy(advancedOperationView.get('controller'), 'bulkOperationConfirm');
+        });
+
+        afterEach(function () {
+          advancedOperationView.get('controller').bulkOperationConfirm.restore();
+        });
+
+        it("bulkOperationConfirm should be called", function () {
+          advancedOperationView.setProperties({
+            content: {},
+            selection: 'selection'
+          });
+          advancedOperationView.reopen({
+            disabledElement: ''
+          });
+          advancedOperationView.click();
+          expect(advancedOperationView.get('controller').bulkOperationConfirm.calledWith(
+            {}, 'selection'
+          )).to.be.true;
+        });
+
+        it("bulkOperationConfirm should not be called", function () {
+          advancedOperationView.setProperties({
+            content: {},
+            selection: 'selection'
+          });
+          advancedOperationView.reopen({
+            disabledElement: 'disabled'
+          });
+          advancedOperationView.click();
+          expect(advancedOperationView.get('controller').bulkOperationConfirm.called).to.be.false;
+        });
+      });
+
+      describe("#didInsertElement()", function () {
+
+        beforeEach(function() {
+          sinon.stub(App, 'tooltip');
+        });
+
+        afterEach(function() {
+          App.tooltip.restore();
+        });
+
+        it("App.tooltip should be called", function() {
+          advancedOperationView.didInsertElement();
+          expect(App.tooltip.calledOnce).to.be.true;
+        });
+      });
+    });
+  });
+
+  describe("#hostItemView", function () {
+    var hostItemView;
+
+    beforeEach(function () {
+      hostItemView = view.get('hostItemView').create();
+    });
+
+    describe("#operationView", function () {
+      var operationView;
+
+      beforeEach(function () {
+        operationView = hostItemView.get('operationView').create({
+          controller: Em.Object.create({
+            bulkOperationConfirm: Em.K
+          })
+        });
+      });
+
+      describe("#click()", function () {
+
+        beforeEach(function () {
+          sinon.spy(operationView.get('controller'), 'bulkOperationConfirm');
+        });
+
+        afterEach(function () {
+          operationView.get('controller').bulkOperationConfirm.restore();
+        });
+
+        it("bulkOperationConfirm should be called", function () {
+          operationView.setProperties({
+            content: {},
+            selection: 'selection'
+          });
+          operationView.click();
+          expect(operationView.get('controller').bulkOperationConfirm.calledWith(
+            {}, 'selection'
+          )).to.be.true;
+        });
+      });
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/log_metrics_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/log_metrics_test.js b/ambari-web/test/views/main/host/log_metrics_test.js
new file mode 100644
index 0000000..a0a3c6c
--- /dev/null
+++ b/ambari-web/test/views/main/host/log_metrics_test.js
@@ -0,0 +1,116 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var fileUtils = require('utils/file_utils');
+
+describe('App.MainHostLogMetrics', function() {
+  var view;
+
+  beforeEach(function() {
+    view = App.MainHostLogMetrics.create({
+      content: Em.Object.create()
+    });
+  });
+
+  describe("#logsData", function () {
+
+    it("should return logsData", function() {
+      view.set('content.hostComponents', [{service: 'S1'}]);
+      view.propertyDidChange('logsData');
+      expect(view.get('logsData').mapProperty('service')).to.be.eql(['S1']);
+      expect(view.get('logsData')[0].get('logs').mapProperty('level')).to.be.eql([
+        'fatal', 'critical', 'error', 'warning', 'info', 'debug'
+      ]);
+    });
+  });
+
+  describe("#chartView", function () {
+    var chartView;
+
+    beforeEach(function () {
+      chartView = view.get('chartView').create({
+        donut: Em.K
+      });
+    });
+
+    describe("#prepareChartData()", function () {
+
+      it("data should be set", function () {
+        chartView.prepareChartData(Em.Object.create({logs: [Em.Object.create()]}));
+        expect(chartView.get('data')).to.be.eql([Em.Object.create()]);
+      });
+    });
+
+    describe("#didInsertElement()", function () {
+
+      beforeEach(function() {
+        sinon.stub(chartView, 'prepareChartData');
+        sinon.stub(chartView, 'appendLabels');
+        sinon.stub(chartView, 'formatCenterText');
+        sinon.stub(chartView, 'attachArcEvents');
+        sinon.stub(chartView, 'colorizeArcs');
+        chartView.didInsertElement();
+      });
+
+      afterEach(function() {
+        chartView.prepareChartData.restore();
+        chartView.appendLabels.restore();
+        chartView.formatCenterText.restore();
+        chartView.attachArcEvents.restore();
+        chartView.colorizeArcs.restore();
+      });
+
+      it("prepareChartData should be called", function() {
+        expect(chartView.prepareChartData.calledOnce).to.be.true;
+      });
+
+      it("appendLabels should be called", function() {
+        expect(chartView.appendLabels.calledOnce).to.be.true;
+      });
+
+      it("formatCenterText should be called", function() {
+        expect(chartView.formatCenterText.calledOnce).to.be.true;
+      });
+
+      it("attachArcEvents should be called", function() {
+        expect(chartView.attachArcEvents.calledOnce).to.be.true;
+      });
+
+      it("colorizeArcs should be called", function() {
+        expect(chartView.colorizeArcs.calledOnce).to.be.true;
+      });
+    });
+  });
+
+  describe("#transitionByService()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'transitionTo');
+    });
+
+    afterEach(function() {
+      App.router.transitionTo.restore()
+    });
+
+    it("App.router.transitionTo should be called", function() {
+      view.transitionByService({context: Em.Object.create({service: {serviceName: 'S1'}})});
+      expect(App.router.transitionTo.calledWith('logs', {query: '?service_name=S1'})).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/logs_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/logs_view_test.js b/ambari-web/test/views/main/host/logs_view_test.js
new file mode 100644
index 0000000..38159df
--- /dev/null
+++ b/ambari-web/test/views/main/host/logs_view_test.js
@@ -0,0 +1,178 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var fileUtils = require('utils/file_utils');
+
+describe('App.MainHostLogsView', function() {
+  var view;
+
+  beforeEach(function() {
+    view = App.MainHostLogsView.create({
+      host: Em.Object.create()
+    });
+  });
+
+  describe("#hostLogs", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponentLog, 'find').returns([Em.Object.create({
+        hostName: 'host1',
+        logFileNames: []
+      })]);
+    });
+
+    afterEach(function() {
+      App.HostComponentLog.find.restore();
+    });
+
+    it("should return host logs", function() {
+      view.set('host.hostName', 'host1');
+      view.propertyDidChange('hostLogs');
+      expect(view.get('hostLogs')).to.be.eql([Em.Object.create({
+        hostName: 'host1',
+        logFileNames: []
+      })]);
+    });
+  });
+
+  describe("#content", function () {
+
+    beforeEach(function() {
+      sinon.stub(fileUtils, 'fileNameFromPath').returns('file1');
+    });
+
+    afterEach(function() {
+      fileUtils.fileNameFromPath.restore();
+    });
+
+    it("should return content", function() {
+      view.reopen({
+        host: Em.Object.create({hostName: 'host1'}),
+        hostLogs: [Em.Object.create({
+          hostComponent: {
+            service: {
+              displayName: 's1',
+              serviceName: 'S1'
+            },
+            componentName: 'C1',
+            displayName: 'c1'
+          },
+          hostName: 'host1',
+          name: 'l1',
+          logFileNames: ['f1', 'f2']
+        })]
+      });
+      view.propertyDidChange('content');
+      expect(view.get('content')).to.be.eql([Em.Object.create({
+        serviceName: 'S1',
+        serviceDisplayName: 's1',
+        componentName: 'C1',
+        componentDisplayName: 'c1',
+        hostName: 'host1',
+        logComponentName: 'l1',
+        fileNamesObject: [
+          {
+            fileName: 'file1',
+            filePath: 'f1',
+            linkTail: '?host_name=host1&file_name=f1&component_name=l1'
+          },
+          {
+            fileName: 'file1',
+            filePath: 'f2',
+            linkTail: '?host_name=host1&file_name=f2&component_name=l1'
+          }
+        ],
+        fileNamesFilterValue: 'f1,f2'
+      })]);
+    });
+  });
+
+  describe("#logFileRowView", function () {
+    var logFileRowView;
+
+    beforeEach(function() {
+      logFileRowView = view.get('logFileRowView').create()
+    });
+
+    describe("#didInsertElement()", function () {
+
+      beforeEach(function() {
+        sinon.stub(App, 'tooltip');
+      });
+
+      afterEach(function() {
+        App.tooltip.restore();
+      });
+
+      it("App.tooltip should be called", function() {
+        logFileRowView.didInsertElement();
+        expect(App.tooltip.calledOnce).to.be.true;
+      });
+    });
+
+    describe("#willDestroyElement()", function () {
+      var mock = {
+        tooltip: Em.K
+      };
+
+      beforeEach(function() {
+        sinon.stub(logFileRowView, '$').returns(mock);
+        sinon.spy(mock, 'tooltip');
+      });
+
+      afterEach(function() {
+        logFileRowView.$.restore();
+        mock.tooltip.restore();
+      });
+
+      it("tooltip should be destroyed", function() {
+        logFileRowView.willDestroyElement();
+        expect(mock.tooltip.calledWith('destroy')).to.be.true;
+      });
+    });
+  });
+
+  describe("#openLogFile()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App, 'showLogTailPopup');
+    });
+
+    afterEach(function() {
+      App.showLogTailPopup.restore();
+    });
+
+    it("contexts is empty", function() {
+      view.openLogFile({contexts: []});
+      expect(App.showLogTailPopup.called).to.be.false;
+    });
+
+    it("App.showLogTailPopup should be called", function() {
+      view.openLogFile({contexts: [Em.Object.create({
+        logComponentName: 'lc1',
+        hostName: 'host1'
+      }), 'f1']});
+      expect(App.showLogTailPopup.calledWith(Em.Object.create({
+        logComponentName: 'lc1',
+        hostName: 'host1',
+        filePath: 'f1'
+      }))).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/stack_versions_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/stack_versions_view_test.js b/ambari-web/test/views/main/host/stack_versions_view_test.js
index 0d1d6ff..c92c404 100644
--- a/ambari-web/test/views/main/host/stack_versions_view_test.js
+++ b/ambari-web/test/views/main/host/stack_versions_view_test.js
@@ -43,4 +43,70 @@ describe('App.MainHostStackVersionsView', function() {
       expect(view.get('filteredContentInfo')).to.eql(Em.I18n.t('hosts.host.stackVersions.table.filteredInfo').format(1, 2));
     });
   });
+
+  describe("#showInstallProgress()", function () {
+    var mock = {
+      showProgressPopup: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(mock);
+      sinon.stub(mock, 'showProgressPopup');
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+      mock.showProgressPopup.restore();
+    });
+
+    it("showProgressPopup should be called", function() {
+      view.showInstallProgress({context: {}});
+      expect(mock.showProgressPopup.calledWith({})).to.be.true;
+    });
+  });
+
+  describe("#outOfSyncInfo", function () {
+    var outOfSyncInfo;
+
+    beforeEach(function() {
+      outOfSyncInfo = view.get('outOfSyncInfo').create()
+    });
+
+    describe("#didInsertElement()", function () {
+
+      beforeEach(function() {
+        sinon.stub(App, 'tooltip');
+      });
+
+      afterEach(function() {
+        App.tooltip.restore();
+      });
+
+      it("App.tooltip should be called", function() {
+        outOfSyncInfo.didInsertElement();
+        expect(App.tooltip.calledOnce).to.be.true;
+      });
+    });
+
+    describe("#willDestroyElement()", function () {
+      var mock = {
+        tooltip: Em.K
+      };
+
+      beforeEach(function() {
+        sinon.stub(window, '$').returns(mock);
+        sinon.spy(mock, 'tooltip');
+      });
+
+      afterEach(function() {
+        window.$.restore();
+        mock.tooltip.restore();
+      });
+
+      it("tooltip should be destroyed", function() {
+        outOfSyncInfo.willDestroyElement();
+        expect(mock.tooltip.calledWith('destroy')).to.be.true;
+      });
+    });
+  });
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/2ad42074/ambari-web/test/views/main/host/summary_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/summary_test.js b/ambari-web/test/views/main/host/summary_test.js
index 31b6d94..f1c9d06 100644
--- a/ambari-web/test/views/main/host/summary_test.js
+++ b/ambari-web/test/views/main/host/summary_test.js
@@ -694,4 +694,95 @@ describe('App.MainHostSummaryView', function() {
       expect(mainHostSummaryView.get('controller.installClients').calledWith([1,2,3])).to.be.true;
     });
   });
+
+  describe("#timeSinceHeartBeat", function () {
+
+    beforeEach(function() {
+      sinon.stub($, 'timeago').returns('1');
+    });
+
+    afterEach(function() {
+      $.timeago.restore();
+    });
+
+    it("rawLastHeartBeatTime = null", function() {
+      mainHostSummaryView.set('content.rawLastHeartBeatTime', null);
+      mainHostSummaryView.propertyDidChange('timeSinceHeartBeat');
+      expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.empty;
+    });
+
+    it("rawLastHeartBeatTime = 1", function() {
+      mainHostSummaryView.set('content.rawLastHeartBeatTime', '1');
+      mainHostSummaryView.propertyDidChange('timeSinceHeartBeat');
+      expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.equal('1');
+    });
+  });
+
+  describe("#clientsWithCustomCommands", function () {
+
+    beforeEach(function() {
+      this.mockComponents = sinon.stub(App.StackServiceComponent, 'find');
+    });
+
+    afterEach(function() {
+      this.mockComponents.restore();
+    });
+
+    var testCases = [
+      {
+        component: Em.Object.create(),
+        clients: [],
+        expected: []
+      },
+      {
+        component: Em.Object.create(),
+        clients: [
+          Em.Object.create({componentName: 'KERBEROS_CLIENT'})
+        ],
+        expected: []
+      },
+      {
+        component: Em.Object.create({customCommands: []}),
+        clients: [
+          Em.Object.create({componentName: 'C1'})
+        ],
+        expected: []
+      },
+      {
+        component: Em.Object.create({customCommands: ['cmd1']}),
+        clients: [
+          Em.Object.create({
+            hostName: 'host1',
+            displayName: 'dn1',
+            componentName: 'C1',
+            service: Em.Object.create({serviceName: 'S1'})
+          })
+        ],
+        expected: [{
+          label: 'dn1',
+          commands: [
+            {
+              label: Em.I18n.t('services.service.actions.run.executeCustomCommand.menu').format('cmd1'),
+              service: "S1",
+              hosts: 'host1',
+              component: 'C1',
+              command: 'cmd1'
+            }
+          ]
+        }]
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("component = " + JSON.stringify(test.component) +
+         " clients = " + JSON.stringify(test.clients), function() {
+        this.mockComponents.returns(test.component);
+        mainHostSummaryView.reopen({
+          clients: test.clients
+        });
+        mainHostSummaryView.propertyDidChange('clientsWithCustomCommands');
+        expect(mainHostSummaryView.get('clientsWithCustomCommands')).to.be.eql(test.expected);
+      });
+    });
+  });
 });


Mime
View raw message