فهرست منبع

HUE-9090 [frontend] Add tests for the reminding simple ko components

Johan Ahlen 6 سال پیش
والد
کامیت
172c5805c0
25فایلهای تغییر یافته به همراه739 افزوده شده و 9 حذف شده
  1. 9 0
      desktop/core/src/desktop/js/jest/jest.init.js
  2. 38 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.multiClusterSidebar.test.js.snap
  3. 38 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.navProperties.test.js.snap
  4. 19 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.navTags.test.js.snap
  5. 26 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.performanceGraph.test.js.snap
  6. 8 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.pollingCatalogEntriesList.test.js.snap
  7. 102 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sentryPrivileges.test.js.snap
  8. 139 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sidebar.test.js.snap
  9. 48 0
      desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sqlColumnsTable.test.js.snap
  10. 4 2
      desktop/core/src/desktop/js/ko/components/ko.multiClusterSidebar.js
  11. 28 0
      desktop/core/src/desktop/js/ko/components/ko.multiClusterSidebar.test.js
  12. 3 1
      desktop/core/src/desktop/js/ko/components/ko.navProperties.js
  13. 46 0
      desktop/core/src/desktop/js/ko/components/ko.navProperties.test.js
  14. 3 1
      desktop/core/src/desktop/js/ko/components/ko.navTags.js
  15. 46 0
      desktop/core/src/desktop/js/ko/components/ko.navTags.test.js
  16. 3 1
      desktop/core/src/desktop/js/ko/components/ko.performanceGraph.js
  17. 37 0
      desktop/core/src/desktop/js/ko/components/ko.performanceGraph.test.js
  18. 3 1
      desktop/core/src/desktop/js/ko/components/ko.pollingCatalogEntriesList.js
  19. 39 0
      desktop/core/src/desktop/js/ko/components/ko.pollingCatalogEntriesList.test.js
  20. 3 1
      desktop/core/src/desktop/js/ko/components/ko.sentryPrivileges.js
  21. 30 0
      desktop/core/src/desktop/js/ko/components/ko.sentryPrivileges.test.js
  22. 3 1
      desktop/core/src/desktop/js/ko/components/ko.sidebar.js
  23. 28 0
      desktop/core/src/desktop/js/ko/components/ko.sidebar.test.js
  24. 3 1
      desktop/core/src/desktop/js/ko/components/ko.sqlColumnsTable.js
  25. 33 0
      desktop/core/src/desktop/js/ko/components/ko.sqlColumnsTable.test.js

+ 9 - 0
desktop/core/src/desktop/js/jest/jest.init.js

@@ -22,7 +22,13 @@ import 'apps/notebook2/execution/sessionManager';
 import './jquery.setup';
 import './sqlTestUtils';
 
+import ko from 'knockout';
+import komapping from 'knockout.mapping';
+
+ko.mapping = komapping;
+
 const globalVars = {
+  ko: ko,
   AUTOCOMPLETE_TIMEOUT: 1,
   CACHEABLE_TTL: 1,
   HAS_OPTIMIZER: false,
@@ -36,6 +42,9 @@ const globalVars = {
   STATIC_URLS: {
     'impala/art/icon_impala_48.png': 'impala/art/icon_impala_48.png',
     'beeswax/art/icon_beeswax_48.png': 'beeswax/art/icon_beeswax_48.png'
+  },
+  SQL_COLUMNS_KNOWN_FACET_VALUES: {
+    type: { array: -1, boolean: -1 }
   }
 };
 

+ 38 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.multiClusterSidebar.test.js.snap

@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.multiClusterSidebar.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;hue-multi-cluster-sidebar&quot;, params: $data.params }\\"><div class=\\"sidebar-content\\" data-bind=\\"foreach: categories\\">
+    <!-- ko if: typeof label !== 'undefined' --><!-- /ko -->
+    <!-- ko foreach: items -->
+    <!-- ko if: typeof items == 'undefined' -->
+    <a role=\\"button\\" class=\\"sidebar-item\\" data-bind=\\"css: { 'active': false }, hueLink: url, attr: { 'title': label }\\" title=\\"Data Warehouse\\">
+      <span class=\\"sidebar-icon without-tooltip\\">
+        <span data-bind=\\"css: icon\\" class=\\"altus-icon altus-adb-query\\"></span>
+      </span>
+      <span class=\\"sidebar-item-name\\" data-bind=\\"text: label\\">Data Warehouse</span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: typeof items !== 'undefined' --><!-- /ko -->
+    
+    <!-- ko if: typeof items == 'undefined' -->
+    <a role=\\"button\\" class=\\"sidebar-item\\" data-bind=\\"css: { 'active': false }, hueLink: url, attr: { 'title': label }\\" title=\\"Data Science\\">
+      <span class=\\"sidebar-icon without-tooltip\\">
+        <span data-bind=\\"css: icon\\" class=\\"altus-icon altus-ds\\"></span>
+      </span>
+      <span class=\\"sidebar-item-name\\" data-bind=\\"text: label\\">Data Science</span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: typeof items !== 'undefined' --><!-- /ko -->
+    
+    <!-- ko if: typeof items == 'undefined' -->
+    <a role=\\"button\\" class=\\"sidebar-item\\" data-bind=\\"css: { 'active': false }, hueLink: url, attr: { 'title': label }\\" title=\\"Admin\\">
+      <span class=\\"sidebar-icon without-tooltip\\">
+        <span data-bind=\\"css: icon\\" class=\\"altus-icon altus-de\\"></span>
+      </span>
+      <span class=\\"sidebar-item-name\\" data-bind=\\"text: label\\">Admin</span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: typeof items !== 'undefined' --><!-- /ko -->
+    <!-- /ko -->
+  </div></div>"
+`;

+ 38 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.navProperties.test.js.snap

@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.navProperties.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;nav-properties&quot;, params: $data.params }\\"><script type=\\"text/html\\" id=\\"nav-property-edit-popover-content\\">
+    <div class=\\"hue-nav-property-edit-content\\">
+      <a href=\\"javascript: void(0);\\" class=\\"close-popover\\"><i class=\\"fa fa-times\\"></i></a>
+      <div class=\\"control-group\\" data-bind=\\"css: { 'error': keyInvalid }\\">
+        <label class=\\"control-label\\">Key</label>
+        <div class=\\"controls\\">
+          <input type=\\"text\\" placeholder=\\"Key\\" data-bind=\\"textInput: key\\">
+        </div>
+      </div>
+      <div class=\\"control-group\\" data-bind=\\"css: { 'error': valueInvalid }\\">
+        <label class=\\"control-label\\">Value</label>
+        <div class=\\"controls\\">
+          <input type=\\"text\\" placeholder=\\"Value\\" data-bind=\\"textInput: value\\">
+        </div>
+      </div>
+    </div>
+  </script><div class=\\"hue-nav-properties\\"><div data-bind=\\"hueSpinner: { spin: loading, inline: true }\\"></div></div><div class=\\"hue-nav-properties\\" data-bind=\\"click: startEdit, visibleOnHover: { selector: '.editable-inline-action' }\\">
+    <!-- ko if: !properties().length --><!-- /ko -->
+    <!-- ko foreach: properties -->
+    <div class=\\"hue-nav-property\\"><div class=\\"hue-nav-property-key\\" data-bind=\\"text: key, attr: { 'title': key }\\" title=\\"testProp\\">testProp</div><div class=\\"hue-nav-property-value\\" data-bind=\\"text: value, attr: { 'title': value }\\" title=\\"testVal\\">testVal</div></div>
+    <!-- /ko -->
+    <div class=\\"editable-inline-action\\" title=\\"Edit\\"><a href=\\"javascript: void(0);\\" data-bind=\\"click: startEdit\\"><i class=\\"fa fa-pencil\\"></i></a></div>
+  </div><div class=\\"hue-nav-properties\\">
+    <!-- ko foreach: properties -->
+    <div class=\\"hue-nav-property\\" data-bind=\\"tooltip: { placement: 'bottom' }, attr: { title: title }\\"><div class=\\"hue-nav-property-key\\" data-bind=\\"text: key\\">testProp</div><div class=\\"hue-nav-property-value\\" data-bind=\\"text: value\\">testVal</div></div>
+    <!-- /ko -->
+  </div><div class=\\"hue-nav-properties hue-nav-properties-edit\\">
+    <!-- ko foreach: editProperties --><!-- /ko -->
+    <div class=\\"hue-nav-property-add\\"><a href=\\"javascript: void(0);\\" title=\\"Add\\" data-bind=\\"click: addProperty\\"><i class=\\"fa fa-plus\\"></i></a></div>
+    <div class=\\"hue-nav-properties-edit-actions\\">
+      <a href=\\"javascript: void(0);\\" title=\\"Save\\" data-bind=\\"click: saveEdit\\"><i class=\\"fa fa-check\\"></i></a>
+      <a href=\\"javascript: void(0);\\" title=\\"Cancel\\" data-bind=\\"click: cancelEdit\\"><i class=\\"fa fa-close\\"></i></a>
+    </div>
+  </div></div>"
+`;

+ 19 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.navTags.test.js.snap

@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.navTags.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;nav-tags&quot;, params: $data.params }\\"><div style=\\"width: 100%; height: 20px; left: 6px; position: relative;\\" data-bind=\\"hueSpinner: { spin: loading }\\"></div><div class=\\"hue-tags read-only-tags\\" style=\\"width: 100%\\" data-bind=\\"ifnot: loading, css: { 'read-only-tags': readOnly }\\">
+   <textarea style=\\"width: 100%; display: none;\\" data-bind=\\"tagEditor: {
+      emptyPlaceholder: 'Add tags...',
+      readOnly: readOnly,
+      hasErrors: hasErrors,
+      errorMessage: 'Tags could not be loaded.',
+      setTags: currentTags,
+      overflowEllipsis: overflowEllipsis,
+      onSave: saveTags.bind($data),
+      validRegExp: '^[a-zA-z0-9_-]{1,50}$',
+      invalidMessage: 'Tags can only contain 1 to 50 alphanumeric characters, _ or -.',
+      load: getSelectizeTags
+    }\\"></textarea><div style=\\"width: 100%\\" class=\\"selectize-control selectize-read-only multi\\" title=\\"testTag\\"><div class=\\"selectize-input items not-full has-options has-items\\"><div>testTag</div></div></div>
+   <div class=\\"selectize-error\\" style=\\"display: none;\\"><i class=\\"fa fa-exclamation-triangle\\"></i> <span class=\\"message\\"></span></div>
+  </div></div>"
+`;

+ 26 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.performanceGraph.test.js.snap

@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.performanceGraph.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;performance-graph&quot;, params: $data.params }\\"><script type=\\"text/html\\" id=\\"performance-graph-d3-template\\">
+  <div style=\\"position:relative;\\"
+      data-bind=\\"attr: { 'id': id }, style: { height: graphHeight + 'px', width: graphWidth + 'px' }\\"></div>
+</script><div style=\\"position: relative\\" data-bind=\\"template: { afterRender: componentRendered }\\">
+  <div style=\\"padding: 10px; float: right;\\" data-bind=\\"foreach: availableGranularities\\">
+    <!-- ko if: $data === $parent.selectedGranularity() -->
+    <span class=\\"margin-right-20\\" data-bind=\\"text: label\\">1 hour</span>
+    <!-- /ko -->
+    <!-- ko if: $data !== $parent.selectedGranularity() --><!-- /ko -->
+  
+    <!-- ko if: $data === $parent.selectedGranularity() --><!-- /ko -->
+    <!-- ko if: $data !== $parent.selectedGranularity() -->
+    <a class=\\"margin-right-20\\" href=\\"javascript: void(0);\\" data-bind=\\"text: label, click: function () { $parent.selectedGranularity($data); }\\">8 hours</a>
+    <!-- /ko -->
+  
+    <!-- ko if: $data === $parent.selectedGranularity() --><!-- /ko -->
+    <!-- ko if: $data !== $parent.selectedGranularity() -->
+    <a class=\\"margin-right-20\\" href=\\"javascript: void(0);\\" data-bind=\\"text: label, click: function () { $parent.selectedGranularity($data); }\\">1 week</a>
+    <!-- /ko -->
+  </div>
+  <div style=\\"clear: both; height: 500px; width: 1000px;\\" class=\\"performance-graph\\" data-bind=\\"style: { height: graphHeight + 'px', width: graphWidth + 'px' }\\"><svg width=\\"1000\\" height=\\"500\\"><text class=\\"loading-text\\" text-anchor=\\"middle\\" dy=\\"250\\" dx=\\"500\\">Loading metrics...</text></svg></div>
+</div></div>"
+`;

+ 8 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.pollingCatalogEntriesList.test.js.snap

@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.pollingCatalogEntriesList.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;polling-catalog-entries-list&quot;, params: $data.params }\\"><div>
+    <!-- ko hueSpinner: { spin: !catalogEntryExists(), inline: true } --><!-- /ko -->
+    <!-- ko if: catalogEntryExists --><!-- /ko -->
+  </div></div>"
+`;

+ 102 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sentryPrivileges.test.js.snap

@@ -0,0 +1,102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.sentryPrivileges.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;hue-sentry-privileges&quot;, params: $data.params }\\"><script type=\\"text/html\\" id=\\"sentry-privileges-component-role\\">
+    <div class=\\"acl-block-title\\">
+      <i class=\\"fa fa-cube muted\\"></i> <a class=\\"pointer\\" data-bind=\\"click: function(){  $parents[0].showRole($data); }\\"><span data-bind=\\"text: name\\"></span></a>
+    </div>
+    <div data-bind=\\"template: { name: 'sentry-privileges-component-privilege', foreach: privilegesForView }\\"></div>
+    <div class=\\"acl-block acl-actions\\">
+      <span class=\\"pointer\\" data-bind=\\"visible: privilegesForViewTo() < privileges().length, click: function(){ privilegesForViewTo(privilegesForViewTo() + 50) }\\" title=\\"Show 50 more...\\"><i class=\\"fa fa-ellipsis-h\\"></i></span>
+      <span class=\\"pointer\\" data-bind=\\"click: addPrivilege, visible: $parents[0].isSentryAdmin && !$parents[0].readOnly\\" title=\\"Add privilege\\"><i class=\\"fa fa-plus\\"></i></span>
+      <span class=\\"pointer\\" data-bind=\\"click: function() { $parents[0].listSentryPrivilegesByAuthorizable() }, visible: privilegesChanged().length > 0\\" title=\\"Undo\\"> &nbsp; <i class=\\"fa fa-undo\\"></i></span>
+      <span class=\\"pointer\\" data-bind=\\"click: function() { $parents[0].deletePrivilegeModal($data) }, visible: privilegesChanged().length > 0\\" title=\\"Save\\"> &nbsp; <i class=\\"fa fa-save\\"></i></span>
+    </div>
+  </script><script type=\\"text/html\\" id=\\"sentry-privileges-component-privilege\\">
+    <div data-bind=\\"visible: status() != 'deleted' && status() != 'alreadydeleted'\\" class=\\"acl-block acl-block-airy\\">
+      <!-- ko if: editing -->
+      <div class=\\"pull-right privilege-actions\\" data-bind=\\"visible: (grantOption() || $parents[1].isSentryAdmin) && !$parents[1].readOnly\\">
+        <a class=\\"pointer\\" style=\\"margin-right: 4px\\" data-bind=\\"click: function() { if (editing()) { editing(false); }}\\"><i class=\\"fa fa-eye\\"></i></a>
+        <a class=\\"pointer\\" style=\\"margin-right: 4px\\" data-bind=\\"click: remove\\"><i class=\\"fa fa-times\\"></i></a>
+      </div>
+    
+      <div class=\\"inline-block\\" style=\\"vertical-align: middle\\">
+        <a class=\\"pointer\\" style=\\"padding-top: 4px\\" data-bind=\\"click: function(){ privilegeType('db'); action($parents[1].availableActions(privilegeScope())[0]) }\\">
+          <i class=\\"fa fa-fw fa-1halfx muted\\" data-bind=\\"css: {'fa-circle-o': privilegeType() != 'db' , 'fa-check-circle-o': privilegeType() == 'db'}\\"></i>
+        </a>
+      </div>
+      <input type=\\"text\\" data-bind=\\"hiveChooser: $data.path, enable: privilegeType() == 'db', apiHelperUser: 'foo', apiHelperType: 'hive'\\" placeholder=\\"dbName.tableName <CTRL+SPACE>\\">
+    
+      <div class=\\"inline-block\\" style=\\"vertical-align: middle\\">
+        <a class=\\"pointer\\" style=\\"padding-top: 4px\\" data-bind=\\"click: function(){ privilegeType('uri'); action('ALL'); }\\">
+          <i class=\\"fa fa-fw fa-1halfx muted\\" data-bind=\\"css: {'fa-circle-o': privilegeType() != 'uri' , 'fa-check-circle-o': privilegeType() == 'uri'}\\"></i>
+        </a>
+      </div>
+      <input type=\\"text\\" data-bind=\\"filechooser: $data.URI, enable: privilegeType() == 'uri', valueUpdate: 'afterkeydown'\\" placeholder=\\"URI\\">
+    
+      <select data-bind=\\"options: $parents[1].availableActions(privilegeScope()), value: $data.action, enable: (privilegeType() == 'db')\\" style=\\"width: 100px; margin-bottom: 0\\"></select>
+    
+      <div class=\\"margin-top-10\\">
+        <label class=\\"checkbox pull-left\\"><input type=\\"checkbox\\" data-bind=\\"checked: grantOption\\"> With grant</label>
+        <div class=\\"inline-block margin-left-10\\" style=\\"margin-top: 6px\\">
+          <a class=\\"pointer showAdvanced\\" data-bind=\\"click: function(){ showAdvanced(true); }, visible: !showAdvanced()\\"><i class=\\"fa fa-cog\\"></i> Show advanced</a>
+          <a class=\\"pointer showAdvanced\\" data-bind=\\"click: function(){ showAdvanced(false); }, visible: showAdvanced()\\"><i class=\\"fa fa-cog\\"></i> Hide advanced</a>
+        </div>
+        <div class=\\"clearfix\\"></div>
+      </div>
+    
+      <div class=\\"acl-block-section\\" data-bind=\\"visible: showAdvanced\\" style=\\"margin-top: 0\\">
+        Server <input type=\\"text\\" data-bind=\\"value: serverName\\" placeholder=\\"serverName\\" style=\\"margin-left: 6px\\">
+      </div>
+      <!-- /ko -->
+  
+      <!-- ko ifnot: editing -->
+      <div class=\\"pull-right privilege-actions\\" data-bind=\\"visible: (grantOption() || $parents[1].isSentryAdmin) && !$parents[1].readOnly\\">
+        <a title=\\"Edit this privilege\\" class=\\"pointer\\" style=\\"margin-right: 4px\\" data-bind=\\"visible: $parents[1].isSentryAdmin, click: function() { if (! editing()) { editing(true); }}\\"><i class=\\"fa fa-pencil\\"></i></a>
+        <a title=\\"Delete this privilege\\" class=\\"pointer\\" style=\\"margin-right: 4px\\" data-bind=\\"visible: $parents[1].isSentryAdmin, click: remove\\"><i class=\\"fa fa-times\\"></i></a>
+      </div>
+  
+      <span class=\\"muted\\" data-bind=\\"text: privilegeScope, attr: {title: moment(timestamp()).fromNow()}\\"></span>
+      <!-- ko if: grantOption -->
+        <i class=\\"fa fa-unlock muted\\" title=\\"With grant option\\"></i>
+      <!-- /ko -->
+      <span data-bind=\\"visible: metastorePath() != '' && privilegeType() == 'db'\\">
+        <a data-bind=\\"hueLink: metastorePath()\\" class=\\"muted\\" style=\\"margin-left: 4px\\" title=\\"Open in Table Browser...\\"><i class=\\"fa fa-external-link\\"></i></a>
+      </span>
+      <br/>
+  
+      server=<span data-bind=\\"text: serverName\\"></span>
+  
+      <!-- ko if: privilegeType() == 'db' -->
+      <span data-bind=\\"visible: dbName\\">
+        <i class=\\"fa fa-long-arrow-right\\"></i> db=<a class=\\"pointer\\" data-bind=\\"click: function(){ $parents[1].linkToBrowse(dbName()) }\\" title=\\"Browse db privileges\\"><span data-bind=\\"text: dbName\\"></span></a>
+      </span>
+      <span data-bind=\\"visible: tableName\\">
+        <i class=\\"fa fa-long-arrow-right\\"></i> table=<a class=\\"pointer\\" data-bind=\\"click: function(){ $parents[1].linkToBrowse(dbName() + '.' + tableName()) }\\" title=\\"Browse table privileges\\"><span data-bind=\\"text: tableName\\"></span></a>
+      </span>
+      <span data-bind=\\"visible: columnName\\">
+        <i class=\\"fa fa-long-arrow-right\\"></i> column=<a class=\\"pointer\\" data-bind=\\"click: function(){ $parents[1].linkToBrowse(dbName() + '.' + tableName() + '.' + columnName()) }\\" title=\\"Browse column privileges\\"><span data-bind=\\"text: columnName\\"></span></a>
+      </span>
+      <!-- /ko -->
+  
+      <!-- ko if: privilegeType() == 'uri' -->
+      <i class=\\"fa fa-long-arrow-right\\"></i> <i class=\\"fa fa-file-o\\"></i> <i class=\\"fa fa-long-arrow-right\\"></i> <a data-bind=\\"hueLink: '/filebrowser/view=/' + URI().split('/')[3]\\"><span data-bind=\\"text: URI\\"></span></a>
+      <!-- /ko -->
+  
+      <i class=\\"fa fa-long-arrow-right\\"></i> action=<span data-bind=\\"text: action\\"></span>
+      <!-- /ko -->
+    </div>
+  </script><div data-bind=\\"template: { name: 'sentry-privileges-component-role', foreach: roles }\\"></div><em>No privileges found for the selected object.</em><div id=\\"sentryPrivilegesComponentDeletePrivilegeModal\\" class=\\"modal hide fade in\\" role=\\"dialog\\">
+    <div class=\\"modal-header\\">
+      <button type=\\"button\\" class=\\"close\\" data-dismiss=\\"modal\\" aria-label=\\"Close\\"><span aria-hidden=\\"true\\">×</span></button>
+      <h2 class=\\"modal-title\\">Confirm the deletion?</h2>
+    </div>
+    <div class=\\"modal-body\\">
+      Sentry will recursively delete the SERVER or DATABASE privileges you marked for deletion.
+    </div>
+    <div class=\\"modal-footer\\">
+      <button class=\\"btn\\" data-dismiss=\\"modal\\" aria-hidden=\\"true\\">Cancel</button>
+      <button data-loading-text=\\"Deleting...\\" class=\\"btn btn-danger\\" data-bind=\\"click: function() { roleToUpdate().savePrivileges(roleToUpdate()); }\\">Yes, delete</button>
+    </div>
+  </div></div>"
+`;

+ 139 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sidebar.test.js.snap

@@ -0,0 +1,139 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.sidebar.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;hue-sidebar&quot;, params: $data.params }\\" class=\\"collapsed\\"><script type=\\"text/html\\" id=\\"sidebar-inner-item\\">
+    <!-- ko if: iconHtml -->
+    <div class=\\"icon\\" data-bind=\\"html: iconHtml\\"></div><span data-bind=\\"text: displayName\\"></span>
+    <!-- /ko -->
+    <!-- ko ifnot: iconHtml -->
+    <div class=\\"icon\\" data-bind=\\"hueAppIcon: icon\\"></div><span data-bind=\\"text: displayName\\"></span>
+    <!-- /ko -->
+  </script><script type=\\"text/html\\" id=\\"sidebar-item\\">
+    <div class=\\"item-wrapper\\" data-bind=\\"css: itemClass\\">
+      <!-- ko if: click -->
+      <a href=\\"javascript: void(0);\\" data-bind=\\"click: click, attr: { 'aria-label': displayName, 'data-tooltip': displayName }, css: { 'active': active }\\" class=\\"item\\">
+        <!-- ko template: 'sidebar-inner-item' --><!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: click -->
+      <a href=\\"javascript: void(0);\\" data-bind=\\"hueLink: url, publish: 'hue.sidebar.update.active', attr: { 'aria-label': displayName, 'data-tooltip': displayName }, css: { 'active': active }\\" class=\\"item\\">
+        <!-- ko template: 'sidebar-inner-item' --><!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko if: subMenuTemplate -->
+      <!-- ko template: subMenuTemplate --><!-- /ko -->
+      <!-- /ko -->
+    </div>
+  </script><script type=\\"text/html\\" id=\\"user-sub-menu-template\\">
+    <div class=\\"sidebar-menu user-menu\\" data-bind=\\"css: { 'open' : $component.userMenuOpen }\\">
+      <div class=\\"menu\\">
+        <div class=\\"menu-header\\">
+          <div class=\\"user-icon\\" style=\\"background-color: #fff\\">F</div>
+          <div>
+            <div>foo</div>
+          </div>
+        </div>
+        <ul class=\\"sidebar-nav-list\\">
+          <!-- ko if: window.USER_VIEW_EDIT_USER_ENABLED -->
+          <li><a href=\\"javascript:void(0);\\" data-bind=\\"
+              hueLink: '/useradmin/users/edit/foo',
+              attr: { 
+                'title': window.IS_LDAP_SETUP ? 'View Profile' : 'Edit Profile'
+              }
+            \\">My Profile</a></li>
+          <!-- /ko -->
+          <!-- ko if: window.USER_IS_ADMIN -->
+          <li><a href=\\"javascript: void(0);\\" data-bind=\\"hueLink: '/useradmin/users/'\\">Manage Users</a></li>
+          <li><a href=\\"javascript: void(0);\\" data-bind=\\"hueLink: '/about/'\\">Administration</a></li>
+          <!-- /ko -->
+          <li><a href=\\"javascript: void(0);\\" data-bind=\\"hueLink: '/accounts/logout'\\" title=\\"Sign out\\" >Sign out</a></li>
+        </ul>
+      </div>
+    </div>
+  </script><script type=\\"text/html\\" id=\\"support-sub-menu-template\\">
+    <div class=\\"sidebar-menu support-menu\\" data-bind=\\"css: { 'open' : $component.supportMenuOpen }\\">
+      <div class=\\"menu\\">
+        <ul class=\\"sidebar-nav-list\\">
+          <li><a href=\\"https://docs.gethue.com\\" target=\\"_blank\\">Documentation</a></li>
+          <li><a href=\\"javascript:void(0)\\" data-bind=\\"publish: 'show.welcome.tour'\\">Welcome Tour</a></li>
+          <li><a href=\\"http://gethue.com\\" target=\\"_blank\\">Gethue.com</a></li>
+        </ul>
+      </div>
+    </div>
+  </script><div class=\\"hue-sidebar-header\\" data-bind=\\"css: { 'hue-sidebar-custom-logo' : window.CUSTOM_LOGO }\\">
+    <a data-bind=\\"hueLink: '/home/'\\" href=\\"javascript: void(0);\\" title=\\"Documents\\">
+      <div class=\\"hue-sidebar-logo\\"><svg><use xlink:href=\\"#hi-sidebar-logo\\"></use></svg></div>
+    </a>
+  </div><div class=\\"hue-sidebar-body\\">
+    <!-- ko foreach: items --><!-- /ko -->
+  </div><div class=\\"hue-sidebar-footer\\">
+    <!-- ko foreach: footerItems -->
+    <!-- ko template: { name: 'sidebar-item' } -->
+    <div class=\\"item-wrapper\\" data-bind=\\"css: itemClass\\">
+      <!-- ko if: click -->
+      <a href=\\"javascript: void(0);\\" data-bind=\\"click: click, attr: { 'aria-label': displayName, 'data-tooltip': displayName }, css: { 'active': active }\\" class=\\"item\\" aria-label=\\"Support\\" data-tooltip=\\"Support\\">
+        <!-- ko template: 'sidebar-inner-item' -->
+    <!-- ko if: iconHtml --><!-- /ko -->
+    <!-- ko ifnot: iconHtml -->
+    <div class=\\"icon\\" data-bind=\\"hueAppIcon: icon\\"></div><span data-bind=\\"text: displayName\\">Support</span>
+    <!-- /ko -->
+  <!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: click --><!-- /ko -->
+      <!-- ko if: subMenuTemplate -->
+      <!-- ko template: subMenuTemplate -->
+    <div class=\\"sidebar-menu support-menu\\" data-bind=\\"css: { 'open' : $component.supportMenuOpen }\\">
+      <div class=\\"menu\\">
+        <ul class=\\"sidebar-nav-list\\">
+          <li><a href=\\"https://docs.gethue.com\\" target=\\"_blank\\">Documentation</a></li>
+          <li><a href=\\"javascript:void(0)\\" data-bind=\\"publish: 'show.welcome.tour'\\">Welcome Tour</a></li>
+          <li><a href=\\"http://gethue.com\\" target=\\"_blank\\">Gethue.com</a></li>
+        </ul>
+      </div>
+    </div>
+  <!-- /ko -->
+      <!-- /ko -->
+    </div>
+  <!-- /ko -->
+    
+    <!-- ko template: { name: 'sidebar-item' } -->
+    <div class=\\"item-wrapper shepherd-user-menu\\" data-bind=\\"css: itemClass\\">
+      <!-- ko if: click -->
+      <a href=\\"javascript: void(0);\\" data-bind=\\"click: click, attr: { 'aria-label': displayName, 'data-tooltip': displayName }, css: { 'active': active }\\" class=\\"item\\" aria-label=\\"foo\\" data-tooltip=\\"foo\\">
+        <!-- ko template: 'sidebar-inner-item' -->
+    <!-- ko if: iconHtml -->
+    <div class=\\"icon\\" data-bind=\\"html: iconHtml\\"><div class=\\"user-icon\\">F</div></div><span data-bind=\\"text: displayName\\">foo</span>
+    <!-- /ko -->
+    <!-- ko ifnot: iconHtml --><!-- /ko -->
+  <!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: click --><!-- /ko -->
+      <!-- ko if: subMenuTemplate -->
+      <!-- ko template: subMenuTemplate -->
+    <div class=\\"sidebar-menu user-menu\\" data-bind=\\"css: { 'open' : $component.userMenuOpen }\\">
+      <div class=\\"menu\\">
+        <div class=\\"menu-header\\">
+          <div class=\\"user-icon\\" style=\\"background-color: #fff\\">F</div>
+          <div>
+            <div>foo</div>
+          </div>
+        </div>
+        <ul class=\\"sidebar-nav-list\\">
+          <!-- ko if: window.USER_VIEW_EDIT_USER_ENABLED --><!-- /ko -->
+          <!-- ko if: window.USER_IS_ADMIN --><!-- /ko -->
+          <li><a href=\\"javascript: void(0);\\" data-bind=\\"hueLink: '/accounts/logout'\\" title=\\"Sign out\\">Sign out</a></li>
+        </ul>
+      </div>
+    </div>
+  <!-- /ko -->
+      <!-- /ko -->
+    </div>
+  <!-- /ko -->
+    <!-- /ko -->
+    <a class=\\"hue-sidebar-trigger\\" data-bind=\\"toggle: collapsed\\">
+      <svg><use xlink:href=\\"#hi-collapse-nav\\"></use></svg>
+    </a>
+  </div></div>"
+`;

+ 48 - 0
desktop/core/src/desktop/js/ko/components/__snapshots__/ko.sqlColumnsTable.test.js.snap

@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.sqlColumnsTable.js should render component 1`] = `
+"<div data-bind=\\"descendantsComplete: $data.descendantsComplete.bind($data), component: { name: &quot;sql-columns-table&quot;, params: $data.params }\\"><div class=\\"context-popover-flex\\">
+      <div class=\\"context-popover-flex-header\\">
+        <div class=\\"context-popover-header\\" style=\\"display:inline-block;\\">Columns (<span data-bind=\\"text: filteredColumns().length\\">0</span>)</div>
+        <div class=\\"context-popover-inline-autocomplete\\">
+          <div class=\\"context-popover-asterisk-filter\\">
+            <!-- ko component: {
+              name: 'inline-autocomplete',
+              params: {
+                querySpec: querySpec,
+                facets: Object.keys(SQL_COLUMNS_KNOWN_FACET_VALUES),
+                knownFacetValues: SQL_COLUMNS_KNOWN_FACET_VALUES,
+                autocompleteFromEntries: autocompleteFromEntries
+              }
+            } --><div class=\\"inline-autocomp-container\\">
+    <div>
+      <!-- ko if: showMagnify --><!-- /ko-->
+      <form autocomplete=\\"off\\">
+        <input class=\\"inline-autocomp-input\\" autocorrect=\\"off\\" autocomplete=\\"do-not-autocomplete\\" autocapitalize=\\"off\\" spellcheck=\\"false\\" type=\\"text\\" data-bind=\\"
+          attr: { 'placeHolder' : hasFocus() ? '' : placeHolder },
+          textInput: searchInput,
+          hasFocus: hasFocus,
+          clearable: { value: searchInput, onClear: onClear },
+          css: { 'inline-autocomp-magnify-input': showMagnify }\\" placeholder=\\"Filter...\\">
+        <input class=\\"inline-autocomp-autocomp\\" disabled=\\"\\" type=\\"text\\" autocomplete=\\"do-not-autocomplete\\" data-bind=\\"
+          value: inlineAutocomplete,
+          css: { 'inline-autocomp-magnify-input': showMagnify }\\">
+      </form>
+    </div>
+  </div><div class=\\"hue-drop-down-container hue-drop-down-fixed\\" data-bind=\\"event: { 'mousedown': facetDropDownMouseDown }, css: { 'open' : facetDropDownVisible() }, dropDownKeyUp: { onEsc: facetDropDownOnEsc, onEnter: facetDropDownOnEnter, onSelected: facetDropDownOnSelected, dropDownVisible: facetDropDownVisible }\\">
+    <div class=\\"dropdown-menu\\" style=\\"overflow-y: auto; display: none;\\" data-bind=\\"visible: facetSuggestions().length > 0\\">
+      <ul class=\\"hue-inner-drop-down\\" data-bind=\\"foreach: facetSuggestions\\"></ul>
+    </div>
+  </div><!-- /ko -->
+          </div>
+        </div>
+      </div>
+      <div class=\\"context-popover-flex-fill sql-columns-table\\" style=\\"position:relative; height: 100%; overflow-y: auto;\\">
+        <div style=\\"position: relative; width: 100%; height: 0px;\\" class=\\"foreach-wrapper\\"><table id=\\"sqlColumnsTable\\" style=\\"width: 100%; position: absolute; top: 0px;\\" class=\\"table table-condensed table-nowrap\\">
+          <!-- ko if: filteredColumns().length !== 0 --><!-- /ko -->
+          <tbody data-bind=\\"foreachVisible: { data: filteredColumns, minHeight: 29, container: '.sql-columns-table', pubSubDispose: 'context.popover.dispose' }\\"></tbody>
+        </table></div>
+        <div class=\\"context-popover-empty-columns\\" data-bind=\\"visible: filteredColumns().length === 0\\">No columns found</div>
+      </div>
+    </div></div>"
+`;

+ 4 - 2
desktop/core/src/desktop/js/ko/components/ko.multiClusterSidebar.js

@@ -21,6 +21,8 @@ import componentUtils from './componentUtils';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 
+export const NAME = 'hue-multi-cluster-sidebar';
+
 const TEMPLATE = `
   <div class="sidebar-content" data-bind="foreach: categories">
     <!-- ko if: typeof label !== 'undefined' -->
@@ -28,7 +30,7 @@ const TEMPLATE = `
     <!-- /ko -->
     <!-- ko foreach: items -->
     <!-- ko if: typeof items == 'undefined' -->
-    <a role="button" class="sidebar-item" data-bind="css: { 'active': false }, hueLink: url, attr: { 'title': label">
+    <a role="button" class="sidebar-item" data-bind="css: { 'active': false }, hueLink: url, attr: { 'title': label }">
       <span class="sidebar-icon without-tooltip">
         <span data-bind="css: icon"></span>
       </span>
@@ -131,4 +133,4 @@ class MultiClusterSidebar {
   }
 }
 
-componentUtils.registerComponent('hue-multi-cluster-sidebar', MultiClusterSidebar, TEMPLATE);
+componentUtils.registerComponent(NAME, MultiClusterSidebar, TEMPLATE);

+ 28 - 0
desktop/core/src/desktop/js/ko/components/ko.multiClusterSidebar.test.js

@@ -0,0 +1,28 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.multiClusterSidebar';
+
+describe('ko.multiClusterSidebar.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {});
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.navProperties.js

@@ -21,6 +21,8 @@ import componentUtils from './componentUtils';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 
+export const NAME = 'nav-properties';
+
 const TEMPLATE = `
   <script type="text/html" id="nav-property-edit-popover-content">
     <div class="hue-nav-property-edit-content">
@@ -320,7 +322,7 @@ class NavProperties {
 }
 
 componentUtils.registerComponent(
-  'nav-properties',
+  NAME,
   {
     createViewModel: function(params, componentInfo) {
       return new NavProperties(params, componentInfo.element);

+ 46 - 0
desktop/core/src/desktop/js/ko/components/ko.navProperties.test.js

@@ -0,0 +1,46 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.navProperties';
+import $ from 'jquery';
+
+describe('ko.navProperties.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {
+      catalogEntry: {
+        isField: () => true,
+        isComplex: () => false,
+        getChildren: () => $.Deferred().resolve([]),
+        getSample: () => $.Deferred().reject(),
+        loadNavigatorMetaForChildren: () => $.Deferred().reject(),
+        loadNavOptPopularityForChildren: () => $.Deferred().reject(),
+        isTableOrView: () => false,
+        getSourceType: () => 'impala',
+        getNavigatorMeta: () =>
+          $.Deferred().resolve({
+            properties: {
+              testProp: 'testVal'
+            }
+          })
+      }
+    });
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.navTags.js

@@ -22,6 +22,8 @@ import dataCatalog from 'catalog/dataCatalog';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 
+export const NAME = 'nav-tags';
+
 const TEMPLATE = `
   <!-- ko if: loading -->
   <div style="width: 100%; height: 20px; left: 6px; position: relative;" data-bind="hueSpinner: { spin: loading }"></div>
@@ -174,4 +176,4 @@ class NavTags {
   }
 }
 
-componentUtils.registerComponent('nav-tags', NavTags, TEMPLATE);
+componentUtils.registerComponent(NAME, NavTags, TEMPLATE);

+ 46 - 0
desktop/core/src/desktop/js/ko/components/ko.navTags.test.js

@@ -0,0 +1,46 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.navTags';
+import $ from 'jquery';
+
+import 'ko/bindings/ko.tagEditor';
+
+describe('ko.navTags.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {
+      catalogEntry: {
+        isField: () => true,
+        isComplex: () => false,
+        getChildren: () => $.Deferred().resolve([]),
+        getSample: () => $.Deferred().reject(),
+        loadNavigatorMetaForChildren: () => $.Deferred().reject(),
+        loadNavOptPopularityForChildren: () => $.Deferred().reject(),
+        isTableOrView: () => false,
+        getSourceType: () => 'impala',
+        getNavigatorMeta: () =>
+          $.Deferred().resolve({
+            tags: ['testTag']
+          })
+      }
+    });
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.performanceGraph.js

@@ -21,6 +21,8 @@ import apiHelper from 'api/apiHelper';
 import componentUtils from './componentUtils';
 import I18n from 'utils/i18n';
 
+export const NAME = 'performance-graph';
+
 const TEMPLATE = `
 <script type="text/html" id="performance-graph-d3-template">
   <div style="position:relative;"
@@ -793,4 +795,4 @@ class PerformanceGraph {
   }
 }
 
-componentUtils.registerComponent('performance-graph', PerformanceGraph, TEMPLATE);
+componentUtils.registerComponent(NAME, PerformanceGraph, TEMPLATE);

+ 37 - 0
desktop/core/src/desktop/js/ko/components/ko.performanceGraph.test.js

@@ -0,0 +1,37 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+
+import ApiHelper from 'api/apiHelper';
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.performanceGraph';
+
+describe('ko.performanceGraph.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    jest.spyOn(ApiHelper, 'fetchResourceStats').mockImplementation(() =>
+      $.Deferred()
+        .reject()
+        .promise()
+    );
+
+    const element = await setup.renderComponent(NAME, {});
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.pollingCatalogEntriesList.js

@@ -21,6 +21,8 @@ import 'ko/components/ko.catalogEntriesList';
 import componentUtils from './componentUtils';
 import dataCatalog from 'catalog/dataCatalog';
 
+export const NAME = 'polling-catalog-entries-list';
+
 const TEMPLATE = `
   <div>
     <!-- ko hueSpinner: { spin: !catalogEntryExists(), inline: true } --><!-- /ko -->
@@ -162,7 +164,7 @@ class PollingCatalogEntriesList {
 }
 
 componentUtils.registerComponent(
-  'polling-catalog-entries-list',
+  NAME,
   PollingCatalogEntriesList,
   TEMPLATE
 );

+ 39 - 0
desktop/core/src/desktop/js/ko/components/ko.pollingCatalogEntriesList.test.js

@@ -0,0 +1,39 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.pollingCatalogEntriesList';
+import $ from 'jquery';
+import ApiHelper from 'api/apiHelper';
+
+describe('ko.pollingCatalogEntriesList.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    jest.spyOn(ApiHelper, 'fetchSourceMetadata').mockImplementation(() =>
+      $.Deferred()
+        .reject()
+        .promise()
+    );
+
+    const element = await setup.renderComponent(NAME, {
+      sourceType: 'impala',
+      namespace: { id: 'namespaceId' }
+    });
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.sentryPrivileges.js

@@ -21,6 +21,8 @@ import componentUtils from './componentUtils';
 import I18n from 'utils/i18n';
 import hueUtils from 'utils/hueUtils';
 
+export const NAME = 'hue-sentry-privileges';
+
 const TEMPLATE = `
   <script type="text/html" id="sentry-privileges-component-role">
     <div class="acl-block-title">
@@ -765,4 +767,4 @@ class SentryPrivilegesViewModel {
   }
 }
 
-componentUtils.registerComponent('hue-sentry-privileges', SentryPrivilegesViewModel, TEMPLATE);
+componentUtils.registerComponent(NAME, SentryPrivilegesViewModel, TEMPLATE);

+ 30 - 0
desktop/core/src/desktop/js/ko/components/ko.sentryPrivileges.test.js

@@ -0,0 +1,30 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.sentryPrivileges';
+
+describe('ko.sentryPrivileges.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {
+      path: 'foo'
+    });
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.sidebar.js

@@ -22,6 +22,8 @@ import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 
+export const NAME = 'hue-sidebar';
+
 // prettier-ignore
 const TEMPLATE = `
   <script type="text/html" id="sidebar-inner-item">
@@ -387,7 +389,7 @@ class Sidebar {
 }
 
 componentUtils.registerComponent(
-  'hue-sidebar',
+  NAME,
   {
     createViewModel: function(params, componentInfo) {
       return new Sidebar(params, componentInfo.element);

+ 28 - 0
desktop/core/src/desktop/js/ko/components/ko.sidebar.test.js

@@ -0,0 +1,28 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.sidebar';
+
+describe('ko.sidebar.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {});
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});

+ 3 - 1
desktop/core/src/desktop/js/ko/components/ko.sqlColumnsTable.js

@@ -19,6 +19,8 @@ import ko from 'knockout';
 import componentUtils from './componentUtils';
 import I18n from 'utils/i18n';
 
+export const NAME = 'sql-columns-table';
+
 const TEMPLATE = `
     <div class="context-popover-flex">
       <div class="context-popover-flex-header">
@@ -145,4 +147,4 @@ class SqlColumnsTable {
   }
 }
 
-componentUtils.registerComponent('sql-columns-table', SqlColumnsTable, TEMPLATE);
+componentUtils.registerComponent(NAME, SqlColumnsTable, TEMPLATE);

+ 33 - 0
desktop/core/src/desktop/js/ko/components/ko.sqlColumnsTable.test.js

@@ -0,0 +1,33 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import { koSetup } from 'jest/koTestUtils';
+import { NAME } from './ko.sqlColumnsTable';
+
+import 'ko/components/ko.inlineAutocomplete';
+import 'ko/bindings/ko.foreachVisible';
+
+describe('ko.sqlColumnsTable.js', () => {
+  const setup = koSetup();
+
+  it('should render component', async () => {
+    const element = await setup.renderComponent(NAME, {
+      columns: []
+    });
+
+    expect(element.innerHTML).toMatchSnapshot();
+  });
+});