Prechádzať zdrojové kódy

AMBARI-14629 Cover Alerts views with unit tests. (atkach)

Andrii Tkach 9 rokov pred
rodič
commit
fee1c3aa1f

+ 2 - 0
ambari-web/app/assets/test/tests.js

@@ -150,6 +150,7 @@ var files = [
   'test/mixins/common/table_server_view_mixin_test',
   'test/mixins/common/widget_mixin_test',
   'test/mixins/main/host/details/host_components/decommissionable_test',
+  'test/mixins/main/host/details/host_components/install_component_test',
   'test/mixins/main/service/configs/widget_popover_support_test',
   'test/mixins/main/service/configs/config_overridable_test',
   'test/mixins/routers/redirections_test',
@@ -216,6 +217,7 @@ var files = [
   'test/views/main/alerts/manage_alert_groups_view_test',
   'test/views/main/alerts/manage_alert_notifications_view_test',
   'test/views/main/alerts/definition_details_view_test',
+  'test/views/main/alerts/definition_configs_view_test',
   'test/views/main/alerts/add_alert_definition/step1_view_test',
   'test/views/main/alerts/add_alert_definition/step3_view_test',
   'test/views/main/alerts/manage_alert_groups/select_definitions_popup_body_view_test',

+ 1 - 0
ambari-web/app/messages.js

@@ -300,6 +300,7 @@ Em.I18n.translations = {
   'common.timeout.warning.popup.secondary': 'Log Out Now',
   'common.openNewWindow': 'Open in New Window',
   'common.fullLogPopup.clickToCopy': 'Click to Copy',
+  'common.nothingToDelete': 'Nothing to delete',
 
   'models.alert_instance.tiggered.verbose': "Occurred on {0} <br> Checked on {1}",
   'models.alert_definition.triggered.verbose': "Occurred on {0}",

+ 27 - 16
ambari-web/app/mixins/main/host/details/host_components/decommissionable.js

@@ -34,21 +34,21 @@ App.Decommissionable = Em.Mixin.create({
 
   /**
    * Is component in decommission process right know
-   * @type {bool}
+   * @type {boolean}
    */
-  isComponentDecommissioning: null,
+  isComponentDecommissioning: false,
 
   /**
    * May component be decommissioned
-   * @type {bool}
+   * @type {boolean}
    */
-  isComponentDecommissionAvailable: null,
+  isComponentDecommissionAvailable: false,
 
   /**
    * May component be recommissioned
-   * @type {bool}
+   * @type {boolean}
    */
-  isComponentRecommissionAvailable: null,
+  isComponentRecommissionAvailable: false,
 
   /**
    * Component with stopped masters can't be docommissioned
@@ -70,12 +70,14 @@ App.Decommissionable = Em.Mixin.create({
   /**
    * Tooltip message shows if decommission/recommission is disabled
    * when masters for current component is down
+   * @type {string}
    */
   decommissionTooltipMessage: function () {
     if (this.get('isComponentDecommissionDisable') && (this.get('isComponentRecommissionAvailable') || this.get('isComponentDecommissionAvailable'))) {
       var decom = this.get('isComponentRecommissionAvailable') ? Em.I18n.t('common.recommission') : Em.I18n.t('common.decommission');
       return Em.I18n.t('hosts.decommission.tooltip.warning').format(decom, App.format.role(this.get('componentForCheckDecommission')));
     }
+    return '';
   }.property('isComponentDecommissionDisable', 'isComponentRecommissionAvailable', 'isComponentDecommissionAvailable', 'componentForCheckDecommission'),
   /**
    * Recalculated component status based on decommission
@@ -163,7 +165,7 @@ App.Decommissionable = Em.Mixin.create({
    */
   getDesiredAdminStateSuccessCallback: function (response) {
     var status = response.HostRoles.desired_admin_state;
-    if (status != null) {
+    if (!Em.isNone(status)) {
       this.setDesiredAdminState(status);
       return status;
     }
@@ -206,7 +208,7 @@ App.Decommissionable = Em.Mixin.create({
    */
   getDecommissionStatusSuccessCallback: function (response) {
     var statusObject = response.ServiceComponentInfo;
-    if (statusObject != null) {
+    if (!Em.isNone(statusObject)) {
       statusObject.component_state = response.host_components[0].HostRoles.state;
       this.setDecommissionStatus(statusObject);
       return statusObject;
@@ -260,23 +262,32 @@ App.Decommissionable = Em.Mixin.create({
    */
   doBlinking: function () {
     var workStatus = this.get('workStatus');
-    var self = this;
     var pulsate = [App.HostComponentStatus.starting, App.HostComponentStatus.stopping, App.HostComponentStatus.installing].contains(workStatus);
+
     if (!pulsate) {
       var component = this.get('content');
-      if (component && workStatus != "INSTALLED") {
+      if (component && workStatus !== "INSTALLED") {
         pulsate = this.get('isDecommissioning');
       }
     }
-    if (pulsate && !self.get('isBlinking')) {
-      self.set('isBlinking', true);
-      uiEffects.pulsate(self.$('.components-health'), 1000, function () {
-        self.set('isBlinking', false);
-        self.doBlinking();
-      });
+    if (pulsate && !this.get('isBlinking')) {
+      this.set('isBlinking', true);
+      this.pulsate();
     }
   },
 
+  /**
+   * perform the pulsation
+   */
+  pulsate: function() {
+    var self = this;
+
+    uiEffects.pulsate(self.$('.components-health'), 1000, function () {
+      self.set('isBlinking', false);
+      self.doBlinking();
+    });
+  },
+
   /**
    * Start blinking when host component is starting/stopping/decommissioning
    */

+ 5 - 4
ambari-web/app/mixins/main/host/details/host_components/install_component.js

@@ -101,7 +101,7 @@ App.InstallComponent = Em.Mixin.create({
   updateAndCreateServiceComponent: function (componentName) {
     var self = this;
     var dfd = $.Deferred();
-    var updater =  App.router.get('updateController');
+    var updater = App.router.get('updateController');
     updater.updateComponentsState(function () {
       updater.updateServiceMetric(function () {
         self.createServiceComponent(componentName, dfd);
@@ -112,9 +112,9 @@ App.InstallComponent = Em.Mixin.create({
 
   /**
    *
-   * @param componentName
-   * @param dfd
-   * @returns {*}
+   * @param {string} componentName
+   * @param {$.Deferred} dfd
+   * @returns {$.ajax|null}
    */
   createServiceComponent: function (componentName, dfd) {
     var allServiceComponents = [];
@@ -125,6 +125,7 @@ App.InstallComponent = Em.Mixin.create({
     }, this);
     if (allServiceComponents.contains(componentName)) {
       dfd.resolve();
+      return null;
     } else {
       return App.ajax.send({
         name: 'common.create_component',

+ 5 - 0
ambari-web/app/views/main/alerts/definition_configs_view.js

@@ -31,6 +31,11 @@ App.AlertDefinitionConfigsView = Em.View.extend({
    */
   canEdit: true,
 
+  /**
+   * @type {string}
+   */
+  alertDefinitionType: '',
+
   /**
    * List of classes applied to all inputs
    * @type {String}

+ 21 - 16
ambari-web/app/views/main/alerts/definition_details_view.js

@@ -45,25 +45,30 @@ App.MainAlertDefinitionDetailsView = App.TableView.extend({
   willInsertElement: function () {
     this._super();
     this.get('controller').clearStep();
-    var self = this,
-      updater = App.router.get('updateController');
-    if (self.get('controller.content.isLoaded')) {
-      self.set('isLoaded', true);
-      self.get('controller').loadAlertInstances();
+
+    if (this.get('controller.content.isLoaded')) {
+      this.set('isLoaded', true);
+      this.get('controller').loadAlertInstances();
+    } else {
+      this.loadDefinitionDetails();
     }
-    else {
-      updater.updateAlertGroups(function () {
-        updater.updateAlertDefinitions(function () {
-          updater.updateAlertDefinitionSummary(function () {
-            self.set('isLoaded', true);
-            // App.AlertDefinition doesn't represents real models
-            // Real model (see AlertDefinition types) should be used
-            self.set('controller.content', App.AlertDefinition.find().findProperty('id', parseInt(self.get('controller.content.id'))));
-            self.get('controller').loadAlertInstances();
-          });
+  },
+
+  loadDefinitionDetails: function() {
+    var self = this,
+        updater = App.router.get('updateController');
+
+    updater.updateAlertGroups(function () {
+      updater.updateAlertDefinitions(function () {
+        updater.updateAlertDefinitionSummary(function () {
+          self.set('isLoaded', true);
+          // App.AlertDefinition doesn't represents real models
+          // Real model (see AlertDefinition types) should be used
+          self.set('controller.content', App.AlertDefinition.find(parseInt(self.get('controller.content.id'))));
+          self.get('controller').loadAlertInstances();
         });
       });
-    }
+    });
   },
 
   didInsertElement: function () {

+ 8 - 13
ambari-web/app/views/main/alerts/manage_alert_groups_view.js

@@ -120,14 +120,11 @@ App.MainAlertsManageAlertGroupView = Em.View.extend({
   addDefinitionTooltip: function () {
     if (this.get('controller.selectedAlertGroup.default')) {
       return Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinitionToDefault');
+    } else if (this.get('controller.selectedAlertGroup.isAddDefinitionsDisabled')) {
+      return Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinitionDisabled');
+    } else {
+      return Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinition');
     }
-    else
-      if (this.get('controller.selectedAlertGroup.isAddDefinitionsDisabled')) {
-        return Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinitionDisabled');
-      }
-      else {
-        return  Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinition');
-      }
   }.property('controller.selectedAlertGroup.default', 'controller.selectedAlertGroup.isAddDefinitionsDisabled'),
 
   /**
@@ -137,13 +134,11 @@ App.MainAlertsManageAlertGroupView = Em.View.extend({
   removeDefinitionTooltip: function () {
     if (this.get('controller.selectedAlertGroup.default')) {
       return Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeDefinitionDisabled');
+    } else if (this.get('controller.isDeleteDefinitionsDisabled')) {
+      return Em.I18n.t('common.nothingToDelete');
+    } else {
+      return Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeDefinition');
     }
-    else
-      if (this.get('controller.isDeleteDefinitionsDisabled')) {
-        return 'Nothing to delete';
-      } else {
-        return Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeDefinition');
-      }
   }.property('controller.selectedAlertGroup.default', 'controller.isDeleteDefinitionsDisabled')
 
 });

+ 20 - 6
ambari-web/app/views/main/alerts/manage_alert_notifications_view.js

@@ -31,12 +31,24 @@ App.ManageAlertNotificationsView = Em.View.extend({
     return this.get('controller.selectedAlertNotification.groups').toArray().mapProperty('displayName').join(', ');
   }.property('controller.selectedAlertNotification', 'controller.selectedAlertNotification.groups.@each', 'controller.isLoaded'),
 
+  /**
+   * @type {boolean}
+   */
   isAddButtonDisabled: true,
 
+  /**
+   * @type {boolean}
+   */
   isEditButtonDisabled: true,
 
+  /**
+   * @type {boolean}
+   */
   isRemoveButtonDisabled: true,
 
+  /**
+   * @type {boolean}
+   */
   isDuplicateButtonDisabled: true,
 
   /**
@@ -55,6 +67,9 @@ App.ManageAlertNotificationsView = Em.View.extend({
     return this.get('controller.selectedAlertNotification.properties')['ambari.dispatch.recipients'];
   }.property('controller.selectedAlertNotification.properties'),
 
+  /**
+   * @type {string}
+   */
   severities: function () {
     return this.get('controller.selectedAlertNotification.alertStates').join(', ');
   }.property('controller.selectedAlertNotification.alertStates'),
@@ -96,17 +111,16 @@ App.ManageAlertNotificationsView = Em.View.extend({
       if (notifications && notifications.length) {
         this.set('selectedAlertNotification', this.get('controller.selectedAlertNotification') || notifications[0]);
         this.buttonObserver();
-      }  else {
-        if(!App.isOperator)
-	 {
-	    this.set('isAddButtonDisabled',false);
-	 }
+      } else {
+        if (!App.isOperator) {
+          this.set('isAddButtonDisabled', false);
+        }
         this.set('selectedAlertNotification', null);
       }
       Em.run.later(this, function () {
         App.tooltip(this.$("[rel='button-info']"));
         App.tooltip(this.$("[rel='button-info-dropdown']"), {placement: 'left'});
-      }, 50) ;
+      }, 50);
     }
   }.observes('controller.isLoaded'),
 

+ 455 - 0
ambari-web/test/mixins/main/host/details/host_components/decommissionable_test.js

@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+var uiEffects = require('utils/ui_effects');
 
 require('mixins/main/host/details/host_components/decommissionable');
 
@@ -37,6 +38,7 @@ describe('App.Decommissionable', function () {
 
   beforeEach(function () {
     decommissionable = Em.Object.create(App.Decommissionable);
+    decommissionable.set('$', Em.K);
   });
 
   describe('#decommissionView.text', function () {
@@ -57,4 +59,457 @@ describe('App.Decommissionable', function () {
 
   });
 
+  describe("#isComponentDecommissionDisable", function() {
+
+    beforeEach(function() {
+      decommissionable.set('componentForCheckDecommission', 'MC1');
+      decommissionable.set('content', Em.Object.create({service: Em.Object.create()}));
+    });
+
+    it("masterComponent is absent, service workStatus is STARTED", function() {
+      decommissionable.set('content.service.hostComponents', []);
+      decommissionable.set('content.service.workStatus', App.HostComponentStatus.started);
+      expect(decommissionable.get('isComponentDecommissionDisable')).to.be.false;
+    });
+
+    it("masterComponent is absent, service workStatus is STOPPED", function() {
+      decommissionable.set('content.service.hostComponents', []);
+      decommissionable.set('content.service.workStatus', App.HostComponentStatus.stopped);
+      expect(decommissionable.get('isComponentDecommissionDisable')).to.be.true;
+    });
+
+    it("masterComponent is present, component workStatus is STARTED", function() {
+      decommissionable.set('content.service.hostComponents', [Em.Object.create({
+        componentName: 'MC1',
+        workStatus: App.HostComponentStatus.started
+      })]);
+      decommissionable.set('content.service.workStatus', App.HostComponentStatus.started);
+      expect(decommissionable.get('isComponentDecommissionDisable')).to.be.false;
+    });
+
+    it("masterComponent is present, component workStatus is STOPPED", function() {
+      decommissionable.set('content.service.hostComponents', [Em.Object.create({
+        componentName: 'MC1',
+        workStatus: App.HostComponentStatus.stopped
+      })]);
+      decommissionable.set('content.service.workStatus', App.HostComponentStatus.started);
+      expect(decommissionable.get('isComponentDecommissionDisable')).to.be.true;
+    });
+  });
+
+  describe("#isRestartableComponent", function() {
+
+    beforeEach(function() {
+      sinon.stub(App, 'get').returns(['C2']);
+    });
+    afterEach(function() {
+      App.get.restore();
+    });
+
+    var testCases = [
+      {
+        input: {
+          isDecomissionable: false,
+          componentName: 'C1'
+        },
+        expected: false
+      },
+      {
+        input: {
+          isDecomissionable: true,
+          componentName: 'C1'
+        },
+        expected: false
+      },
+      {
+        input: {
+          isDecomissionable: false,
+          componentName: 'C2'
+        },
+        expected: false
+      },
+      {
+        input: {
+          isDecomissionable: true,
+          componentName: 'C2'
+        },
+        expected: true
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("isDecomissionable = " + test.input.isDecomissionable + "; " +
+         "componentName=" + test.input.componentName, function() {
+        decommissionable.setProperties({
+          isComponentDecommissionAvailable: test.input.isDecomissionable,
+          content: Em.Object.create({
+            componentName: test.input.componentName
+          })
+        });
+        expect(decommissionable.get('isRestartableComponent')).to.equal(test.expected);
+      });
+    }, this);
+  });
+
+  describe("#decommissionTooltipMessage", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.format, 'role').returns('role');
+    });
+    afterEach(function() {
+      App.format.role.restore();
+    });
+
+    var testCases = [
+      {
+        input: {
+          isComponentDecommissionDisable: false,
+          isComponentRecommissionAvailable: false,
+          isComponentDecommissionAvailable: false
+        },
+        expected: ''
+      },
+      {
+        input: {
+          isComponentDecommissionDisable: true,
+          isComponentRecommissionAvailable: false,
+          isComponentDecommissionAvailable: false
+        },
+        expected: ''
+      },
+      {
+        input: {
+          isComponentDecommissionDisable: true,
+          isComponentRecommissionAvailable: true,
+          isComponentDecommissionAvailable: false
+        },
+        expected: Em.I18n.t('hosts.decommission.tooltip.warning').format(Em.I18n.t('common.recommission'), 'role')
+      },
+      {
+        input: {
+          isComponentDecommissionDisable: true,
+          isComponentRecommissionAvailable: false,
+          isComponentDecommissionAvailable: true
+        },
+        expected: Em.I18n.t('hosts.decommission.tooltip.warning').format(Em.I18n.t('common.decommission'), 'role')
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("isComponentDecommissionDisable = " + test.input.isComponentDecommissionDisable + '; ' +
+         "isComponentRecommissionAvailable = " + test.input.isComponentRecommissionAvailable + '; ' +
+         "isComponentDecommissionAvailable = " + test.input.isComponentDecommissionAvailable
+        , function() {
+        decommissionable.reopen({
+          isComponentDecommissionDisable: test.input.isComponentDecommissionDisable,
+          isComponentRecommissionAvailable: test.input.isComponentRecommissionAvailable,
+          isComponentDecommissionAvailable: test.input.isComponentDecommissionAvailable
+        });
+        expect(decommissionable.get('decommissionTooltipMessage')).to.equal(test.expected);
+      });
+    }, this);
+  });
+
+  describe("#loadComponentDecommissionStatus()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, 'getDesiredAdminState');
+    });
+    afterEach(function() {
+      decommissionable.getDesiredAdminState.restore();
+    });
+
+    it("getDesiredAdminState should be called", function() {
+      decommissionable.loadComponentDecommissionStatus();
+      expect(decommissionable.getDesiredAdminState.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#getDesiredAdminStateSuccessCallback()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, 'setDesiredAdminState');
+    });
+    afterEach(function() {
+      decommissionable.setDesiredAdminState.restore();
+    });
+
+    it("desired_admin_state is null", function() {
+      var response = {
+        HostRoles: {
+          desired_admin_state: null
+        }
+      };
+
+      expect(decommissionable.getDesiredAdminStateSuccessCallback(response)).to.be.null;
+      expect(decommissionable.setDesiredAdminState.called).to.be.false;
+    });
+
+    it("setDesiredAdminState should be called", function() {
+      var response = {
+        HostRoles: {
+          desired_admin_state: "STATUS"
+        }
+      };
+
+      expect(decommissionable.getDesiredAdminStateSuccessCallback(response)).to.equal('STATUS');
+      expect(decommissionable.setDesiredAdminState.calledWith('STATUS')).to.be.true;
+    });
+  });
+
+  describe("#getDecommissionStatus()", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.ajax, 'send');
+    });
+    afterEach(function() {
+      App.ajax.send.restore();
+    });
+
+    it("App.ajax.send should be called", function() {
+      decommissionable.setProperties({
+        componentForCheckDecommission: 'C1',
+        content: Em.Object.create({
+          hostName: 'host1',
+          service: Em.Object.create({
+            serviceName: 'S1'
+          })
+        })
+      });
+      decommissionable.getDecommissionStatus();
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'host.host_component.decommission_status',
+        sender: decommissionable,
+        data: {
+          hostName: 'host1',
+          componentName: 'C1',
+          serviceName: 'S1'
+        },
+        success: 'getDecommissionStatusSuccessCallback',
+        error: 'getDecommissionStatusErrorCallback'
+      });
+    });
+  });
+
+  describe("#getDecommissionStatusSuccessCallback()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, 'setDecommissionStatus');
+    });
+    afterEach(function() {
+      decommissionable.setDecommissionStatus.restore();
+    });
+
+    it("ServiceComponentInfo is null", function() {
+      var response = {
+        ServiceComponentInfo: null
+      };
+
+      expect(decommissionable.getDecommissionStatusSuccessCallback(response)).to.be.null;
+      expect(decommissionable.setDecommissionStatus.called).to.be.false;
+    });
+
+    it("setDecommissionStatus should be called", function() {
+      var response = {
+        ServiceComponentInfo: {},
+        host_components: [{
+          HostRoles: {
+            state: 'STATE'
+          }
+        }]
+      };
+      var statusObject = {
+        component_state: 'STATE'
+      };
+
+      expect(decommissionable.getDecommissionStatusSuccessCallback(response)).to.eql(statusObject);
+      expect(decommissionable.setDecommissionStatus.calledWith(statusObject)).to.be.true;
+    });
+  });
+
+  describe("#setStatusAs()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, 'startBlinking');
+    });
+    afterEach(function() {
+      decommissionable.startBlinking.restore();
+    });
+
+    describe("#INSERVICE status", function() {
+      beforeEach(function() {
+        decommissionable.set('isStart', false);
+        decommissionable.setStatusAs('INSERVICE');
+      });
+
+      it("isComponentRecommissionAvailable should be false", function() {
+        expect(decommissionable.get('isComponentRecommissionAvailable')).to.be.false;
+      });
+      it("isComponentDecommissioning should be false", function() {
+        expect(decommissionable.get('isComponentDecommissioning')).to.be.false;
+      });
+      it("isComponentDecommissionAvailable should be false", function() {
+        expect(decommissionable.get('isComponentDecommissionAvailable')).to.be.false;
+      });
+    });
+
+    describe("#DECOMMISSIONING status", function() {
+      beforeEach(function() {
+        decommissionable.setStatusAs('DECOMMISSIONING')
+      });
+
+      it("isComponentRecommissionAvailable should be true", function() {
+        expect(decommissionable.get('isComponentRecommissionAvailable')).to.be.true;
+      });
+      it("isComponentDecommissioning should be true", function() {
+        expect(decommissionable.get('isComponentDecommissioning')).to.be.true;
+      });
+      it("isComponentDecommissionAvailable should be false", function() {
+        expect(decommissionable.get('isComponentDecommissionAvailable')).to.be.false;
+      });
+    });
+
+    describe("#DECOMMISSIONED status", function() {
+      beforeEach(function() {
+        decommissionable.setStatusAs('DECOMMISSIONED')
+      });
+
+      it("isComponentRecommissionAvailable should be true", function() {
+        expect(decommissionable.get('isComponentRecommissionAvailable')).to.be.true;
+      });
+      it("isComponentDecommissioning should be false", function() {
+        expect(decommissionable.get('isComponentDecommissioning')).to.be.false;
+      });
+      it("isComponentDecommissionAvailable should be false", function() {
+        expect(decommissionable.get('isComponentDecommissionAvailable')).to.be.false;
+      });
+    });
+
+    describe("#RS_DECOMMISSIONED status", function() {
+      beforeEach(function() {
+        decommissionable.set('isStart', false);
+        decommissionable.setStatusAs('RS_DECOMMISSIONED');
+      });
+
+      it("isComponentRecommissionAvailable should be true", function() {
+        expect(decommissionable.get('isComponentRecommissionAvailable')).to.be.true;
+      });
+      it("isComponentDecommissioning should be false", function() {
+        expect(decommissionable.get('isComponentDecommissioning')).to.be.false;
+      });
+      it("isComponentDecommissionAvailable should be false", function() {
+        expect(decommissionable.get('isComponentDecommissionAvailable')).to.be.false;
+      });
+    });
+  });
+
+
+  describe("#doBlinking()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, 'pulsate');
+      sinon.spy(decommissionable, 'doBlinking');
+      sinon.stub(decommissionable, 'startBlinking');
+    });
+    afterEach(function() {
+      decommissionable.pulsate.restore();
+      decommissionable.doBlinking.restore();
+      decommissionable.startBlinking.restore();
+    });
+
+    it("INSTALLED status", function() {
+      decommissionable.setProperties({
+        workStatus: 'INSTALLED',
+        content: Em.Object.create()
+      });
+      decommissionable.doBlinking();
+      expect(decommissionable.pulsate.called).to.be.false;
+    });
+
+    it("STARTED status, content is null", function() {
+      decommissionable.setProperties({
+        workStatus: 'STARTED',
+        content: null
+      });
+      decommissionable.doBlinking();
+      expect(decommissionable.pulsate.called).to.be.false;
+    });
+
+    it("STARTED status, isDecommissioning = false", function() {
+      decommissionable.setProperties({
+        workStatus: 'STARTED',
+        content: Em.Object.create(),
+        isDecommissioning: false
+      });
+      decommissionable.doBlinking();
+      expect(decommissionable.pulsate.called).to.be.false;
+    });
+
+    it("STARTED status, isDecommissioning = true, isBlinking = true", function() {
+      decommissionable.setProperties({
+        workStatus: 'STARTED',
+        isDecommissioning: true,
+        isBlinking: true,
+        content: Em.Object.create()
+      });
+      decommissionable.doBlinking();
+      expect(decommissionable.pulsate.called).to.be.false;
+    });
+
+    it("STARTED status, isDecommissioning = true, isBlinking = false", function() {
+      decommissionable.setProperties({
+        workStatus: 'STARTED',
+        isDecommissioning: true,
+        isBlinking: false,
+        content: Em.Object.create()
+      });
+      decommissionable.doBlinking();
+      expect(decommissionable.get('isBlinking')).to.be.true;
+      expect(decommissionable.pulsate.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#pulsate()", function() {
+
+    beforeEach(function() {
+      sinon.stub(uiEffects, 'pulsate', function(node, time, callback) {
+        callback();
+      });
+      sinon.stub(decommissionable, 'doBlinking');
+      decommissionable.pulsate();
+    });
+    afterEach(function() {
+      uiEffects.pulsate.restore();
+      decommissionable.doBlinking.restore();
+    });
+
+    it("uiEffects.pulsate should be called", function() {
+      expect(uiEffects.pulsate.calledOnce).to.be.true;
+    });
+
+    it("doBlinking should be called", function() {
+      expect(decommissionable.doBlinking.calledOnce).to.be.true;
+    });
+
+    it("isBlinking should be false", function() {
+      expect(decommissionable.get('isBlinking')).to.be.false;
+    });
+  });
+
+  describe("#startBlinking()", function() {
+
+    beforeEach(function() {
+      sinon.stub(decommissionable, '$').returns({stop: Em.K, css: Em.K});
+      sinon.stub(decommissionable, 'doBlinking');
+    });
+    afterEach(function() {
+      decommissionable.$.restore();
+      decommissionable.doBlinking.restore();
+    });
+
+    it("doBlinking should be called", function() {
+      decommissionable.startBlinking();
+      expect(decommissionable.doBlinking.calledOnce).to.be.true;
+    });
+  });
 });

+ 207 - 0
ambari-web/test/mixins/main/host/details/host_components/install_component_test.js

@@ -0,0 +1,207 @@
+/**
+ * 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('mixins/main/host/details/host_components/install_component');
+
+var installComponent;
+
+describe('App.InstallComponent', function () {
+
+  beforeEach(function () {
+    installComponent = Em.Object.create(App.InstallComponent);
+  });
+
+  describe("#installHostComponentCall()", function() {
+    var component = Em.Object.create({
+      componentName: 'C1',
+      displayName: 'c1'
+    });
+
+    beforeEach(function() {
+      sinon.stub(installComponent, 'updateAndCreateServiceComponent').returns({done: Em.clb});
+      sinon.stub(App.ajax, 'send');
+      installComponent.installHostComponentCall('host1', component);
+    });
+    afterEach(function() {
+      installComponent.updateAndCreateServiceComponent.restore();
+      App.ajax.send.restore();
+    });
+
+    it("updateAndCreateServiceComponent should be called", function() {
+      expect(installComponent.updateAndCreateServiceComponent.calledWith('C1')).to.be.true;
+    });
+
+    it("App.ajax.send should be called", function() {
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'host.host_component.add_new_component',
+        sender: installComponent,
+        data: {
+          hostName: 'host1',
+          component: component,
+          data: JSON.stringify({
+            RequestInfo: {
+              "context": Em.I18n.t('requestInfo.installHostComponent') + " " + 'c1'
+            },
+            Body: {
+              host_components: [
+                {
+                  HostRoles: {
+                    component_name: 'C1'
+                  }
+                }
+              ]
+            }
+          })
+        },
+        success: 'addNewComponentSuccessCallback',
+        error: 'ajaxErrorCallback'
+      });
+    });
+  });
+
+  describe("#addNewComponentSuccessCallback()", function() {
+    var params = {
+      hostName: 'host1',
+      component: Em.Object.create({
+        componentName: 'C1',
+        serviceName: 'S1',
+        displayName: 'c1'
+      })
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.ajax, 'send');
+    });
+    afterEach(function() {
+      App.ajax.send.restore();
+    });
+
+
+    it("App.ajax.send should be called", function() {
+      installComponent.addNewComponentSuccessCallback({}, {}, params);
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'common.host.host_component.update',
+        sender: App.router.get('mainHostDetailsController'),
+        data: {
+          hostName: 'host1',
+          componentName: 'C1',
+          serviceName: 'S1',
+          component: params.component,
+          "context": Em.I18n.t('requestInfo.installNewHostComponent') + " " + 'c1',
+          HostRoles: {
+            state: 'INSTALLED'
+          },
+          urlParams: "HostRoles/state=INIT"
+        },
+        success: 'installNewComponentSuccessCallback',
+        error: 'ajaxErrorCallback'
+      });
+    });
+  });
+
+  describe("#ajaxErrorCallback()", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.ajax, 'defaultErrorHandler');
+    });
+    afterEach(function() {
+      App.ajax.defaultErrorHandler.restore();
+    });
+
+    it("App.ajax.defaultErrorHandler should be called", function() {
+      installComponent.ajaxErrorCallback({}, {}, 'error', {method: 'method1', url: 'url1'}, {});
+      expect(App.ajax.defaultErrorHandler.calledWith({}, 'url1', 'method1'));
+    });
+  });
+
+  describe("#updateAndCreateServiceComponent()", function() {
+
+    var updater = {
+      updateComponentsState: Em.clb,
+      updateServiceMetric: Em.clb
+    };
+
+    beforeEach(function() {
+      sinon.spy(updater, 'updateComponentsState');
+      sinon.spy(updater, 'updateServiceMetric');
+      sinon.stub(App.router, 'get').returns(updater);
+      sinon.stub(installComponent, 'createServiceComponent');
+      installComponent.updateAndCreateServiceComponent('C1');
+    });
+    afterEach(function() {
+      App.router.get.restore();
+      installComponent.createServiceComponent.restore();
+      updater.updateComponentsState.restore();
+      updater.updateServiceMetric.restore();
+    });
+
+    it("updater.updateComponentsState should be called", function() {
+      expect(updater.updateComponentsState.calledOnce).to.be.true;
+    });
+
+    it("updater.updateServiceMetric should be called", function() {
+      expect(updater.updateServiceMetric.calledOnce).to.be.true;
+    });
+
+    it("createServiceComponent should be called", function() {
+      expect(installComponent.createServiceComponent.calledWith('C1')).to.be.true;
+    });
+  });
+
+  describe("#createServiceComponent()", function() {
+    var dfd = {resolve: Em.K};
+
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns([Em.Object.create({
+        componentName: 'C2',
+        serviceName: 'S1'
+      })]);
+      sinon.spy(dfd, 'resolve');
+      sinon.stub(App.ajax, 'send').returns({complete: Em.clb});
+      this.mock = sinon.stub(App.Service, 'find');
+      this.mock.returns([{serviceName: "S1"}]);
+      this.mock.withArgs('S1').returns(Em.Object.create({serviceComponents: ['C1']}))
+
+    });
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+      dfd.resolve.restore();
+      this.mock.restore();
+      App.ajax.send.restore();
+    });
+
+    it("component already created", function() {
+      expect(installComponent.createServiceComponent('C1', dfd)).to.be.null;
+      expect(dfd.resolve.calledOnce).to.be.true;
+    });
+
+    it("component not created", function() {
+      installComponent.createServiceComponent('C2', dfd);
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'common.create_component',
+        sender: installComponent,
+        data: {
+          componentName: 'C2',
+          serviceName: 'S1'
+        }
+      });
+      expect(dfd.resolve.calledOnce).to.be.true;
+    });
+  });
+});

+ 1 - 1
ambari-web/test/views/main/alert_definitions_view_test.js

@@ -31,7 +31,7 @@ describe('App.MainAlertDefinitionsView', function () {
   beforeEach(function () {
     view = getView();
     sinon.stub(App.db, 'setFilterConditions', Em.K);
-    sinon.stub(App.db, 'getFilterConditions', Em.K);
+    sinon.stub(App.db, 'getFilterConditions').returns([]);
     sinon.stub(App.db, 'getDisplayLength', Em.K);
     sinon.stub(App.db, 'setStartIndex', Em.K);
     sinon.stub(view, 'initFilters', Em.K);

+ 123 - 0
ambari-web/test/views/main/alerts/definition_configs_view_test.js

@@ -0,0 +1,123 @@
+/**
+ * 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 alertDefinitionConfigsView, alertConfigRadioButtonView;
+
+describe('App.AlertDefinitionConfigsView', function () {
+
+  beforeEach(function () {
+    alertDefinitionConfigsView = App.AlertDefinitionConfigsView.create({
+      controller: Em.Object.create({
+        renderConfigs: Em.K
+      })
+    });
+  });
+
+  describe("#init()", function() {
+
+    beforeEach(function() {
+      sinon.spy(alertDefinitionConfigsView.get('controller'), 'renderConfigs');
+      alertDefinitionConfigsView.set('canEdit', true);
+      alertDefinitionConfigsView.set('isWizard', true);
+      alertDefinitionConfigsView.set('alertDefinitionType', 't1');
+      alertDefinitionConfigsView.set('content', {});
+      alertDefinitionConfigsView.init();
+    });
+
+    afterEach(function() {
+      alertDefinitionConfigsView.get('controller').renderConfigs.restore();
+    });
+
+    it("should set canEdit to controller", function() {
+      expect(alertDefinitionConfigsView.get('controller.canEdit')).to.be.true;
+    });
+
+    it("should set isWizard to controller", function() {
+      expect(alertDefinitionConfigsView.get('controller.isWizard')).to.be.true;
+    });
+
+    it("should set alertDefinitionType to controller", function() {
+      expect(alertDefinitionConfigsView.get('controller.alertDefinitionType')).to.equal('t1');
+    });
+
+    it("should set content to controller", function() {
+      expect(alertDefinitionConfigsView.get('controller.content')).to.eql({});
+    });
+
+    it("should call renderConfigs of controller", function() {
+      expect(alertDefinitionConfigsView.get('controller').renderConfigs.calledOnce).to.be.true;
+    });
+  });
+});
+
+describe('App.AlertConfigRadioButtonView', function () {
+
+  beforeEach(function () {
+    alertConfigRadioButtonView = App.AlertConfigRadioButtonView.create({
+      parentView: Em.Object.create({
+        controller: Em.Object.create({
+          changeType: Em.K,
+          configs: [
+            Em.Object.create({
+              group: 'g1',
+              value: true
+            })
+          ]
+        })
+      }),
+      property: {
+        name: 'p1'
+      }
+    });
+  });
+
+  describe("#change", function() {
+    var config = Em.Object.create({
+      group: 'g1',
+      value: true
+    });
+
+    beforeEach(function() {
+      sinon.stub(alertConfigRadioButtonView.get('parentView.controller'), 'changeType');
+      alertConfigRadioButtonView.reopen({
+        name: 'g1'
+      });
+      alertConfigRadioButtonView.set('parentView.controller.configs', [config]);
+      alertConfigRadioButtonView.change();
+    });
+
+    afterEach(function() {
+      alertConfigRadioButtonView.get('parentView.controller').changeType.restore();
+    });
+
+    it("should set property.value", function() {
+      expect(alertConfigRadioButtonView.get('property.value')).to.be.true;
+    });
+
+    it("should set config value", function() {
+      expect(config.get('value')).to.be.false;
+    });
+
+    it("should call changeType()", function() {
+      expect(alertConfigRadioButtonView.get('parentView.controller').changeType.calledWith('p1')).to.be.true;
+    });
+  });
+
+});

+ 319 - 22
ambari-web/test/views/main/alerts/definition_details_view_test.js

@@ -22,7 +22,12 @@ var view, instanceTableRow;
 
 function getView() {
   return App.MainAlertDefinitionDetailsView.create({
-    initFilters: Em.K
+    initFilters: Em.K,
+    controller: Em.Object.create({
+      loadAlertInstances: Em.K,
+      clearStep: Em.K,
+      content: Em.Object.create()
+    })
   });
 }
 
@@ -30,43 +35,303 @@ describe('App.MainAlertDefinitionDetailsView', function () {
 
   beforeEach(function () {
     view = getView();
-    instanceTableRow = view.get('instanceTableRow').create();
   });
 
-  describe("#goToHostAlerts()", function () {
-    beforeEach(function () {
-      sinon.stub(App.get('router'), 'transitionTo', Em.K);
+  describe("#willInsertElement()", function() {
+
+    beforeEach(function() {
+      sinon.spy(view.get('controller'), 'clearStep');
+      sinon.spy(view.get('controller'), 'loadAlertInstances');
+      sinon.stub(view, 'loadDefinitionDetails');
     });
-    afterEach(function () {
-      App.get('router').transitionTo.restore();
+
+    afterEach(function() {
+      view.get('controller').clearStep.restore();
+      view.get('controller').loadAlertInstances.restore();
+      view.loadDefinitionDetails.restore();
+    });
+
+    it("clearStep() should be called", function() {
+      view.willInsertElement();
+      expect(view.get('controller').clearStep.calledOnce).to.be.true;
+    });
+
+    it("content is loaded", function() {
+      view.set('controller.content.isLoaded', true);
+      view.willInsertElement();
+      expect(view.get('isLoaded')).to.be.true;
+      expect(view.get('controller').loadAlertInstances.calledOnce).to.be.true;
+    });
+
+    it("content is not loaded", function() {
+      view.set('controller.content.isLoaded', false);
+      view.willInsertElement();
+      expect(view.get('isLoaded')).to.be.false;
+      expect(view.loadDefinitionDetails.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#loadDefinitionDetails()", function() {
+    var mock = {
+      updateAlertGroups: function(callback) { callback(); },
+      updateAlertDefinitions: function(callback) { callback(); },
+      updateAlertDefinitionSummary: function(callback) { callback(); }
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(mock);
+      sinon.spy(mock, 'updateAlertGroups');
+      sinon.spy(mock, 'updateAlertDefinitions');
+      sinon.spy(mock, 'updateAlertDefinitionSummary');
+      sinon.stub(view.get('controller'), 'loadAlertInstances');
+      sinon.stub(App.AlertDefinition, 'find').returns({});
+
+      view.loadDefinitionDetails();
     });
-    it("not route to host - no event", function () {
-      instanceTableRow.goToHostAlerts(null);
-      expect(App.get('router').transitionTo.notCalled).to.be.true;
+
+    afterEach(function() {
+      App.router.get.restore();
+      mock.updateAlertGroups.restore();
+      mock.updateAlertDefinitions.restore();
+      mock.updateAlertDefinitionSummary.restore();
+      view.get('controller').loadAlertInstances.restore();
+      App.AlertDefinition.find.restore();
     });
-    it("not route to host - no event context", function () {
-      instanceTableRow.goToHostAlerts({});
-      expect(App.get('router').transitionTo.notCalled).to.be.true;
+
+    it("updateAlertGroups() should be called", function() {
+      expect(mock.updateAlertGroups.calledOnce).to.be.true;
+    });
+
+    it("updateAlertDefinitions() should be called", function() {
+      expect(mock.updateAlertDefinitions.calledOnce).to.be.true;
     });
-    it("routes to host", function () {
-      instanceTableRow.goToHostAlerts({"context": "hostname"});
-      expect(App.get('router').transitionTo.calledOnce).to.be.true;
+
+    it("updateAlertDefinitionSummary() should be called", function() {
+      expect(mock.updateAlertDefinitionSummary.calledOnce).to.be.true;
+    });
+
+    it("loadAlertInstances() should be called", function() {
+      expect(view.get('isLoaded')).to.be.true;
+      expect(view.get('controller.content')).to.eql({});
+      expect(view.get('controller').loadAlertInstances.calledOnce).to.be.true;
     });
   });
 
-  describe("#openFullResponse()", function() {
+  describe("#didInsertElement()", function() {
 
     beforeEach(function() {
-      sinon.stub(App, 'showLogsPopup');
+      sinon.stub(view, 'tooltipsUpdater');
+      sinon.stub(view, 'filter');
     });
 
     afterEach(function() {
-      App.showLogsPopup.restore();
+      view.tooltipsUpdater.restore();
+      view.filter.restore();
+    });
+
+    it("filter() should be called", function() {
+      view.didInsertElement();
+      expect(view.filter.calledOnce).to.be.true;
+    });
+
+    it("tooltipsUpdater() should be called", function() {
+      view.didInsertElement();
+      expect(view.tooltipsUpdater.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#tooltipsUpdater()", function () {
+
+    beforeEach(function () {
+      sinon.stub(Em.run, 'next', Em.clb);
+      sinon.stub(App, 'tooltip');
+    });
+
+    afterEach(function () {
+      Em.run.next.restore();
+      App.tooltip.restore();
+    });
+
+    it("Em.run.next should be called", function () {
+      view.tooltipsUpdater();
+      expect(Em.run.next.calledOnce).to.be.true;
+      expect(App.tooltip.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#lastDayCount", function () {
+    var lastDayCountView;
+
+    beforeEach(function () {
+      lastDayCountView = view.get('lastDayCount').create({
+        hostName: 'host1',
+        parentView: Em.Object.create({
+          controller: Em.Object.create()
+        })
+      });
+    });
+
+    describe("#count()", function () {
+
+      it("lastDayAlertsCount is null", function () {
+        lastDayCountView.set('parentView.controller.lastDayAlertsCount', null);
+        expect(lastDayCountView.get('count')).to.equal(Em.I18n.t('app.loadingPlaceholder'));
+      });
+
+      it("lastDayAlertsCount does not contain host", function () {
+        lastDayCountView.set('parentView.controller.lastDayAlertsCount', {});
+        expect(lastDayCountView.get('count')).to.equal(0);
+      });
+
+      it("lastDayAlertsCount is null", function () {
+        lastDayCountView.set('parentView.controller.lastDayAlertsCount', {host1: 1});
+        expect(lastDayCountView.get('count')).to.equal(1);
+      });
+
+    });
+  });
+
+  describe("#instanceTableRow", function() {
+    var instanceTableRowView;
+
+    beforeEach(function () {
+      instanceTableRowView = view.get('instanceTableRow').create();
+    });
+
+    describe("#didInsertElement()", function() {
+
+      beforeEach(function() {
+        sinon.stub(App, 'tooltip');
+      });
+      afterEach(function() {
+        App.tooltip.restore();
+      });
+
+      it("App.tooltip should be called", function() {
+        instanceTableRowView.didInsertElement();
+        expect(App.tooltip.calledTwice).to.be.true;
+      });
+    });
+
+    describe("#goToService()", function() {
+
+      beforeEach(function() {
+        sinon.stub(App.router, 'transitionTo');
+      });
+      afterEach(function() {
+        App.router.transitionTo.restore();
+      });
+
+      it("event is null", function() {
+        instanceTableRowView.goToService(null);
+        expect(App.router.transitionTo.called).to.be.false;
+      });
+
+      it("context is null", function() {
+        instanceTableRowView.goToService({context: null});
+        expect(App.router.transitionTo.called).to.be.false;
+      });
+
+      it("correct context", function() {
+        instanceTableRowView.goToService({context: {}});
+        expect(App.router.transitionTo.calledWith('main.services.service.summary', {})).to.be.true;
+      });
+    });
+
+    describe("#goToHostAlerts()", function() {
+      var ctrl = Em.Object.create({
+        referer: null
+      });
+
+      beforeEach(function() {
+        sinon.stub(App.router, 'transitionTo');
+        sinon.stub(App.router, 'get').returns(ctrl);
+      });
+      afterEach(function() {
+        App.router.transitionTo.restore();
+        App.router.get.restore();
+      });
+
+      it("event is null", function() {
+        instanceTableRowView.goToHostAlerts(null);
+        expect(App.router.transitionTo.called).to.be.false;
+        expect(ctrl.get('referer')).to.be.null;
+      });
+
+      it("context is null", function() {
+        instanceTableRowView.goToHostAlerts({context: null});
+        expect(App.router.transitionTo.called).to.be.false;
+        expect(ctrl.get('referer')).to.be.null;
+      });
+
+      it("correct context", function() {
+        instanceTableRowView.goToHostAlerts({context: {}});
+        expect(ctrl.get('referer')).to.equal('/login');
+        expect(App.router.transitionTo.calledWith('main.hosts.hostDetails.alerts', {})).to.be.true;
+      });
+    });
+    describe("#openFullResponse()", function() {
+
+      beforeEach(function() {
+        sinon.stub(App, 'showLogsPopup');
+      });
+
+      afterEach(function() {
+        App.showLogsPopup.restore();
+      });
+
+      it("App.showLogsPopup should be called", function() {
+        instanceTableRowView.openFullResponse({context: Em.Object.create({text: 'text1'})});
+        expect(App.showLogsPopup.calledWith(Em.I18n.t('alerts.instance.fullLogPopup.header'), 'text1')).to.be.true;
+      });
+    });
+  });
+
+
+
+  describe("#paginationLeftClass", function() {
+
+    it("startIndex is 2", function() {
+      view.set('startIndex', 2);
+      expect(view.get('paginationLeftClass')).to.equal('paginate_previous');
+    });
+
+    it("startIndex is 1", function() {
+      view.set('startIndex', 1);
+      expect(view.get('paginationLeftClass')).to.equal('paginate_disabled_previous');
+    });
+
+    it("startIndex is 0", function() {
+      view.set('startIndex', 0);
+      expect(view.get('paginationLeftClass')).to.equal('paginate_disabled_previous');
+    });
+  });
+
+  describe("#paginationRightClass", function() {
+
+    it("endIndex more than filteredCount", function() {
+      view.reopen({
+        endIndex: 4,
+        filteredCount: 3
+      });
+      expect(view.get('paginationRightClass')).to.equal('paginate_disabled_next');
+    });
+
+    it("endIndex equal to filteredCount", function() {
+      view.reopen({
+        endIndex: 4,
+        filteredCount: 4
+      });
+      expect(view.get('paginationRightClass')).to.equal('paginate_disabled_next');
     });
 
-    it("App.showLogsPopup should be called", function() {
-      instanceTableRow.openFullResponse({context: Em.Object.create({text: 'text1'})});
-      expect(App.showLogsPopup.calledWith(Em.I18n.t('alerts.instance.fullLogPopup.header'), 'text1')).to.be.true;
+    it("endIndex less than filteredCount", function() {
+      view.reopen({
+        endIndex: 3,
+        filteredCount: 4
+      });
+      view.propertyDidChange('paginationRightClass');
+      expect(view.get('paginationRightClass')).to.equal('paginate_next');
     });
   });
 
@@ -78,6 +343,38 @@ function getInstanceView() {
 
 describe('App.AlertInstanceServiceHostView', function () {
 
+  var instanceView;
+
+  beforeEach(function() {
+    instanceView = getInstanceView();
+  });
+
   App.TestAliases.testAsComputedAnd(getInstanceView(), 'showSeparator', ['instance.serviceDisplayName', 'instance.hostName']);
 
+  describe("#serviceIsLink", function() {
+
+    beforeEach(function() {
+      sinon.stub(App, 'get').returns(['S1']);
+    });
+    afterEach(function() {
+      App.get.restore();
+    });
+
+    it("service belongs to all", function() {
+      instanceView.set('instance', Em.Object.create({
+        service: Em.Object.create({
+          serviceName: 'S1'
+        })
+      }));
+      expect(instanceView.get('serviceIsLink')).to.be.true;
+    });
+    it("service does not belong to all", function() {
+      instanceView.set('instance', Em.Object.create({
+        service: Em.Object.create({
+          serviceName: 'S2'
+        })
+      }));
+      expect(instanceView.get('serviceIsLink')).to.be.false;
+    });
+  });
 });

+ 197 - 0
ambari-web/test/views/main/alerts/manage_alert_groups_view_test.js

@@ -18,6 +18,8 @@
 
 var App = require('app');
 
+var view;
+
 function getView() {
   return App.MainAlertsManageAlertGroupView.create({
     controller: Em.Object.create()
@@ -26,6 +28,201 @@ function getView() {
 
 describe('App.MainAlertsManageAlertGroupView', function () {
 
+  beforeEach(function () {
+    view = getView();
+  });
+
   App.TestAliases.testAsComputedIfThenElse(getView(), 'removeButtonTooltip', 'controller.isRemoveButtonDisabled', Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeButtonDisabled'), Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeButton'))
 
+
+  describe("#onGroupSelect()", function() {
+
+    beforeEach(function() {
+      view.removeObserver('selectedAlertGroup', view, 'onGroupSelect');
+      view.set('controller', Em.Object.create({selectedAlertGroup: null}));
+    });
+
+    it("selectedAlertGroup is null", function() {
+      view.set('selectedAlertGroup', null);
+      view.onGroupSelect();
+      expect(view.get('selectedAlertGroup')).to.be.null;
+      expect(view.get('controller.selectedAlertGroup')).to.be.null;
+      expect(view.get('controller.selectedDefinitions')).to.be.empty;
+    });
+
+    it("selectedAlertGroup is empty array", function() {
+      view.set('selectedAlertGroup', []);
+      view.onGroupSelect();
+      expect(view.get('selectedAlertGroup')).to.be.empty;
+      expect(view.get('controller.selectedAlertGroup')).to.be.null;
+      expect(view.get('controller.selectedDefinitions')).to.be.empty;
+    });
+
+    it("selectedAlertGroup is array with single element", function() {
+      view.set('selectedAlertGroup', [1]);
+      view.onGroupSelect();
+      expect(view.get('selectedAlertGroup')).to.eql([1]);
+      expect(view.get('controller.selectedAlertGroup')).to.equal(1);
+      expect(view.get('controller.selectedDefinitions')).to.be.empty;
+    });
+
+    it("selectedAlertGroup is array with two elements", function() {
+      view.set('selectedAlertGroup', [1, 2]);
+      view.onGroupSelect();
+      expect(view.get('selectedAlertGroup')).to.equal(2);
+      expect(view.get('controller.selectedAlertGroup')).to.equal(2);
+      expect(view.get('controller.selectedDefinitions')).to.be.empty;
+    });
+  });
+
+  describe("#setGroupInController()", function() {
+
+    beforeEach(function() {
+      view.removeObserver('controller.selectedAlertGroup', view, 'setGroupInController');
+      view.set('controller', Em.Object.create({selectedAlertGroup: 1}));
+      view.set('selectedAlertGroup', null);
+      sinon.stub(view, 'onGroupSelect');
+    });
+
+    afterEach(function() {
+      view.onGroupSelect.restore();
+    });
+
+    it("controller.isLoaded is false", function() {
+      view.set('controller.isLoaded', false);
+      view.setGroupInController();
+      expect(view.get('selectedAlertGroup')).to.be.null;
+    });
+
+    it("controller.isLoaded is true", function() {
+      view.set('controller.isLoaded', true);
+      view.setGroupInController();
+      expect(view.get('selectedAlertGroup')).to.eql([1]);
+    });
+  });
+
+  describe("#onLoad()", function() {
+
+    beforeEach(function() {
+      view.removeObserver('controller.isLoaded', view, 'onLoad');
+      view.set('controller', Em.Object.create({alertGroups: Em.A([{}])}));
+      view.set('selectedAlertGroup', null);
+      sinon.stub(view, 'setTooltips');
+    });
+
+    afterEach(function() {
+      view.setTooltips.restore();
+    });
+
+    it("controller.isLoaded is false", function() {
+      view.set('controller.isLoaded', false);
+      view.onLoad();
+      expect(view.get('selectedAlertGroup')).to.be.null;
+      expect(view.setTooltips.called).to.be.false;
+    });
+
+    it("controller.isLoaded is true", function() {
+      view.set('controller.isLoaded', true);
+      view.onLoad();
+      expect(view.get('selectedAlertGroup')).to.eql([{}]);
+      expect(view.setTooltips.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#willInsertElement()", function() {
+
+    beforeEach(function() {
+      view.set('controller', Em.Object.create({loadAlertNotifications: Em.K}));
+      sinon.spy(view.get('controller'), 'loadAlertNotifications');
+    });
+    afterEach(function() {
+      view.get('controller').loadAlertNotifications.restore();
+    });
+
+    it("loadAlertNotifications should be called", function() {
+      view.willInsertElement();
+      expect(view.get('controller').loadAlertNotifications.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#didInsertElement()", function() {
+
+    beforeEach(function() {
+      sinon.stub(view, 'onLoad');
+    });
+    afterEach(function() {
+      view.onLoad.restore();
+    });
+
+    it("loadAlertNotifications should be called", function() {
+      view.didInsertElement();
+      expect(view.onLoad.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#setTooltips()", function() {
+
+    beforeEach(function() {
+      sinon.stub(Em.run, 'next', Em.clb);
+      sinon.stub(App, 'tooltip');
+      view.setTooltips();
+    });
+    afterEach(function() {
+      Em.run.next.restore();
+      App.tooltip.restore();
+    });
+
+    it("Em.run.next should be called", function() {
+      expect(Em.run.next.calledOnce).to.be.true;
+    });
+
+    it("App.tooltip should be called twice", function() {
+      expect(App.tooltip.calledTwice).to.be.true;
+    });
+  });
+
+  describe("#addDefinitionTooltip", function() {
+
+    beforeEach(function() {
+      view.set('controller', Em.Object.create({selectedAlertGroup: Em.Object.create()}));
+    });
+
+    it("controller.selectedAlertGroup.default is true", function() {
+      view.set('controller.selectedAlertGroup.default', true);
+      expect(view.get('addDefinitionTooltip')).to.equal(Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinitionToDefault'));
+    });
+
+    it("controller.selectedAlertGroup.isAddDefinitionsDisabled is true", function() {
+      view.set('controller.selectedAlertGroup.isAddDefinitionsDisabled', true);
+      expect(view.get('addDefinitionTooltip')).to.equal(Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinitionDisabled'));
+    });
+
+    it("controller.selectedAlertGroup is null", function() {
+      view.set('controller.selectedAlertGroup', null);
+      expect(view.get('addDefinitionTooltip')).to.equal(Em.I18n.t('alerts.actions.manage_alert_groups_popup.addDefinition'));
+    });
+  });
+
+  describe("#removeDefinitionTooltip", function() {
+
+    beforeEach(function() {
+      view.set('controller', Em.Object.create({selectedAlertGroup: Em.Object.create()}));
+    });
+
+    it("controller.selectedAlertGroup.default is true", function() {
+      view.set('controller.selectedAlertGroup.default', true);
+      expect(view.get('removeDefinitionTooltip')).to.equal(Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeDefinitionDisabled'));
+    });
+
+    it("controller.isDeleteDefinitionsDisabled is true", function() {
+      view.set('controller.isDeleteDefinitionsDisabled', true);
+      expect(view.get('removeDefinitionTooltip')).to.equal(Em.I18n.t('common.nothingToDelete'));
+    });
+
+    it("isDeleteDefinitionsDisabled & selectedAlertGroup.default are false", function() {
+      view.set('controller.selectedAlertGroup.default', false);
+      view.set('controller.isDeleteDefinitionsDisabled', false);
+      expect(view.get('removeDefinitionTooltip')).to.equal(Em.I18n.t('alerts.actions.manage_alert_groups_popup.removeDefinition'));
+    });
+  });
 });

+ 234 - 33
ambari-web/test/views/main/alerts/manage_alert_notifications_view_test.js

@@ -34,7 +34,7 @@ describe('App.ManageAlertNotificationsView', function () {
 
     Em.A([
       {
-	 isOperator: false,
+        isOperator: false,
         selectedAlertNotification: {id: 1},
         m: 'some alert notification is selected and user is an admin',
         p: {
@@ -43,7 +43,7 @@ describe('App.ManageAlertNotificationsView', function () {
           isRemoveButtonDisabled: true,
           isDuplicateButtonDisabled: true
         },
-        e:  {
+        e: {
           isAddButtonDisabled: false,
           isEditButtonDisabled: false,
           isRemoveButtonDisabled: false,
@@ -60,7 +60,7 @@ describe('App.ManageAlertNotificationsView', function () {
           isRemoveButtonDisabled: true,
           isDuplicateButtonDisabled: true
         },
-        e:  {
+        e: {
           isAddButtonDisabled: true,
           isEditButtonDisabled: true,
           isRemoveButtonDisabled: true,
@@ -77,7 +77,7 @@ describe('App.ManageAlertNotificationsView', function () {
           isRemoveButtonDisabled: false,
           isDuplicateButtonDisabled: false
         },
-        e:  {
+        e: {
           isAddButtonDisabled: true,
           isEditButtonDisabled: true,
           isRemoveButtonDisabled: true,
@@ -86,7 +86,7 @@ describe('App.ManageAlertNotificationsView', function () {
       },
       {
         isOperator: true,
-        selectedAlertNotification: null,        
+        selectedAlertNotification: null,
         m: 'some alert notification is not selected and user is a non-admin operator',
         p: {
           isAddButtonDisabled: true,
@@ -94,7 +94,7 @@ describe('App.ManageAlertNotificationsView', function () {
           isRemoveButtonDisabled: false,
           isDuplicateButtonDisabled: false
         },
-        e:  {
+        e: {
           isAddButtonDisabled: true,
           isEditButtonDisabled: true,
           isRemoveButtonDisabled: true,
@@ -102,18 +102,18 @@ describe('App.ManageAlertNotificationsView', function () {
         }
       }
     ]).forEach(function (test) {
-        it(test.m, function () {
-          Em.keys(test.p).forEach(function (k) {
-            view.set(k, test.p[k]);
-          });
-          view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
-          App.isOperator=test.isOperator;
-          view.buttonObserver();
-          Em.keys(test.e).forEach(function (k) {
-            expect(view.get(k)).to.equal(test.e[k]);
-          });
+      it(test.m, function () {
+        Em.keys(test.p).forEach(function (k) {
+          view.set(k, test.p[k]);
+        });
+        view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
+        App.isOperator = test.isOperator;
+        view.buttonObserver();
+        Em.keys(test.e).forEach(function (k) {
+          expect(view.get(k)).to.equal(test.e[k]);
         });
       });
+    });
 
   });
 
@@ -129,32 +129,233 @@ describe('App.ManageAlertNotificationsView', function () {
         e: true
       }
     ]).forEach(function (test, i) {
-        it('test ' + (i + 1), function () {
-          view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
-          expect(view.get('showEmailDetails')).to.equal(test.e);
-        });
+      it('test ' + (i + 1), function () {
+        view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
+        expect(view.get('showEmailDetails')).to.equal(test.e);
       });
+    });
 
   });
 
   describe('#showSNMPDetails', function () {
 
     Em.A([
-        {
-          selectedAlertNotification: {type: 'SNMP'},
-          e: true
-        },
-        {
-          selectedAlertNotification: {type: 'EMAIL'},
-          e: false
-        }
-      ]).forEach(function (test, i) {
-        it('test ' + (i + 1), function () {
-          view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
-          expect(view.get('showSNMPDetails')).to.equal(test.e);
-        });
+      {
+        selectedAlertNotification: {type: 'SNMP'},
+        e: true
+      },
+      {
+        selectedAlertNotification: {type: 'EMAIL'},
+        e: false
+      }
+    ]).forEach(function (test, i) {
+      it('test ' + (i + 1), function () {
+        view.set('controller.selectedAlertNotification', test.selectedAlertNotification);
+        expect(view.get('showSNMPDetails')).to.equal(test.e);
       });
+    });
+
+  });
+
+  describe("#selectedAlertNotificationGroups", function () {
+    it("should contain group names", function () {
+      view.set('controller', Em.Object.create({
+        selectedAlertNotification: Em.Object.create({
+          groups: [
+            Em.Object.create({
+              displayName: 'g1'
+            }),
+            Em.Object.create({
+              displayName: 'g2'
+            })
+          ]
+        })
+      }));
+      expect(view.get('selectedAlertNotificationGroups')).to.equal('g1, g2');
+    });
+  });
+
+  describe("#email", function () {
+    it("should return ambari.dispatch.recipients", function () {
+      view.set('controller', Em.Object.create({
+        selectedAlertNotification: Em.Object.create({
+          properties: {
+            'ambari.dispatch.recipients': 1
+          }
+        })
+      }));
+      expect(view.get('email')).to.equal(1);
+    });
+  });
+
+  describe("#severities", function () {
+    it("should return list of states", function () {
+      view.set('controller', Em.Object.create({
+        selectedAlertNotification: Em.Object.create({
+          alertStates: ['st1', 'st2']
+        })
+      }));
+      expect(view.get('severities')).to.equal('st1, st2');
+    });
+  });
+
+  describe("#onAlertNotificationSelect()", function () {
+
+    beforeEach(function () {
+      view.removeObserver('selectedAlertNotification', view, 'onAlertNotificationSelect');
+      view.set('controller', Em.Object.create({selectedAlertNotification: null}));
+    });
 
+    it("selectedAlertNotification is null", function () {
+      view.set('selectedAlertNotification', null);
+      view.onAlertNotificationSelect();
+      expect(view.get('selectedAlertNotification')).to.be.null;
+      expect(view.get('controller.selectedAlertNotification')).to.be.null;
+    });
+
+    it("selectedAlertNotification is empty array", function () {
+      view.set('selectedAlertNotification', []);
+      view.onAlertNotificationSelect();
+      expect(view.get('selectedAlertNotification')).to.be.empty;
+      expect(view.get('controller.selectedAlertNotification')).to.be.null;
+    });
+
+    it("selectedAlertNotification is array with single element", function () {
+      view.set('selectedAlertNotification', [1]);
+      view.onAlertNotificationSelect();
+      expect(view.get('selectedAlertNotification')).to.eql([1]);
+      expect(view.get('controller.selectedAlertNotification')).to.equal(1);
+    });
+
+    it("selectedAlertNotification is array with two elements", function () {
+      view.set('selectedAlertNotification', [1, 2]);
+      view.onAlertNotificationSelect();
+      expect(view.get('selectedAlertNotification')).to.equal(2);
+      expect(view.get('controller.selectedAlertNotification')).to.equal(2);
+    });
   });
 
+  describe("#willInsertElement()", function () {
+
+    beforeEach(function () {
+      view.set('controller', Em.Object.create({loadAlertNotifications: Em.K}));
+      sinon.spy(view.get('controller'), 'loadAlertNotifications');
+    });
+    afterEach(function () {
+      view.get('controller').loadAlertNotifications.restore();
+    });
+
+    it("loadAlertNotifications should be called", function () {
+      view.willInsertElement();
+      expect(view.get('controller').loadAlertNotifications.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#didInsertElement()", function () {
+
+    beforeEach(function () {
+      sinon.stub(view, 'onLoad');
+    });
+    afterEach(function () {
+      view.onLoad.restore();
+    });
+
+    it("loadAlertNotifications should be called", function () {
+      view.didInsertElement();
+      expect(view.onLoad.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#onLoad()", function () {
+
+    beforeEach(function () {
+      view.removeObserver('controller.isLoaded', view, 'onLoad');
+      view.set('controller', Em.Object.create());
+      sinon.stub(view, 'buttonObserver');
+      sinon.stub(Em.run, 'later', function (context, callback) {
+        callback();
+      });
+      sinon.stub(App, 'tooltip');
+      this.clock = sinon.useFakeTimers();
+    });
+    afterEach(function () {
+      view.buttonObserver.restore();
+      Em.run.later.restore();
+      App.tooltip.restore();
+      this.clock.restore();
+    });
+
+    it("controller.isLoaded is false", function () {
+      view.set('controller.isLoaded', false);
+      view.onLoad();
+      expect(Em.run.later.called).to.be.false;
+    });
+
+    describe("controller.isLoaded is true, alertNotifications is null", function () {
+
+      beforeEach(function () {
+        view.set('controller.isLoaded', true);
+        view.set('controller.alertNotifications', null);
+      });
+
+      it("Em.run.later should be called", function () {
+        view.onLoad();
+        expect(Em.run.later.calledOnce).to.be.true;
+      });
+
+      it("App.tooltip should be called twice", function () {
+        view.onLoad();
+        this.clock.tick(50);
+        expect(App.tooltip.calledTwice).to.be.true;
+      });
+
+      it("selectedAlertNotification should be null", function () {
+        view.onLoad();
+        expect(view.get('selectedAlertNotification')).to.be.null;
+      });
+
+      it("isAddButtonDisabled should be true", function () {
+        view.set('isAddButtonDisabled', true);
+        App.isOperator = true;
+        view.onLoad();
+        expect(view.get('isAddButtonDisabled')).to.be.true;
+      });
+
+      it("isAddButtonDisabled should be false", function () {
+        view.set('isAddButtonDisabled', true);
+        App.isOperator = false;
+        view.onLoad();
+        expect(view.get('isAddButtonDisabled')).to.be.false;
+      });
+    });
+
+    describe("controller.isLoaded is true, alertNotifications is array", function () {
+
+      beforeEach(function () {
+        view.set('controller.isLoaded', true);
+        view.set('controller.alertNotifications', [{}]);
+      });
+
+      it("Em.run.later should be called", function () {
+        view.onLoad();
+        expect(Em.run.later.calledOnce).to.be.true;
+      });
+
+      it("App.tooltip should be called twice", function () {
+        view.onLoad();
+        this.clock.tick(50);
+        expect(App.tooltip.calledTwice).to.be.true;
+      });
+
+      it("selectedAlertNotification should be object", function () {
+        view.onLoad();
+        expect(view.get('selectedAlertNotification')).to.eql({});
+      });
+
+      it("buttonObserver should be called", function () {
+        view.onLoad();
+        expect(view.buttonObserver.calledOnce).to.be.true;
+      });
+    });
+  });
 });