You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ao...@apache.org on 2017/04/01 07:07:52 UTC

[08/17] ambari git commit: AMBARI-20637 Cover service views with unit tests. (atkach)

AMBARI-20637 Cover service 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/89feb0a7
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/89feb0a7
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/89feb0a7

Branch: refs/heads/branch-3.0-perf
Commit: 89feb0a7f32c2373cac1058d2fa604522344990b
Parents: ebfc848
Author: Andrii Tkach <at...@apache.org>
Authored: Thu Mar 30 19:23:05 2017 +0300
Committer: Andrew Onishuk <ao...@hortonworks.com>
Committed: Sat Apr 1 10:07:36 2017 +0300

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   4 +
 .../main/service/manage_config_groups_view.js   |   2 +-
 ambari-web/app/views/main/service/menu.js       |  40 +--
 .../app/views/main/service/reconfigure.js       |   8 +-
 ambari-web/app/views/main/service/service.js    |  23 +-
 ambari-web/test/views/main/service/item_test.js | 163 +++++++++++-
 .../service/manage_config_groups_view_test.js   | 125 +++++++++
 ambari-web/test/views/main/service/menu_test.js | 210 ++++++++++++---
 .../views/main/service/reassign_view_test.js    | 128 +++++++++
 .../test/views/main/service/reconfigure_test.js | 126 +++++++++
 .../test/views/main/service/service_test.js     | 266 +++++++++++++++++++
 11 files changed, 1001 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/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 d404d1f..8859a29 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -332,6 +332,10 @@ var files = [
   'test/views/main/charts/heatmap_test',
   'test/views/main/charts/heatmap/heatmap_host_test',
   'test/views/main/service/item_test',
+  'test/views/main/service/manage_config_groups_view_test',
+  'test/views/main/service/reassign_view_test',
+  'test/views/main/service/reconfigure_test',
+  'test/views/main/service/service_test',
   'test/views/main/service/info/config_test',
   'test/views/main/service/info/summary_test',
   'test/views/main/service/info/menu_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/app/views/main/service/manage_config_groups_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/manage_config_groups_view.js b/ambari-web/app/views/main/service/manage_config_groups_view.js
index 7517b43..4973984 100644
--- a/ambari-web/app/views/main/service/manage_config_groups_view.js
+++ b/ambari-web/app/views/main/service/manage_config_groups_view.js
@@ -85,7 +85,7 @@ App.MainServiceManageConfigGroupView = Em.View.extend({
     if (!selectedConfigGroup.get('isDefault') && selectedConfigGroup.get('isAddHostsDisabled')) {
       return Em.I18n.t('services.service.config_groups_popup.addHostDisabled');
     } else {
-      return  Em.I18n.t('services.service.config_groups_popup.addHost');
+      return Em.I18n.t('services.service.config_groups_popup.addHost');
     }
   }.property('controller.selectedConfigGroup.isDefault', 'controller.selectedConfigGroup.isAddHostsDisabled'),
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/app/views/main/service/menu.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/menu.js b/ambari-web/app/views/main/service/menu.js
index c168e54..3695bfe 100644
--- a/ambari-web/app/views/main/service/menu.js
+++ b/ambari-web/app/views/main/service/menu.js
@@ -22,28 +22,28 @@ App.MainServiceMenuView = Em.CollectionView.extend({
   disabledServices: [],
 
   content: function () {
-    return App.router.get('mainServiceController.content').filter(function(item){
+    return App.router.get('mainServiceController.content').filter(function (item) {
       return !this.get('disabledServices').contains(item.get('id'));
     }, this);
   }.property('App.router.mainServiceController.content', 'App.router.mainServiceController.content.length'),
 
-  didInsertElement:function () {
+  didInsertElement: function () {
     App.router.location.addObserver('lastSetURL', this, 'renderOnRoute');
     this.renderOnRoute();
-    App.tooltip(this.$(".restart-required-service"), {html:true, placement:"right"});
+    App.tooltip(this.$(".restart-required-service"), {html: true, placement: "right"});
   },
 
-  willDestroyElement: function() {
+  willDestroyElement: function () {
     App.router.location.removeObserver('lastSetURL', this, 'renderOnRoute');
     this.$(".restart-required-service").tooltip('destroy');
   },
 
-  activeServiceId:null,
+  activeServiceId: null,
 
   /**
    *    Syncs navigation menu with requested URL
    */
-  renderOnRoute:function () {
+  renderOnRoute: function () {
     var last_url = App.router.location.lastSetURL || location.href.replace(/^[^#]*#/, '');
     if (last_url.substr(1, 4) !== 'main' || !this._childViews) {
       return;
@@ -54,32 +54,34 @@ App.MainServiceMenuView = Em.CollectionView.extend({
     this.set('activeServiceId', service_id);
   },
 
-  tagName:'ul',
-  classNames:[ "nav", "nav-list", "nav-services"],
+  tagName: 'ul',
+  classNames: ["nav", "nav-list", "nav-services"],
 
-  itemViewClass:Em.View.extend({
+  itemViewClass: Em.View.extend({
 
-    classNameBindings:["active", "clients"],
-    templateName:require('templates/main/service/menu_item'),
+    classNameBindings: ["active", "clients"],
+    templateName: require('templates/main/service/menu_item'),
     restartRequiredMessage: null,
 
     shouldBeRestarted: Em.computed.equal('content.isRestartRequired'),
 
-    active:function () {
-      return this.get('content.id') == this.get('parentView.activeServiceId') ? 'active' : '';
+    active: function () {
+      return this.get('content.id') === this.get('parentView.activeServiceId') ? 'active' : '';
     }.property('parentView.activeServiceId'),
 
     alertsCount: function () {
-      return this.get('content.alertsCount') > 99 ? "99+" : this.get('content.alertsCount') ;
+      return this.get('content.alertsCount') > 99 ? "99+" : this.get('content.alertsCount');
     }.property('content.alertsCount'),
 
     hasCriticalAlerts: Em.computed.alias('content.hasCriticalAlerts'),
 
     isConfigurable: Em.computed.notExistsInByKey('content.serviceName', 'App.services.noConfigTypes'),
 
-    link: function() {
-      var stateName = (['summary','configs'].contains(App.router.get('currentState.name')))
-        ? this.get('isConfigurable') && this.get('parentView.activeServiceId') != this.get('content.id') ?  App.router.get('currentState.name') : 'summary'
+    link: function () {
+      var currentState = App.router.get('currentState.name');
+      var routeToNewState = this.get('parentView.activeServiceId') !== this.get('content.id');
+      var stateName = (['summary', 'configs'].contains(currentState))
+        ? (this.get('isConfigurable') && routeToNewState) ? currentState : 'summary'
         : 'summary';
       return "#/main/services/" + this.get('content.id') + "/" + stateName;
     }.property('App.router.currentState.name', 'parentView.activeServiceId', 'isConfigurable'),
@@ -90,7 +92,7 @@ App.MainServiceMenuView = Em.CollectionView.extend({
       App.router.set('mainServiceItemController.routeToConfigs', false);
     },
 
-    refreshRestartRequiredMessage: function() {
+    refreshRestartRequiredMessage: function () {
       var restarted, componentsCount, hostsCount, message, tHosts, tComponents;
       restarted = this.get('content.restartRequiredHostsAndComponents');
       componentsCount = 0;
@@ -111,7 +113,7 @@ App.MainServiceMenuView = Em.CollectionView.extend({
         tComponents = Em.I18n.t('common.component');
       }
       message += componentsCount + ' ' + tComponents + ' ' + Em.I18n.t('on') + ' ' +
-        hostsCount + ' ' + tHosts + ' ' + Em.I18n.t('services.service.config.restartService.needToRestartEnd');
+      hostsCount + ' ' + tHosts + ' ' + Em.I18n.t('services.service.config.restartService.needToRestartEnd');
       this.set('restartRequiredMessage', message);
     }.observes('content.restartRequiredHostsAndComponents')
   })

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/app/views/main/service/reconfigure.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/reconfigure.js b/ambari-web/app/views/main/service/reconfigure.js
index 894b77f..cbefcfb 100644
--- a/ambari-web/app/views/main/service/reconfigure.js
+++ b/ambari-web/app/views/main/service/reconfigure.js
@@ -33,11 +33,7 @@ App.StageLabelView = Em.View.extend({
    this.onLink();
   },
   onLink: function() {
-   if (this.get('showLink') === true) {
-     this.set('removeLink',null);
-   } else {
-     this.set('removeLink','remove-link');
-   }
+    this.set('removeLink', this.get('showLink') ? null : 'remove-link');
   }.observes('showLink'),
   command: null,
   click: function () {
@@ -74,7 +70,7 @@ App.StageInProgressView = Em.View.extend({
   template: Ember.Handlebars.compile('<div class="progress-bar progress-bar-striped active" {{bindAttr style="command.barWidth"}}></div>'),
 
   isStageCompleted: function () {
-    return this.get('obj.progress') == 100 || this.get('controller.isStepCompleted');
+    return this.get('obj.progress') === 100 || this.get('controller.isStepCompleted');
   }.property('controller.isStepCompleted', 'obj.progress')
 
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/app/views/main/service/service.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/service.js b/ambari-web/app/views/main/service/service.js
index 74319f5..f2c8492 100644
--- a/ambari-web/app/views/main/service/service.js
+++ b/ambari-web/app/views/main/service/service.js
@@ -26,7 +26,7 @@ App.MainDashboardServiceHealthView = Em.View.extend({
   //template: Em.Handlebars.compile(""),
   blink: false,
   tagName: 'span',
-  attributeBindings:['rel', 'title','data-original-title'],
+  attributeBindings: ['rel', 'title', 'data-original-title'],
   rel: 'HealthTooltip',
   'data-original-title': '',
 
@@ -41,7 +41,7 @@ App.MainDashboardServiceHealthView = Em.View.extend({
   doBlink: function () {
     var self = this;
     if (this.get('blink') && this.get("state") === "inDOM") {
-      uiEffects.pulsate(self.$(), 1000, function(){
+      uiEffects.pulsate(self.$(), 1000, function () {
         self.doBlink();
       });
     }
@@ -83,7 +83,7 @@ App.MainDashboardServiceHealthView = Em.View.extend({
     }
 
     return 'health-status-' + status;
-  }.property('service.healthStatus','service.passiveState','service.serviceName'),
+  }.property('service.healthStatus', 'service.passiveState', 'service.serviceName'),
 
   healthStatusClass: function () {
     if (this.get('service.passiveState') !== 'OFF' || App.get('services.clientOnly').contains(this.get('service.serviceName'))) {
@@ -103,7 +103,7 @@ App.MainDashboardServiceHealthView = Em.View.extend({
       default:
         return '';
     }
-  }.property('service.healthStatus','service.passiveState','service.serviceName'),
+  }.property('service.healthStatus', 'service.passiveState', 'service.serviceName'),
 
   didInsertElement: function () {
     this.updateToolTip();
@@ -116,7 +116,7 @@ App.ComponentLiveTextView = Em.View.extend({
   classNameBindings: ['color:service-summary-component-red-dead:service-summary-component-green-live'],
   liveComponents: null,
   totalComponents: null,
-  color: function() {
+  color: function () {
     return this.get("liveComponents") === 0 && this.get('totalComponents') !== 0;
   }.property("liveComponents", 'totalComponents')
 });
@@ -142,18 +142,13 @@ App.MainDashboardServiceView = Em.View.extend(App.MainDashboardServiceViewWrappe
 
   isCollapsed: false,
 
-  toggleInfoView: function () {
-    this.$('.service-body').toggle('blind', 200);
-    this.set('isCollapsed', !this.isCollapsed);
-  },
-
   masters: Em.computed.filterBy('service.hostComponents', 'isMaster', true),
 
-  clients: function(){
+  clients: function () {
     var clients = this.get('service.hostComponents').filterProperty('isClient', true);
     var len = clients.length;
     var template = 'dashboard.services.{0}.client'.format(this.get('serviceName').toLowerCase());
-    if(len > 1){
+    if (len > 1) {
       template += 's';
     }
 
@@ -170,8 +165,8 @@ App.MainDashboardServiceView = Em.View.extend(App.MainDashboardServiceViewWrappe
    */
   isServiceComponentCreated: function (componentName) {
     return App.MasterComponent.find().mapProperty('componentName').concat(
-        App.ClientComponent.find().mapProperty('componentName'),
-        App.SlaveComponent.find().mapProperty('componentName')
+      App.ClientComponent.find().mapProperty('componentName'),
+      App.SlaveComponent.find().mapProperty('componentName')
     ).contains(componentName);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/item_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/item_test.js b/ambari-web/test/views/main/service/item_test.js
index 289f1f6..4b2e6f9 100644
--- a/ambari-web/test/views/main/service/item_test.js
+++ b/ambari-web/test/views/main/service/item_test.js
@@ -26,7 +26,9 @@ function getView() {
     controller: Em.Object.create({
       content: Em.Object.create({
         hostComponents: []
-      })
+      }),
+      setStartStopState: sinon.spy(),
+      loadConfigs: sinon.spy()
     })
   });
 }
@@ -37,21 +39,17 @@ describe('App.MainServiceItemView', function () {
 
   App.TestAliases.testAsComputedAlias(getView(), 'displayName', 'controller.content.displayName', 'string');
 
-  describe('#mastersExcludedCommands', function () {
-
-    view = App.MainServiceItemView.create({
-      controller: Em.Object.create({
-        content: Em.Object.create({
-          hostComponents: []
-        })
-      })
-    });
+  beforeEach(function() {
+    view = getView();
+  });
 
+  describe('#mastersExcludedCommands', function () {
+    view = getView();
     var nonCustomAction = ['RESTART_ALL', 'RUN_SMOKE_TEST', 'REFRESH_CONFIGS', 'ROLLING_RESTART', 'TOGGLE_PASSIVE', 'TOGGLE_NN_HA', 'TOGGLE_RM_HA', 'MOVE_COMPONENT', 'DOWNLOAD_CLIENT_CONFIGS', 'MASTER_CUSTOM_COMMAND'];
-    var keys = Object.keys(view.mastersExcludedCommands);
+    var keys = Object.keys(view.get('mastersExcludedCommands'));
     var mastersExcludedCommands = [];
     for (var i = 0; i < keys.length; i++) {
-      mastersExcludedCommands[i] = view.mastersExcludedCommands[keys[i]];
+      mastersExcludedCommands[i] = view.get('mastersExcludedCommands')[keys[i]];
     }
     var allMastersExcludedCommands = mastersExcludedCommands.reduce(function (previous, current) {
       return previous.concat(current);
@@ -437,8 +435,6 @@ describe('App.MainServiceItemView', function () {
 
     beforeEach(function () {
 
-      view = App.MainServiceItemView.create({});
-
       sinon.stub(App, 'get', function (k) {
         switch (k) {
           case 'isSingleNode':
@@ -592,5 +588,144 @@ describe('App.MainServiceItemView', function () {
     });
   });
 
+  describe('#isMaintenanceActive', function() {
+
+    it('isMaintenanceActive should be false when maintenance empty', function() {
+      view.set('state', 'inDOM');
+      view.set('maintenance', []);
+      expect(view.get('isMaintenanceActive')).to.be.false;
+    });
+
+    it('isMaintenanceActive should be true when maintenance not empty', function() {
+      view.set('state', 'inDOM');
+      view.set('maintenance', [{}]);
+      expect(view.get('isMaintenanceActive')).to.be.true;
+    });
+
+    it('isMaintenanceActive should be true when state not inDOM', function() {
+      view.set('state', '');
+      view.set('maintenance', [{}]);
+      expect(view.get('isMaintenanceActive')).to.be.true;
+    });
+  });
+
+  describe('#hasConfigTab', function() {
+    beforeEach(function() {
+      this.mockAuthorized = sinon.stub(App, 'isAuthorized');
+      this.mockGet = sinon.stub(App, 'get').returns(['S2']);
+    });
+    afterEach(function() {
+      this.mockAuthorized.restore();
+      this.mockGet.restore();
+    });
+
+    it('should return false when not authorized', function() {
+      this.mockAuthorized.returns(false);
+      view.set('controller.content.serviceName', 'S1');
+      expect(view.get('hasConfigTab')).to.be.false;
+    });
+
+    it('should return false when service does not have config types', function() {
+      this.mockAuthorized.returns(true);
+      view.set('controller.content.serviceName', 'S2');
+      expect(view.get('hasConfigTab')).to.be.false;
+    });
+
+    it('should return true when authorized', function() {
+      this.mockAuthorized.returns(true);
+      view.set('controller.content.serviceName', 'S1');
+      expect(view.get('hasConfigTab')).to.be.true;
+    });
+  });
+
+  describe('#hasHeatmapTab', function() {
+    beforeEach(function() {
+      sinon.stub(App, 'get').returns(['S1']);
+    });
+    afterEach(function() {
+      App.get.restore();
+    });
+
+    it('should return false when service does not have heatmaps', function() {
+      view.set('controller.content.serviceName', 'S2');
+      expect(view.get('hasHeatmapTab')).to.be.false;
+    });
+    it('should return true when service has heatmaps', function() {
+      view.set('controller.content.serviceName', 'S1');
+      expect(view.get('hasHeatmapTab')).to.be.true;
+    });
+  });
+
+  describe('#didInsertElement', function() {
+
+    it('setStartStopState should be called', function() {
+      view.didInsertElement();
+      expect(view.get('controller').setStartStopState.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#willInsertElement', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'addObserver');
+    });
+    afterEach(function() {
+      view.addObserver.restore();
+    });
+
+    it('loadConfigs should be called', function() {
+      view.willInsertElement();
+      expect(view.get('controller').loadConfigs.calledOnce).to.be.true;
+    });
+    it('addObserver should be called', function() {
+      view.set('maintenanceObsFields', ['foo']);
+      view.willInsertElement();
+      expect(view.addObserver.calledWith('controller.foo')).to.be.true;
+    });
+  });
+
+  describe('#willDestroyElement', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'removeObserver');
+    });
+    afterEach(function() {
+      view.removeObserver.restore();
+    });
+
+    it('addObserver should be called', function() {
+      view.set('maintenanceObsFields', ['foo']);
+      view.willDestroyElement();
+      expect(view.removeObserver.calledWith('controller.foo')).to.be.true;
+    });
+  });
+
+  describe('#service', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.HDFSService, 'find').returns(Em.A([{}]));
+      sinon.stub(App.YARNService, 'find').returns(Em.A([{}]));
+      sinon.stub(App.HBaseService, 'find').returns(Em.A([{}]));
+      sinon.stub(App.FlumeService, 'find').returns(Em.A([{}]));
+    });
+    afterEach(function() {
+      App.HDFSService.find.restore();
+      App.YARNService.find.restore();
+      App.HBaseService.find.restore();
+      App.FlumeService.find.restore();
+    });
+
+    ['HDFS', 'YARN', 'HBASE', 'FLUME'].forEach(function(service) {
+      it('should return object of ' + service, function() {
+        view.set('controller.content.serviceName', service);
+        expect(view.get('service')).to.be.an.object;
+      });
+    });
+
+    it('should return content', function() {
+      view.set('controller.content', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      expect(view.get('service')).to.be.an.object;
+    });
+  });
 });
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/manage_config_groups_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/manage_config_groups_view_test.js b/ambari-web/test/views/main/service/manage_config_groups_view_test.js
new file mode 100644
index 0000000..b78b2dd
--- /dev/null
+++ b/ambari-web/test/views/main/service/manage_config_groups_view_test.js
@@ -0,0 +1,125 @@
+/**
+ * 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');
+require('views/main/service/manage_config_groups_view');
+
+var view;
+
+describe('App.MainServiceManageConfigGroupView', function () {
+  beforeEach(function() {
+    view = App.MainServiceManageConfigGroupView.create({
+      controller: Em.Object.create({
+        loadHosts: sinon.spy()
+      })
+    });
+  });
+
+  describe('#addHostTooltip', function() {
+
+    it('should return addHost message when group is default', function() {
+      view.set('controller.selectedConfigGroup', Em.Object.create({
+        isDefault: true
+      }));
+      expect(view.get('addHostTooltip')).to.be.equal(Em.I18n.t('services.service.config_groups_popup.addHost'));
+    });
+    it('should return addHost message when add host is enabled', function() {
+      view.set('controller.selectedConfigGroup', Em.Object.create({
+        isDefault: false,
+        isAddHostsDisabled: false
+      }));
+      expect(view.get('addHostTooltip')).to.be.equal(Em.I18n.t('services.service.config_groups_popup.addHost'));
+    });
+    it('should return addHostDisabled message when add host is disabled', function() {
+      view.set('controller.selectedConfigGroup', Em.Object.create({
+        isDefault: false,
+        isAddHostsDisabled: true
+      }));
+      expect(view.get('addHostTooltip')).to.be.equal(Em.I18n.t('services.service.config_groups_popup.addHostDisabled'));
+    });
+  });
+
+  describe('#willInsertElement', function() {
+
+    it('loadHosts should be called', function() {
+      view.willInsertElement();
+      expect(view.get('controller').loadHosts.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#willDestroyElement', function() {
+
+    it('should clean configGroups and originalConfigGroups', function() {
+      view.set('controller.configGroups', [{}]);
+      view.set('controller.originalConfigGroups', [{}]);
+      view.willDestroyElement();
+      expect(view.get('controller.configGroups')).to.be.empty;
+      expect(view.get('controller.originalConfigGroups')).to.be.empty;
+    });
+  });
+
+  describe('#showTooltip', function() {
+    beforeEach(function() {
+      sinon.stub(Em.run, 'next', Em.clb);
+      sinon.stub(App, 'tooltip');
+      sinon.stub(view, 'selectDefaultGroup');
+    });
+    afterEach(function() {
+      Em.run.next.restore();
+      App.tooltip.restore();
+      view.selectDefaultGroup.restore();
+    });
+
+    it('Em.run.next should be called', function() {
+      view.set('controller.isLoaded', true);
+      expect(Em.run.next.calledOnce).to.be.true;
+    });
+
+    it('App.tooltip should be called', function() {
+      view.set('controller.isLoaded', true);
+      expect(App.tooltip.calledThrice).to.be.true;
+    });
+  });
+
+  describe('#onGroupSelect', function() {
+
+    it('should set selectedConfigGroup of controller', function() {
+      view.set('selectedConfigGroup', [Em.Object.create({id: 1})]);
+      expect(view.get('controller.selectedConfigGroup')).to.be.eql(Em.Object.create({id: 1}));
+      expect(view.get('controller.selectedHosts')).to.be.empty;
+    });
+    it('should set selectedConfigGroup of view', function() {
+      view.set('selectedConfigGroup', [Em.Object.create({id: 1}), Em.Object.create({id: 2})]);
+      expect(view.get('controller.selectedConfigGroup')).to.be.eql(Em.Object.create({id: 2}));
+      expect(view.get('selectedConfigGroup')).to.be.eql(Em.Object.create({id: 2}));
+      expect(view.get('controller.selectedHosts')).to.be.empty;
+    });
+  });
+
+  describe('#selectDefaultGroup', function() {
+
+    it('should set selectedConfigGroup', function() {
+      view.set('controller.configGroups', [
+        Em.Object.create({isDefault: true})
+      ]);
+      view.set('controller.isLoaded', true);
+      view.selectDefaultGroup();
+      expect(view.get('selectedConfigGroup')).to.be.eql([Em.Object.create({isDefault: true})]);
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/menu_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/menu_test.js b/ambari-web/test/views/main/service/menu_test.js
index 275006a..f925f2c 100644
--- a/ambari-web/test/views/main/service/menu_test.js
+++ b/ambari-web/test/views/main/service/menu_test.js
@@ -23,60 +23,190 @@ function getView() {
   return App.MainServiceMenuView.create();
 }
 
+function getItemViewClass() {
+  return App.MainServiceMenuView.create().get('itemViewClass').create({
+    content: Em.Object.create(),
+    parentView: Em.Object.create()
+  });
+}
+
 describe('App.MainServiceMenuView', function () {
 
-  var mainServiceMenuView;
+  var view;
 
-  beforeEach(function(){
-    mainServiceMenuView = App.MainServiceMenuView.create();
-    var view = mainServiceMenuView.get('itemViewClass').create({
-      content:{
-        alertsCount: 2
-      }
+  beforeEach(function () {
+    view = App.MainServiceMenuView.create({
+      $: sinon.stub().returns({tooltip: Em.K})
     });
-    mainServiceMenuView.set('itemViewClass',view);
   });
 
-  var cases = [
-    {
-      title:'alertsCount=0 test case:',
-      alertsCount:0,
-      result:0
-    },
-    {
-      title:'alertsCount=5 test case:',
-      alertsCount:5,
-      result:5
-    },
-    {
-      title:'alertsCount=200 test case:',
-      alertsCount:200,
-      result:"99+"
-    },
-    {
-      title:'alertsCount=99 test case:',
-      alertsCount:99,
-      result:99
-    }
-  ];
-
-
-  cases.forEach(function(item){
-    it(item.title,function(){
-      mainServiceMenuView.get('itemViewClass').set('content.alertsCount',item.alertsCount);
-      expect(mainServiceMenuView.get('itemViewClass.alertsCount')).to.not.be.undefined;
-      expect(mainServiceMenuView.get('itemViewClass.alertsCount')).to.equal(item.result);
+  describe('#content', function() {
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns([
+        Em.Object.create({id: 'S1'}),
+        Em.Object.create({id: 'S2'})
+      ]);
+    });
+    afterEach(function() {
+      App.router.get.restore();
+    });
+
+    it('should return content', function() {
+      view.set('disabledServices', ['S2']);
+      view.propertyDidChange('content');
+      expect(view.get('content').mapProperty('id')).to.be.eql(['S1']);
+    });
+  });
+
+  describe('#didInsertElement', function() {
+    beforeEach(function() {
+      sinon.stub(App.router.location, 'addObserver');
+      sinon.stub(view, 'renderOnRoute');
+      sinon.stub(App, 'tooltip');
+      view.didInsertElement();
+    });
+    afterEach(function() {
+      App.router.location.addObserver.restore();
+      view.renderOnRoute.restore();
+      App.tooltip.restore();
+    });
+
+    it('addObserver should be called', function() {
+      expect(App.router.location.addObserver.calledOnce).to.be.true;
+    });
+    it('renderOnRoute should be called', function() {
+      expect(view.renderOnRoute.calledOnce).to.be.true;
+    });
+    it('App.tooltip should be called', function() {
+      expect(App.tooltip.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#willDestroyElement', function() {
+    beforeEach(function() {
+      sinon.stub(App.router.location, 'removeObserver');
+    });
+    afterEach(function() {
+      App.router.location.removeObserver.restore();
+    });
+
+    it('removeObserver should be called', function() {
+      view.willDestroyElement();
+      expect(App.router.location.removeObserver.calledOnce).to.be.true;
     });
   });
 
   describe('#itemViewClass', function () {
+    var itemView;
 
-    function getItemViewClass() {
-      return getView().get('itemViewClass').create();
-    }
+    beforeEach(function () {
+      itemView = getItemViewClass();
+    });
 
     App.TestAliases.testAsComputedNotExistsInByKey(getItemViewClass(), 'isConfigurable', 'content.serviceName', 'App.services.noConfigTypes', ['HDFS', 'ZOOKEEPER']);
 
+    describe('#active', function() {
+
+      it('should return "active" ', function() {
+        itemView.set('content.id', 'S1');
+        itemView.set('parentView.activeServiceId', 'S1');
+        expect(itemView.get('active')).to.be.equal('active');
+      });
+      it('should return "active" ', function() {
+        itemView.set('content.id', 'S1');
+        itemView.set('parentView.activeServiceId', 'S2');
+        expect(itemView.get('active')).to.be.empty;
+      });
+    });
+
+    describe('#alertsCount', function() {
+
+      it('should return 99 ', function() {
+        itemView.set('content.alertsCount', 99);
+        expect(itemView.get('alertsCount')).to.be.equal(99);
+      });
+      it('should return 99+ when more than 99', function() {
+        itemView.set('content.alertsCount', 100);
+        expect(itemView.get('alertsCount')).to.be.equal('99+');
+      });
+    });
+
+    describe('#link', function() {
+      beforeEach(function() {
+        this.mock = sinon.stub(App.router, 'get');
+        itemView.set('content.id', 'S1');
+        itemView.set('isConfigurable', true);
+      });
+      afterEach(function() {
+        this.mock.restore();
+      });
+
+      it('should link to summary when current state is unknown', function() {
+        this.mock.returns('');
+        expect(itemView.get('link')).to.be.equal('#/main/services/S1/summary');
+      });
+      it('should link to summary when current state is configs', function() {
+        this.mock.returns('configs');
+        itemView.set('parentView.activeServiceId', 'S2');
+        expect(itemView.get('link')).to.be.equal('#/main/services/S1/configs');
+      });
+      it('should link to summary when current state is summary', function() {
+        this.mock.returns('configs');
+        itemView.set('parentView.activeServiceId', 'S1');
+        expect(itemView.get('link')).to.be.equal('#/main/services/S1/summary');
+      });
+    });
+
+    describe('#goToConfigs', function() {
+      beforeEach(function() {
+        sinon.stub(App.router, 'set');
+        sinon.stub(App.router, 'transitionTo');
+        itemView.goToConfigs();
+      });
+      afterEach(function() {
+        App.router.set.restore();
+        App.router.transitionTo.restore();
+      });
+
+      it('App.router.set should be called', function() {
+        expect(App.router.set.calledWith('mainServiceItemController.routeToConfigs', true)).to.be.true;
+      });
+      it('App.router.transitionTo should be called', function() {
+        expect(App.router.transitionTo.calledWith('services.service.configs', Em.Object.create())).to.be.true;
+      });
+      it('App.router.set should be called', function() {
+        expect(App.router.set.calledWith('mainServiceItemController.routeToConfigs', false)).to.be.true;
+      });
+    });
+
+    describe('#refreshRestartRequiredMessage', function() {
+
+      it('no component require restart', function() {
+        var expected = 0 + ' ' + Em.I18n.t('common.component') + ' ' + Em.I18n.t('on') + ' ' +
+          0 + ' ' + Em.I18n.t('common.host') + ' ' + Em.I18n.t('services.service.config.restartService.needToRestartEnd');
+        itemView.set('content.restartRequiredHostsAndComponents', {});
+        expect(itemView.get('restartRequiredMessage')).to.be.equal(expected);
+      });
+
+      it('one component on one host require restart', function() {
+        var expected = 1 + ' ' + Em.I18n.t('common.component') + ' ' + Em.I18n.t('on') + ' ' +
+          1 + ' ' + Em.I18n.t('common.host') + ' ' + Em.I18n.t('services.service.config.restartService.needToRestartEnd');
+        itemView.set('content.restartRequiredHostsAndComponents', {
+          'host1': [{}]
+        });
+        expect(itemView.get('restartRequiredMessage')).to.be.equal(expected);
+      });
+
+      it('3 components on two hosts require restart', function() {
+        var expected = 3 + ' ' + Em.I18n.t('common.components') + ' ' + Em.I18n.t('on') + ' ' +
+          2 + ' ' + Em.I18n.t('common.hosts') + ' ' + Em.I18n.t('services.service.config.restartService.needToRestartEnd');
+        itemView.set('content.restartRequiredHostsAndComponents', {
+          'host1': [{}],
+          'host2': [{}, {}]
+        });
+        expect(itemView.get('restartRequiredMessage')).to.be.equal(expected);
+      });
+    });
   });
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/reassign_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/reassign_view_test.js b/ambari-web/test/views/main/service/reassign_view_test.js
new file mode 100644
index 0000000..f6ae81e
--- /dev/null
+++ b/ambari-web/test/views/main/service/reassign_view_test.js
@@ -0,0 +1,128 @@
+/**
+ * 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');
+require('views/main/service/reassign_view');
+var testHelpers = require('test/helpers');
+
+var view;
+
+describe('App.ReassignMasterView', function () {
+  beforeEach(function() {
+    view = App.ReassignMasterView.create({
+      controller: Em.Object.create({
+        content: Em.Object.create()
+      })
+    });
+  });
+  describe('#willInsertElement', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'loadHosts');
+      view.willInsertElement();
+    });
+    afterEach(function() {
+      view.loadHosts.restore();
+    });
+
+    it('loadHosts should be called', function() {
+      expect(view.loadHosts.calledOnce).to.be.true;
+    });
+    it('isLoaded should be false', function() {
+      expect(view.get('isLoaded')).to.be.false;
+    });
+  });
+
+
+  describe('#loadHosts', function() {
+
+    it('App.ajax.send should be called', function() {
+      view.loadHosts();
+      var args = testHelpers.findAjaxRequest('name', 'hosts.high_availability.wizard');
+      expect(args[0]).to.be.eql({
+        name: 'hosts.high_availability.wizard',
+        data: {},
+        sender: view,
+        success: 'loadHostsSuccessCallback',
+        error: 'loadHostsErrorCallback'
+      });
+    });
+  });
+
+  describe('#loadHostsSuccessCallback', function() {
+    var data = {
+      items: [
+        {
+          Hosts: {
+            host_name: 'host1',
+            cpu_count: 1,
+            total_mem: 1024,
+            disk_info: {},
+            maintenance_state: 'ON'
+          }
+        }
+      ]
+    };
+    beforeEach(function() {
+      sinon.stub(App.db, 'setHosts');
+      view.loadHostsSuccessCallback(data);
+    });
+    afterEach(function() {
+      App.db.setHosts.restore();
+    });
+
+    it('setHosts should be called', function() {
+      expect(App.db.setHosts.calledWith(
+        {
+          "host1": {
+            "bootStatus": "REGISTERED",
+            "cpu": 1,
+            "disk_info": {},
+            "isInstalled": true,
+            "maintenance_state": "ON",
+            "memory": 1024,
+            "name": "host1"
+          }
+        }
+      )).to.be.true;
+    });
+    it('should set hosts to content', function() {
+      expect(view.get('controller.content.hosts')).to.be.eql({
+        "host1": {
+          "bootStatus": "REGISTERED",
+          "cpu": 1,
+          "disk_info": {},
+          "isInstalled": true,
+          "maintenance_state": "ON",
+          "memory": 1024,
+          "name": "host1"
+        }
+      });
+    });
+    it('isLoaded should be true', function() {
+      expect(view.get('isLoaded')).to.be.true;
+    });
+  });
+
+  describe('#loadHostsErrorCallback', function() {
+
+    it('isLoaded should be true', function() {
+      view.loadHostsErrorCallback();
+      expect(view.get('isLoaded')).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/reconfigure_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/reconfigure_test.js b/ambari-web/test/views/main/service/reconfigure_test.js
new file mode 100644
index 0000000..8822401
--- /dev/null
+++ b/ambari-web/test/views/main/service/reconfigure_test.js
@@ -0,0 +1,126 @@
+/**
+ * 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');
+require('views/main/service/reconfigure');
+
+describe('App.StageLabelView', function () {
+  var view;
+
+  beforeEach(function() {
+    view = App.StageLabelView.create({
+      controller: Em.Object.create()
+    });
+  });
+
+  describe('#didInsertElement', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'onLink');
+    });
+    afterEach(function() {
+      view.onLink.restore();
+    });
+
+    it('onLink should be called', function() {
+      view.didInsertElement();
+      expect(view.onLink.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#onLink', function() {
+    it('removeLink should be null', function() {
+      view.set('command', Em.Object.create({showLink: true}));
+      view.onLink();
+      expect(view.get('removeLink')).to.be.null;
+    });
+    it('removeLink should be "remove-link"', function() {
+      view.set('command', Em.Object.create({showLink: false}));
+      view.onLink();
+      expect(view.get('removeLink')).to.be.equal('remove-link');
+    });
+  });
+
+  describe('#click', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'showHostPopup');
+    });
+    afterEach(function() {
+      view.showHostPopup.restore();
+    });
+
+    it('showHostsPopup should be called', function() {
+      view.set('command', Em.Object.create({showLink: true}));
+      view.click();
+      expect(view.showHostPopup.calledWith(Em.Object.create({showLink: true}))).to.be.true;
+    });
+  });
+
+  describe('#showHostPopup', function() {
+    var mock = Em.Object.create();
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns({
+        dataLoading: function() {
+          return {
+            done: Em.clb
+          };
+        }
+      });
+      sinon.stub(App.HostPopup, 'initPopup').returns(mock);
+    });
+    afterEach(function() {
+      App.router.get.restore();
+      App.HostPopup.initPopup.restore();
+    });
+
+    it('App.HostPopup.initPopup should be called', function() {
+      view.showHostPopup(Em.Object.create({label: 'foo', requestId: 1}));
+      expect(App.HostPopup.initPopup.calledWith('foo', view.get('controller'), false, 1)).to.be.true;
+      expect(mock.get('isNotShowBgChecked')).to.be.true;
+    });
+  });
+});
+
+describe('App.StageInProgressView', function () {
+  var view;
+
+  beforeEach(function() {
+    view = App.StageInProgressView.create({
+      controller: Em.Object.create(),
+      obj: Em.Object.create()
+    });
+  });
+
+  describe('#isStageCompleted', function() {
+
+    it('should return false when progress is 0 and step not completed', function() {
+      view.set('obj.progress', 0);
+      view.set('controller.isStepCompleted', false);
+      expect(view.get('isStageCompleted')).to.be.false;
+    });
+    it('should return true when progress is 100 and step not completed', function() {
+      view.set('obj.progress', 100);
+      view.set('controller.isStepCompleted', false);
+      expect(view.get('isStageCompleted')).to.be.true;
+    });
+    it('should return true when progress is 0 and step completed', function() {
+      view.set('obj.progress', 0);
+      view.set('controller.isStepCompleted', true);
+      expect(view.get('isStageCompleted')).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/89feb0a7/ambari-web/test/views/main/service/service_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/service/service_test.js b/ambari-web/test/views/main/service/service_test.js
new file mode 100644
index 0000000..72c2f3e
--- /dev/null
+++ b/ambari-web/test/views/main/service/service_test.js
@@ -0,0 +1,266 @@
+/**
+ * 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');
+require('views/main/service/service');
+var testHelpers = require('test/helpers');
+
+
+describe('App.MainDashboardServiceHealthView', function () {
+  var view;
+
+  beforeEach(function() {
+    view = App.MainDashboardServiceHealthView.create({
+      controller: Em.Object.create({
+        content: Em.Object.create()
+      }),
+      service: Em.Object.create()
+    });
+  });
+
+  describe('#updateToolTip', function() {
+
+    it('should set data-original-title', function() {
+      view.set('service.toolTipContent', 'foo');
+      view.updateToolTip();
+      expect(view.get('data-original-title')).to.be.equal('foo');
+    });
+  });
+
+  describe('#startBlink', function() {
+
+    it('blink should be true', function() {
+      view.startBlink();
+      expect(view.get('blink')).to.be.true;
+    });
+  });
+
+  describe('#stopBlink', function() {
+
+    it('blink should be false', function() {
+      view.stopBlink();
+      expect(view.get('blink')).to.be.false;
+    });
+  });
+
+  describe('#healthStatus', function() {
+    beforeEach(function() {
+      this.mock = sinon.stub(App, 'get').returns([]);
+      sinon.stub(view, 'stopBlink');
+      sinon.stub(view, 'startBlink');
+      view.set('service.serviceName', 'S1');
+      view.set('service.passiveState', 'OFF');
+    });
+    afterEach(function() {
+      view.stopBlink.restore();
+      view.startBlink.restore();
+      App.get.restore();
+    });
+
+    it('client only service', function() {
+      this.mock.returns(['S1']);
+      expect(view.get('healthStatus')).to.be.equal('glyphicon glyphicon-blackboard');
+    });
+    it('service in passive state', function() {
+      view.set('service.passiveState', 'ON');
+      expect(view.get('healthStatus')).to.be.equal('icon-medkit');
+    });
+    it('stopBlink should be called when service has green status', function() {
+      view.set('service.healthStatus', 'green');
+      expect(view.get('healthStatus')).to.be.equal('health-status-' + App.Service.Health.live);
+      expect(view.stopBlink.calledOnce).to.be.true;
+    });
+    it('startBlink should be called when service has green-blinking status', function() {
+      view.set('service.healthStatus', 'green-blinking');
+      expect(view.get('healthStatus')).to.be.equal('health-status-' + App.Service.Health.live);
+      expect(view.startBlink.calledOnce).to.be.true;
+    });
+    it('startBlink should be called when service has red-blinking status', function() {
+      view.set('service.healthStatus', 'red-blinking');
+      expect(view.get('healthStatus')).to.be.equal('health-status-' + App.Service.Health.dead);
+      expect(view.startBlink.calledOnce).to.be.true;
+    });
+    it('service has yellow status', function() {
+      view.set('service.healthStatus', 'yellow');
+      expect(view.get('healthStatus')).to.be.equal('health-status-' + App.Service.Health.unknown);
+    });
+    it('stopBlink should be called when service has empty status', function() {
+      view.set('service.healthStatus', '');
+      expect(view.get('healthStatus')).to.be.equal('health-status-' + App.Service.Health.dead);
+      expect(view.stopBlink.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#healthStatusClass', function() {
+    beforeEach(function() {
+      this.mock = sinon.stub(App, 'get').returns([]);
+      view.set('service.serviceName', 'S1');
+      view.set('service.passiveState', 'OFF');
+    });
+    afterEach(function() {
+      App.get.restore();
+    });
+
+    it('should be empty when client only service', function() {
+      this.mock.returns(['S1']);
+      expect(view.get('healthStatusClass')).to.be.empty;
+    });
+    it('should be empty when service in passive state', function() {
+      view.set('service.passiveState', 'ON');
+      expect(view.get('healthStatusClass')).to.be.empty;
+    });
+    it('should be empty when healthStatus is empty', function() {
+      view.set('service.healthStatus', '');
+      expect(view.get('healthStatusClass')).to.be.empty;
+    });
+    it('service has green status', function() {
+      view.set('service.healthStatus', 'green');
+      expect(view.get('healthStatusClass')).to.be.equal(App.healthIconClassGreen);
+    });
+    it('service has red status', function() {
+      view.set('service.healthStatus', 'red');
+      expect(view.get('healthStatusClass')).to.be.equal(App.healthIconClassRed);
+    });
+    it('service has yellow status', function() {
+      view.set('service.healthStatus', 'yellow');
+      expect(view.get('healthStatusClass')).to.be.equal(App.healthIconClassYellow);
+    });
+  });
+
+  describe('#didInsertElement', function() {
+    beforeEach(function() {
+      sinon.stub(view, 'updateToolTip');
+      sinon.stub(App, 'tooltip');
+      sinon.stub(view, 'doBlink');
+      view.didInsertElement();
+    });
+    afterEach(function() {
+      view.updateToolTip.restore();
+      App.tooltip.restore();
+      view.doBlink.restore();
+    });
+
+    it('updateToolTip should be called', function() {
+      expect(view.updateToolTip.calledOnce).to.be.true;
+    });
+    it('App.tooltip should be called', function() {
+      expect(App.tooltip.calledOnce).to.be.true;
+    });
+    it('doBlink should be called', function() {
+      expect(view.doBlink.calledOnce).to.be.true;
+    });
+  });
+});
+
+describe('App.ComponentLiveTextView', function () {
+  var view;
+
+  beforeEach(function() {
+    view = App.ComponentLiveTextView.create();
+  });
+
+  describe('#color', function() {
+    it('color should be true when liveComponents equal to 0 and totalComponents not equal to 0', function() {
+      view.setProperties({
+        liveComponents: 0,
+        totalComponents: 1
+      });
+      expect(view.get('color')).to.be.true;
+    });
+    it('color should be false when liveComponents not equal to 0 and totalComponents not equal to 0', function() {
+      view.setProperties({
+        liveComponents: 1,
+        totalComponents: 1
+      });
+      expect(view.get('color')).to.be.false;
+    });
+    it('color should be false when liveComponents equal 0 and totalComponents equal to 0', function() {
+      view.setProperties({
+        liveComponents: 0,
+        totalComponents: 0
+      });
+      expect(view.get('color')).to.be.false;
+    });
+  });
+});
+
+describe('App.MainDashboardServiceView', function () {
+  var view;
+
+  beforeEach(function() {
+    view = App.MainDashboardServiceView.create({
+      controller: Em.Object.create(),
+      service: Em.Object.create()
+    });
+  });
+
+  describe('#data', function() {
+
+    it('should return data', function() {
+      view.set('serviceName', 'S1');
+      view.set('controller.data', {
+        S1: {id: 'S1'}
+      });
+      expect(view.get('data')).to.be.eql({id: 'S1'});
+    });
+  });
+
+  describe('#clients', function() {
+
+    it('should return clients', function() {
+      view.set('serviceName', 'HDFS');
+      view.set('service.hostComponents', [
+        Em.Object.create({
+          isClient: true
+        }),
+        Em.Object.create({
+          isClient: true
+        })
+      ]);
+      expect(view.get('clients')).to.be.eql({
+        title: view.t('dashboard.services.hdfs.clients').format(2),
+        component: Em.Object.create({
+          isClient: true
+        })
+      });
+    });
+  });
+
+  describe('#isServiceComponentCreated', function() {
+    beforeEach(function() {
+      sinon.stub(App.MasterComponent, 'find').returns([{componentName: 'M1'}]);
+      sinon.stub(App.ClientComponent, 'find').returns([{componentName: 'C1'}]);
+      sinon.stub(App.SlaveComponent, 'find').returns([{componentName: 'S1'}]);
+    });
+    afterEach(function() {
+      App.MasterComponent.find.restore();
+      App.ClientComponent.find.restore();
+      App.SlaveComponent.find.restore();
+    });
+
+    it('client should be created', function() {
+      expect(view.isServiceComponentCreated('C1')).to.be.true;
+    });
+    it('master should be created', function() {
+      expect(view.isServiceComponentCreated('M1')).to.be.true;
+    });
+    it('slave should be created', function() {
+      expect(view.isServiceComponentCreated('S1')).to.be.true;
+    });
+  });
+});