AMBARI-25149. Cover all not covered common views
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index 760a27d..6e6ad3a 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -456,6 +456,10 @@
   'test/views/common/form/field_view_test',
   'test/views/common/form/manage_kdc_credentials_form_test',
   'test/views/common/log_file_search_view_test',
+  'test/views/common/log_search_ui_link_view_test',
+  'test/views/common/log_tail_view_test',
+  'test/views/common/not-scrollable-textarea_test',
+  'test/views/common/service_restart_view_test',
   'test/views/common/search_box_view_test',
   'test/views/wizard/step3/hostLogPopupBody_view_test',
   'test/views/wizard/step3/hostWarningPopupBody_view_test',
diff --git a/ambari-web/app/views/common/log_tail_view.js b/ambari-web/app/views/common/log_tail_view.js
index 4bc8c89..5ec87a3 100644
--- a/ambari-web/app/views/common/log_tail_view.js
+++ b/ambari-web/app/views/common/log_tail_view.js
@@ -231,5 +231,4 @@
     if (!this.get('pollLogs') || this.get('pollLogTimeoutId') === null) return;
     clearTimeout(this.get('pollLogTimeoutId'));
   }
-
 });
diff --git a/ambari-web/test/views/common/log_search_ui_link_view_test.js b/ambari-web/test/views/common/log_search_ui_link_view_test.js
new file mode 100644
index 0000000..1bf8c00
--- /dev/null
+++ b/ambari-web/test/views/common/log_search_ui_link_view_test.js
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.LogSearchUILinkView', function() {
+  var view;
+  beforeEach(function () {
+    view = App.LogSearchUILinkView.create({});
+  });
+
+  describe('#content', function () {
+    it('should keep LOGSEARCH service if it present', function () {
+      var servicesArr = [
+        Em.Object.create({serviceName: 'HDFS'}),
+        Em.Object.create({serviceName: 'LOGSEARCH'}),
+        Em.Object.create({serviceName: 'HIVE'}),
+      ];
+      sinon.stub(App.Service, 'find').returns(servicesArr);
+      expect(view.get('content')).to.be.eql(servicesArr[1]);
+      App.Service.find.restore();
+    });
+
+    it('should keep null if LOGSEARCH is not present', function () {
+      var servicesArr = [
+        Em.Object.create({serviceName: 'HDFS'}),
+        Em.Object.create({serviceName: 'HIVE'}),
+      ];
+      sinon.stub(App.Service, 'find').returns(servicesArr);
+      expect(view.get('content')).to.be.equal(undefined);
+      App.Service.find.restore();
+    });
+  });
+
+  describe('#formatedLink', function () {
+    it('should be false if quickLinks is empty', function () {
+      view.set('quickLinks', []);
+      expect(view.get('formatedLink')).to.be.equal(false);
+    });
+
+    it('should be equal to url of quickLink of logsearch and quiclLinksParams', function () {
+      view.set('quickLinks', [
+        Em.Object.create({'label': 'Log Search UI', url: 'http://logsearchling.com'}),
+        Em.Object.create({'label': 'test 1', url: 'http://test1.com'}),
+        Em.Object.create({'label': 'test 2', url: 'http://test2.com'}),
+      ]);
+      view.set('linkQueryParams', '?param1=p1&param2=p2');
+      expect(view.get('formatedLink')).to.be.equal('http://logsearchling.com?param1=p1&param2=p2');
+    });
+  });
+});
\ No newline at end of file
diff --git a/ambari-web/test/views/common/log_tail_view_test.js b/ambari-web/test/views/common/log_tail_view_test.js
new file mode 100644
index 0000000..e1dd842
--- /dev/null
+++ b/ambari-web/test/views/common/log_tail_view_test.js
@@ -0,0 +1,385 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.LogTailView', function() {
+  var view;
+  beforeEach(function () {
+    view = App.LogTailView.create({});
+  });
+
+  describe('#didInsertElement', function () {
+    beforeEach(function () {
+      sinon.stub(view, 'infiniteScrollInit');
+      sinon.stub(view, 'fetchRows').returns({then: function () {}});
+      sinon.stub(view, 'subscribeResize');
+    });
+
+    afterEach(function () {
+      view.infiniteScrollInit.restore();
+      view.fetchRows.restore();
+      view.subscribeResize.restore();
+    });
+
+    it('should call infiniteScrollInit', function () {
+      view.didInsertElement();
+      expect(view.infiniteScrollInit.calledOnce).to.be.true;
+    });
+
+    it('should call fetchRows', function () {
+      view.didInsertElement();
+      expect(view.fetchRows.calledOnce).to.be.true;
+    });
+
+    it('should call subscribeResize', function () {
+      view.didInsertElement();
+      expect(view.subscribeResize.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#_infiniteScrollHandler', function () {
+    beforeEach(function () {
+      sinon.stub(view, 'fetchRows').returns({then: function () {}});
+    });
+
+    afterEach(function () {
+      view.fetchRows.restore();
+    });
+
+    it('should do nothing if scrollTop is not equal to 0', function () {
+      view._infiniteScrollHandler({target: {}});
+      expect(view.fetchRows.called).to.be.false;
+    });
+
+    it('should do nothing if scrollTop is not equal to 0 noOldLogs is true and oldLogsIsFetching is false', function () {
+      sinon.stub(window, '$').returns({
+        scrollTop: function () {
+          return 0;
+        },
+        get: function () {
+          return {
+            clientHeight: 100
+          }
+        },
+        prop: function () {
+          return 15;
+        },
+        trigger: function () {
+        }
+      });
+      view.set('noOldLogs', true);
+      view.set('oldLogsIsFetching', false);
+      view._infiniteScrollHandler({target: {}});
+      expect(view.fetchRows.called).to.be.false;
+      window.$.restore();
+    });
+
+    it('should call fetchRows if scrollTop is not equal to 0 noOldLogs is false and oldLogsIsFetching is false', function () {
+      sinon.stub(window, '$').returns({
+        scrollTop: function () {
+          return 0;
+        },
+        get: function () {
+          return {
+            clientHeight: 100
+          }
+        },
+        prop: function () {
+          return 15;
+        },
+        trigger: function () {
+        }
+      });
+      view.set('noOldLogs', false);
+      view.set('oldLogsIsFetching', false);
+      view._infiniteScrollHandler({target: {}});
+      expect(view.fetchRows.called).to.be.true;
+      window.$.restore();
+    });
+  });
+
+  describe('#willDestroyElement', function () {
+    it('should call stopLogPolling unsubscribeResize and clear log rows', function () {
+      sinon.stub(view, 'stopLogPolling');
+      sinon.stub(view, 'unsubscribeResize');
+      var logRows = view.get('logRows');
+      sinon.stub(logRows, 'clear');
+      view.willDestroyElement();
+      expect(view.stopLogPolling.called).to.be.true;
+      expect(view.unsubscribeResize.called).to.be.true;
+      expect(logRows.clear.called).to.be.true;
+      view.stopLogPolling.restore();
+      view.unsubscribeResize.restore();
+      logRows.clear.restore();
+    });
+  });
+
+  describe('#unsubscribeResize', function () {
+    var $ = {off: function () {}};
+    beforeEach(function () {
+      sinon.stub($, 'off');
+      sinon.stub(window, '$').returns($);
+    });
+
+    afterEach(function () {
+      $.off.restore();
+      window.$.restore();
+    });
+
+    it('should do nothing if autoResize is false', function () {
+      view.set('autoResize', false);
+      view.unsubscribeResize();
+      expect($.off.called).to.be.false;
+    });
+
+    it('should call off if autoResize is true', function () {
+      view.set('autoResize', true);
+      view.unsubscribeResize();
+      expect($.off.called).to.be.true;
+    });
+  });
+
+  describe('#subscribeResize', function () {
+    var $ = {on: function () {}};
+    beforeEach(function () {
+      sinon.stub($, 'on');
+      sinon.stub(view, 'resizeHandler')
+      sinon.stub(window, '$').returns($);
+    });
+
+    afterEach(function () {
+      $.on.restore();
+      view.resizeHandler.restore();
+      window.$.restore();
+    });
+
+    it('should do nothing if autoResize is false', function () {
+      view.set('autoResize', false);
+      view.subscribeResize();
+      expect($.on.called).to.be.false;
+      expect(view.resizeHandler.called).to.be.false;
+    });
+
+    it('should call on and resizeHandler if autoResize is true', function () {
+      view.set('autoResize', true);
+      view.subscribeResize();
+      expect($.on.called).to.be.true;
+      expect(view.resizeHandler.called).to.be.true;
+    });
+  });
+
+  describe('#fetchRowsSuccess', function () {
+    beforeEach(function () {
+      sinon.stub(view, 'infiniteScrollSetDataAvailable');
+    });
+
+    afterEach(function () {
+      view.infiniteScrollSetDataAvailable.restore();
+    });
+
+    it('should call infiniteScrollSetDataAvailable and return empty array if logList is not available', function () {
+      var result = view.fetchRowsSuccess({});
+      expect(result).to.be.eql([]);
+      expect(view.infiniteScrollSetDataAvailable.called).to.be.true;
+    });
+
+    it('should call infiniteScrollSetDataAvailable and return empty array if logList is empty array', function () {
+      var result = view.fetchRowsSuccess({logList: []});
+      expect(result).to.be.eql([]);
+      expect(view.infiniteScrollSetDataAvailable.called).to.be.true;
+    });
+
+    it('should remap data correctly if logList in not empty', function () {
+      var logList = [
+        {
+          log_message: 'test_msg',
+          logtime: 1549533600141,
+          level: 1,
+          id: 1
+        }
+      ];
+      var result = view.fetchRowsSuccess({logList: logList});
+      expect(result).to.be.eql([Em.Object.create({
+        logMessage: 'test_msg',
+        level: 1,
+        id: 1,
+        logtime: 1549533600141,
+        logtimeFormatted: '2019-02-07 13:00:00,141'
+      })]);
+    });
+  });
+
+  describe('#refreshContent', function () {
+    beforeEach(function () {
+      var respData = [{
+        log_message: 'test_msg',
+        level: 1,
+        id: 1,
+        logtime: 1549533600141,
+      }, {
+        log_message: 'test_msg2',
+        level: 1,
+        id: 2,
+        logtime: 1549533600141,
+      },{
+        log_message: 'test_msg3',
+        level: 1,
+        id: 3,
+        logtime: 1549533600139,
+      }];
+      sinon.stub(view, 'fetchRows').returns({then: function (f) {
+        f({logList: respData});
+      }});
+      sinon.stub(view, 'appendLogRows');
+      sinon.stub(view, 'saveLastTimestamp');
+    });
+
+    afterEach(function () {
+      view.fetchRows.restore();
+      view.appendLogRows.restore();
+      view.saveLastTimestamp.restore();
+    });
+
+    it('should not call fetchRows if refreshEnd is false', function () {
+      view.set('refreshEnd', false);
+      view.refreshContent();
+      expect(view.fetchRows.called).to.be.false;
+    });
+
+    it('should call fetchRows and refresh content correctly if refreshEnd is true', function () {
+      view.set('refreshEnd', true);
+      view.set('lastLogTime', 1549533600140)
+      view.set('logRows', [Em.Object.create({
+        id: 1
+      })]);
+      view.refreshContent();
+      expect(view.fetchRows.called).to.be.true;
+      expect(view.appendLogRows.called).to.be.true;
+      expect(view.saveLastTimestamp.called).to.be.true;
+      expect(view.get('refreshEnd')).to.be.true;
+    });
+  });
+
+  describe('#saveLastTimestamp', function () {
+    it('should set 0 if no logworks or they are empty', function () {
+      view.saveLastTimestamp([]);
+      expect(view.get('lastLogTime')).to.be.equal(0);
+    });
+
+    it('should set first logtime to lastLogTime', function () {
+      view.saveLastTimestamp([Em.Object.create({logtime: 1}), Em.Object.create({logtime: 2})]);
+      expect(view.get('lastLogTime')).to.be.equal(1);
+    });
+  });
+
+  describe('#currentPage', function () {
+    it('should return start index and page size', function () {
+      view.set('selectedTailCount', 20);
+      expect(view.currentPage()).to.be.eql({
+        startIndex: 0,
+        pageSize: 20
+      });
+    });
+  });
+
+  describe('#nextPage', function() {
+    it('should increase startIndex on selectedTaiCount and return start index and page size', function() {
+      view.set('selectedTailCount', 20);
+      view.set('startIndex', 1);
+      expect(view.nextPage()).to.be.eql({
+        startIndex: 21,
+        pageSize: 20
+      });
+    });
+
+    it('should and return start index not less than 0 and page size', function() {
+      view.set('selectedTailCount', 20);
+      view.set('startIndex', -30);
+      expect(view.nextPage()).to.be.eql({
+        startIndex: 0,
+        pageSize: 20
+      });
+    });
+  });
+
+  describe('#oldestLogs', function () {
+    it('should return log rows length as start index and current page size', function () {
+      view.set('logRows', [{}, {}]);
+      view.set('selectedTailCount', 20);
+      expect(view.oldestLogs()).to.be.eql({
+        startIndex: 2,
+        pageSize: 20
+      });
+    });
+  });
+
+  describe('#startLogPolling', function () {
+    beforeEach(function () {
+      view.set('pollLogTimeoutId', null);
+    });
+
+    it('should do nothing if no pollLogs', function () {
+      view.set('pollLogs', null);
+      expect(view.get('pollLogTimeoutId')).to.be.equal(null);
+    });
+
+    it('should do nothing if state is destroyed', function () {
+      view.set('pollLogs', [{}]);
+      view.set('state', 'destroyed');
+      expect(view.get('pollLogTimeoutId')).to.be.equal(null);
+    });
+
+    it('should start polling if pollLogs are available and state is not destroyed', function () {
+      view.set('pollLogs', [{}]);
+      view.set('state', '');
+      expect(view.get('pollLogTimeoutId')).to.be.truthy;
+    });
+  });
+
+  describe('#stopLogPolling', function () {
+    beforeEach(function () {
+      sinon.stub(window, 'clearTimeout');
+    });
+
+    afterEach(function () {
+      window.clearTimeout.restore();
+    });
+
+    it('should do nothing if no pollLogs', function () {
+      view.set('pollLogs', null);
+      view.stopLogPolling();
+      expect(window.clearTimeout.called).to.be.false;
+    });
+
+    it('should do nothing if pollLogTimeoutId is null', function () {
+      view.set('pollLogs', [{}]);
+      view.set('pollLogTimeoutId', null);
+      view.stopLogPolling();
+      expect(window.clearTimeout.called).to.be.false;
+    });
+
+    it('should clear timeout if pollLogTimeoutId and pollLogs', function () {
+      view.set('pollLogs', [{}]);
+      view.set('pollLogTimeoutId', 1);
+      view.stopLogPolling();
+      expect(window.clearTimeout.called).to.be.true;
+      expect(window.clearTimeout.calledWith(1)).to.be.true;
+    });
+  });
+});
\ No newline at end of file
diff --git a/ambari-web/test/views/common/not-scrollable-textarea_test.js b/ambari-web/test/views/common/not-scrollable-textarea_test.js
new file mode 100644
index 0000000..cadd4e9
--- /dev/null
+++ b/ambari-web/test/views/common/not-scrollable-textarea_test.js
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.NotScrollableTextArea', function() {
+  var view;
+  var $ = {select: function () {}, val: function () {}, height: function () {}};
+  beforeEach(function () {
+    view = App.NotScrollableTextArea.create({});
+    sinon.stub($, 'select');
+    sinon.stub($, 'height');
+    sinon.stub(view, '$').returns($);
+  });
+
+  afterEach(function () {
+    $.select.restore();
+    $.height.restore();
+    view.$.restore();
+  });
+
+  describe('#didInsertElement', function () {
+    it('should call fitHeight and select methods', function () {
+      sinon.stub(view, 'fitHeight');
+      view.didInsertElement();
+      expect(view.fitHeight.called).to.be.true;
+      expect($.select.called).to.be.true;
+      view.fitHeight.restore();
+    });
+  });
+
+  describe('#fitHeight', function () {
+    it('should run next height', function () {
+      sinon.stub(Em.run, 'next');
+      view.set('value', 1);
+      expect(Em.run.next.called).to.be.true;
+      Em.run.next.restore();
+    });
+  });
+});
\ No newline at end of file
diff --git a/ambari-web/test/views/common/service_restart_view_test.js b/ambari-web/test/views/common/service_restart_view_test.js
new file mode 100644
index 0000000..d8e35f5
--- /dev/null
+++ b/ambari-web/test/views/common/service_restart_view_test.js
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.ServiceRestartView', function() {
+  var view;
+  beforeEach(function () {
+    view = App.ServiceRestartView.create({});
+    view.initDefaultConfigs();
+  });
+
+  describe('#initDefaultConfigs', function () {
+    it('should set correct default configs', function () {
+      expect(view.get('useRolling')).to.be.true;
+      expect(view.get('showAdvancedOptions')).to.be.false;
+      expect(view.get('showBatchRackOptions')).to.be.false;
+      expect(view.get('batchesOfHosts')).to.be.true;
+      expect(view.get('noOfHostsInBatch')).to.be.equal(10);
+      expect(view.get('batchIntervalHosts')).to.be.equal(120);
+      expect(view.get('percentRackStarted')).to.be.equal(100);
+      expect(view.get('batchIntervalRacks')).to.be.equal(120);
+      expect(view.get('isRetryChecked')).to.be.true;
+      expect(view.get('noOfRetriesPerHost')).to.be.equal(2);
+      expect(view.get('maxFailuresTolerated')).to.be.equal(10);
+      expect(view.get('maxFailuresBatch')).to.be.equal(2);
+      expect(view.get('maxFailuresRack')).to.be.equal(2);
+      expect(view.get('suppressAlerts')).to.be.true;
+      expect(view.get('pauseAfterFirst')).to.be.false;
+    });
+  });
+
+  describe('#getRestartConfigs', function () {
+    it('should return batchInterval as batchIntervalHosts if batchesOfHosts', function () {
+      var result = view.getRestartConfigs();
+      expect(result).to.be.eql(Em.Object.create({
+        batchInterval: view.get('batchIntervalHosts'),
+        maxFailures: view.get('maxFailuresTolerated'),
+        maxFailuresPerBatch: view.get('maxFailuresTolerated'),
+      }));
+    });
+
+    it('should return batchInterval as batchIntervalRacks if no batchesOfHosts', function () {
+      view.set('batchesOfHosts', false);
+      view.set('batchIntervalRacks', 95);
+      var result = view.getRestartConfigs();
+      expect(result).to.be.eql(Em.Object.create({
+        batchInterval: view.get('batchIntervalRacks'),
+        maxFailures: view.get('maxFailuresTolerated'),
+        maxFailuresPerBatch: view.get('maxFailuresTolerated'),
+      }));
+    });
+  });
+
+  describe('#getNoOfHosts', function () {
+    it('should return noOfHostsInBatch if batchesOfHosts is truthly', function () {
+      var result = view.getNoOfHosts();
+      expect(result).to.be.equal(view.get('noOfHostsInBatch'));
+    });
+
+    it('should return correct value if batchesOfHosts is falsy', function () {
+      view.set('batchesOfHosts', false);
+      view.set('componentHostRackInfoMap', {
+        test: {
+          size: function () { return 200}
+        }
+      });
+      var result = view.getNoOfHosts('test');
+      expect(result).to.be.equal(50);
+    });
+  });
+
+  describe('#toggleAdvancedOptions', function () {
+    it('should toggle showAdvancedOptions', function () {
+      view.set('showAdvancedOptions', true);
+      view.toggleAdvancedOptions();
+      expect(view.get('showAdvancedOptions')).to.be.false;
+      view.toggleAdvancedOptions();
+      expect(view.get('showAdvancedOptions')).to.be.true;
+    });
+  });
+});
\ No newline at end of file