Browse Source

AMBARI-7927. Slider view: Integrate unit test with maven and doc the commands to run unit test. (onechiporenko)

Oleg Nechiporenko 10 years ago
parent
commit
f89a0b9bb5

+ 1 - 1
contrib/views/slider/docs/index.md

@@ -101,7 +101,7 @@ kadmin.local -q "addprinc -randkey slider-user@EXAMPLE.COM"
 Next, extract keytab file 
 Next, extract keytab file 
 
 
 ```
 ```
-kadmin.local -q "xst -k /path/to/keytab/slider-user.headless.keytab view-principal@EXAMPLE.COM"
+kadmin.local -q "xst -k /path/to/keytab/slider-user.headless.keytab slider-user@EXAMPLE.COM"
 ```
 ```
 The keytab file should then be copied over to the keytabs location on the host where the view is hosted.
 The keytab file should then be copied over to the keytabs location on the host where the view is hosted.
 
 

+ 5 - 3
contrib/views/slider/pom.xml

@@ -480,9 +480,11 @@
 							<goal>run</goal>
 							<goal>run</goal>
 						</goals>
 						</goals>
 						<configuration>
 						<configuration>
-							<target name="slider-ui-test">
-								<!-- <exec dir="${ui.directory}" executable="brunch" failonerror="false">
-									<arg value="test"/> </exec> -->
+							<target name="slider-ui-test" unless="${skipTests}">
+                <exec dir="${ui.directory}" executable="npm" failonerror="true">
+                  <env key="PYTHON" value="${basedir}/../ambari-common/src/main/unix/ambari-python-wrap" />
+                  <arg value="test"/>
+                </exec>
 							</target>
 							</target>
 						</configuration>
 						</configuration>
 					</execution>
 					</execution>

+ 692 - 0
contrib/views/slider/src/main/resources/ui/app/assets/javascripts/jquery.mockjax.js

@@ -0,0 +1,692 @@
+/*!
+ * MockJax - jQuery Plugin to Mock Ajax requests
+ *
+ * Version:  1.6.0
+ * Released:
+ * Home:   https://github.com/jakerella/jquery-mockjax
+ * Author:   Jonathan Sharp (http://jdsharp.com)
+ * License:  MIT,GPL
+ *
+ * Copyright (c) 2014 appendTo, Jordan Kasper
+ * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014
+ *
+ * Dual licensed under the MIT or GPL licenses.
+ * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html
+ */
+(function($) {
+  var _ajax = $.ajax,
+    mockHandlers = [],
+    mockedAjaxCalls = [],
+    unmockedAjaxCalls = [],
+    CALLBACK_REGEX = /=\?(&|$)/,
+    jsc = (new Date()).getTime();
+
+
+  // Parse the given XML string.
+  function parseXML(xml) {
+    if ( window.DOMParser == undefined && window.ActiveXObject ) {
+      DOMParser = function() { };
+      DOMParser.prototype.parseFromString = function( xmlString ) {
+        var doc = new ActiveXObject('Microsoft.XMLDOM');
+        doc.async = 'false';
+        doc.loadXML( xmlString );
+        return doc;
+      };
+    }
+
+    try {
+      var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+      if ( $.isXMLDoc( xmlDoc ) ) {
+        var err = $('parsererror', xmlDoc);
+        if ( err.length == 1 ) {
+          throw new Error('Error: ' + $(xmlDoc).text() );
+        }
+      } else {
+        throw new Error('Unable to parse XML');
+      }
+      return xmlDoc;
+    } catch( e ) {
+      var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+      $(document).trigger('xmlParseError', [ msg ]);
+      return undefined;
+    }
+  }
+
+  // Check if the data field on the mock handler and the request match. This
+  // can be used to restrict a mock handler to being used only when a certain
+  // set of data is passed to it.
+  function isMockDataEqual( mock, live ) {
+    var identical = true;
+    // Test for situations where the data is a querystring (not an object)
+    if (typeof live === 'string') {
+      // Querystring may be a regex
+      return $.isFunction( mock.test ) ? mock.test(live) : mock == live;
+    }
+    $.each(mock, function(k) {
+      if ( live[k] === undefined ) {
+        identical = false;
+        return identical;
+      } else {
+        if ( typeof live[k] === 'object' && live[k] !== null ) {
+          if ( identical && $.isArray( live[k] ) ) {
+            identical = $.isArray( mock[k] ) && live[k].length === mock[k].length;
+          }
+          identical = identical && isMockDataEqual(mock[k], live[k]);
+        } else {
+          if ( mock[k] && $.isFunction( mock[k].test ) ) {
+            identical = identical && mock[k].test(live[k]);
+          } else {
+            identical = identical && ( mock[k] == live[k] );
+          }
+        }
+      }
+    });
+
+    return identical;
+  }
+
+  // See if a mock handler property matches the default settings
+  function isDefaultSetting(handler, property) {
+    return handler[property] === $.mockjaxSettings[property];
+  }
+
+  // Check the given handler should mock the given request
+  function getMockForRequest( handler, requestSettings ) {
+    // If the mock was registered with a function, let the function decide if we
+    // want to mock this request
+    if ( $.isFunction(handler) ) {
+      return handler( requestSettings );
+    }
+
+    // Inspect the URL of the request and check if the mock handler's url
+    // matches the url for this ajax request
+    if ( $.isFunction(handler.url.test) ) {
+      // The user provided a regex for the url, test it
+      if ( !handler.url.test( requestSettings.url ) ) {
+        return null;
+      }
+    } else {
+      // Look for a simple wildcard '*' or a direct URL match
+      var star = handler.url.indexOf('*');
+      if (handler.url !== requestSettings.url && star === -1 ||
+        !new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace(/\*/g, '.+')).test(requestSettings.url)) {
+        return null;
+      }
+    }
+
+    // Inspect the data submitted in the request (either POST body or GET query string)
+    if ( handler.data ) {
+      if ( ! requestSettings.data || !isMockDataEqual(handler.data, requestSettings.data) ) {
+        // They're not identical, do not mock this request
+        return null;
+      }
+    }
+    // Inspect the request type
+    if ( handler && handler.type &&
+      handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) {
+      // The request type doesn't match (GET vs. POST)
+      return null;
+    }
+
+    return handler;
+  }
+
+  function parseResponseTimeOpt(responseTime) {
+    if ($.isArray(responseTime)) {
+      var min = responseTime[0];
+      var max = responseTime[1];
+      return (typeof min === 'number' && typeof max === 'number') ? Math.floor(Math.random() * (max - min)) + min : null;
+    } else {
+      return (typeof responseTime === 'number') ? responseTime: null;
+    }
+  }
+
+  // Process the xhr objects send operation
+  function _xhrSend(mockHandler, requestSettings, origSettings) {
+
+    // This is a substitute for < 1.4 which lacks $.proxy
+    var process = (function(that) {
+      return function() {
+        return (function() {
+          // The request has returned
+          this.status     = mockHandler.status;
+          this.statusText = mockHandler.statusText;
+          this.readyState	= 1;
+
+          var finishRequest = function () {
+            this.readyState	= 4;
+
+            var onReady;
+            // Copy over our mock to our xhr object before passing control back to
+            // jQuery's onreadystatechange callback
+            if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) {
+              this.responseText = JSON.stringify(mockHandler.responseText);
+            } else if ( requestSettings.dataType == 'xml' ) {
+              if ( typeof mockHandler.responseXML == 'string' ) {
+                this.responseXML = parseXML(mockHandler.responseXML);
+                //in jQuery 1.9.1+, responseXML is processed differently and relies on responseText
+                this.responseText = mockHandler.responseXML;
+              } else {
+                this.responseXML = mockHandler.responseXML;
+              }
+            } else if (typeof mockHandler.responseText === 'object' && mockHandler.responseText !== null) {
+              // since jQuery 1.9 responseText type has to match contentType
+              mockHandler.contentType = 'application/json';
+              this.responseText = JSON.stringify(mockHandler.responseText);
+            } else {
+              this.responseText = mockHandler.responseText;
+            }
+            if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) {
+              this.status = mockHandler.status;
+            }
+            if( typeof mockHandler.statusText === "string") {
+              this.statusText = mockHandler.statusText;
+            }
+            // jQuery 2.0 renamed onreadystatechange to onload
+            onReady = this.onreadystatechange || this.onload;
+
+            // jQuery < 1.4 doesn't have onreadystate change for xhr
+            if ( $.isFunction( onReady ) ) {
+              if( mockHandler.isTimeout) {
+                this.status = -1;
+              }
+              onReady.call( this, mockHandler.isTimeout ? 'timeout' : undefined );
+            } else if ( mockHandler.isTimeout ) {
+              // Fix for 1.3.2 timeout to keep success from firing.
+              this.status = -1;
+            }
+          };
+
+          // We have an executable function, call it to give
+          // the mock handler a chance to update it's data
+          if ( $.isFunction(mockHandler.response) ) {
+            // Wait for it to finish
+            if ( mockHandler.response.length === 2 ) {
+              mockHandler.response(origSettings, function () {
+                finishRequest.call(that);
+              });
+              return;
+            } else {
+              mockHandler.response(origSettings);
+            }
+          }
+
+          finishRequest.call(that);
+        }).apply(that);
+      };
+    })(this);
+
+    if ( mockHandler.proxy ) {
+      // We're proxying this request and loading in an external file instead
+      _ajax({
+        global: false,
+        url: mockHandler.proxy,
+        type: mockHandler.proxyType,
+        data: mockHandler.data,
+        dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType,
+        complete: function(xhr) {
+          mockHandler.responseXML = xhr.responseXML;
+          mockHandler.responseText = xhr.responseText;
+          // Don't override the handler status/statusText if it's specified by the config
+          if (isDefaultSetting(mockHandler, 'status')) {
+            mockHandler.status = xhr.status;
+          }
+          if (isDefaultSetting(mockHandler, 'statusText')) {
+            mockHandler.statusText = xhr.statusText;
+          }
+          this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 0);
+        }
+      });
+    } else {
+      // type == 'POST' || 'GET' || 'DELETE'
+      if ( requestSettings.async === false ) {
+        // TODO: Blocking delay
+        process();
+      } else {
+        this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 50);
+      }
+    }
+  }
+
+  // Construct a mocked XHR Object
+  function xhr(mockHandler, requestSettings, origSettings, origHandler) {
+    // Extend with our default mockjax settings
+    mockHandler = $.extend(true, {}, $.mockjaxSettings, mockHandler);
+
+    if (typeof mockHandler.headers === 'undefined') {
+      mockHandler.headers = {};
+    }
+    if (typeof requestSettings.headers === 'undefined') {
+      requestSettings.headers = {};
+    }
+    if ( mockHandler.contentType ) {
+      mockHandler.headers['content-type'] = mockHandler.contentType;
+    }
+
+    return {
+      status: mockHandler.status,
+      statusText: mockHandler.statusText,
+      readyState: 1,
+      open: function() { },
+      send: function() {
+        origHandler.fired = true;
+        _xhrSend.call(this, mockHandler, requestSettings, origSettings);
+      },
+      abort: function() {
+        clearTimeout(this.responseTimer);
+      },
+      setRequestHeader: function(header, value) {
+        requestSettings.headers[header] = value;
+      },
+      getResponseHeader: function(header) {
+        // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
+        if ( mockHandler.headers && mockHandler.headers[header] ) {
+          // Return arbitrary headers
+          return mockHandler.headers[header];
+        } else if ( header.toLowerCase() == 'last-modified' ) {
+          return mockHandler.lastModified || (new Date()).toString();
+        } else if ( header.toLowerCase() == 'etag' ) {
+          return mockHandler.etag || '';
+        } else if ( header.toLowerCase() == 'content-type' ) {
+          return mockHandler.contentType || 'text/plain';
+        }
+      },
+      getAllResponseHeaders: function() {
+        var headers = '';
+        // since jQuery 1.9 responseText type has to match contentType
+        if (mockHandler.contentType) {
+          mockHandler.headers['Content-Type'] = mockHandler.contentType;
+        }
+        $.each(mockHandler.headers, function(k, v) {
+          headers += k + ': ' + v + "\n";
+        });
+        return headers;
+      }
+    };
+  }
+
+  // Process a JSONP mock request.
+  function processJsonpMock( requestSettings, mockHandler, origSettings ) {
+    // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
+    // because there isn't an easy hook for the cross domain script tag of jsonp
+
+    processJsonpUrl( requestSettings );
+
+    requestSettings.dataType = "json";
+    if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
+      createJsonpCallback(requestSettings, mockHandler, origSettings);
+
+      // We need to make sure
+      // that a JSONP style response is executed properly
+
+      var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+        parts = rurl.exec( requestSettings.url ),
+        remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+      requestSettings.dataType = "script";
+      if(requestSettings.type.toUpperCase() === "GET" && remote ) {
+        var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
+
+        // Check if we are supposed to return a Deferred back to the mock call, or just
+        // signal success
+        if(newMockReturn) {
+          return newMockReturn;
+        } else {
+          return true;
+        }
+      }
+    }
+    return null;
+  }
+
+  // Append the required callback parameter to the end of the request URL, for a JSONP request
+  function processJsonpUrl( requestSettings ) {
+    if ( requestSettings.type.toUpperCase() === "GET" ) {
+      if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
+        requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") +
+          (requestSettings.jsonp || "callback") + "=?";
+      }
+    } else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
+      requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?";
+    }
+  }
+
+  // Process a JSONP request by evaluating the mocked response text
+  function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
+    // Synthesize the mock request for adding a script tag
+    var callbackContext = origSettings && origSettings.context || requestSettings,
+      newMock = null;
+
+
+    // If the response handler on the moock is a function, call it
+    if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
+      mockHandler.response(origSettings);
+    } else {
+
+      // Evaluate the responseText javascript in a global context
+      if( typeof mockHandler.responseText === 'object' ) {
+        $.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
+      } else {
+        $.globalEval( '(' + mockHandler.responseText + ')');
+      }
+    }
+
+    // Successful response
+    setTimeout(function() {
+      jsonpSuccess( requestSettings, callbackContext, mockHandler );
+      jsonpComplete( requestSettings, callbackContext, mockHandler );
+    }, parseResponseTimeOpt(mockHandler.responseTime) || 0);
+
+    // If we are running under jQuery 1.5+, return a deferred object
+    if($.Deferred){
+      newMock = new $.Deferred();
+      if(typeof mockHandler.responseText == "object"){
+        newMock.resolveWith( callbackContext, [mockHandler.responseText] );
+      }
+      else{
+        newMock.resolveWith( callbackContext, [$.parseJSON( mockHandler.responseText )] );
+      }
+    }
+    return newMock;
+  }
+
+
+  // Create the required JSONP callback function for the request
+  function createJsonpCallback( requestSettings, mockHandler, origSettings ) {
+    var callbackContext = origSettings && origSettings.context || requestSettings;
+    var jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++);
+
+    // Replace the =? sequence both in the query string and the data
+    if ( requestSettings.data ) {
+      requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1");
+    }
+
+    requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1");
+
+
+    // Handle JSONP-style loading
+    window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+      data = tmp;
+      jsonpSuccess( requestSettings, callbackContext, mockHandler );
+      jsonpComplete( requestSettings, callbackContext, mockHandler );
+      // Garbage collect
+      window[ jsonp ] = undefined;
+
+      try {
+        delete window[ jsonp ];
+      } catch(e) {}
+
+      if ( head ) {
+        head.removeChild( script );
+      }
+    };
+  }
+
+  // The JSONP request was successful
+  function jsonpSuccess(requestSettings, callbackContext, mockHandler) {
+    // If a local callback was specified, fire it and pass it the data
+    if ( requestSettings.success ) {
+      requestSettings.success.call( callbackContext, mockHandler.responseText || "", status, {} );
+    }
+
+    // Fire the global callback
+    if ( requestSettings.global ) {
+      (requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxSuccess", [{}, requestSettings]);
+    }
+  }
+
+  // The JSONP request was completed
+  function jsonpComplete(requestSettings, callbackContext) {
+    // Process result
+    if ( requestSettings.complete ) {
+      requestSettings.complete.call( callbackContext, {} , status );
+    }
+
+    // The request was completed
+    if ( requestSettings.global ) {
+      (requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxComplete", [{}, requestSettings]);
+    }
+
+    // Handle the global AJAX counter
+    if ( requestSettings.global && ! --$.active ) {
+      $.event.trigger( "ajaxStop" );
+    }
+  }
+
+
+  // The core $.ajax replacement.
+  function handleAjax( url, origSettings ) {
+    var mockRequest, requestSettings, mockHandler, overrideCallback;
+
+    // If url is an object, simulate pre-1.5 signature
+    if ( typeof url === "object" ) {
+      origSettings = url;
+      url = undefined;
+    } else {
+      // work around to support 1.5 signature
+      origSettings = origSettings || {};
+      origSettings.url = url;
+    }
+
+    // Extend the original settings for the request
+    requestSettings = $.extend(true, {}, $.ajaxSettings, origSettings);
+
+    // Generic function to override callback methods for use with
+    // callback options (onAfterSuccess, onAfterError, onAfterComplete)
+    overrideCallback = function(action, mockHandler) {
+      var origHandler = origSettings[action.toLowerCase()];
+      return function() {
+        if ( $.isFunction(origHandler) ) {
+          origHandler.apply(this, [].slice.call(arguments));
+        }
+        mockHandler['onAfter' + action]();
+      };
+    };
+
+    // Iterate over our mock handlers (in registration order) until we find
+    // one that is willing to intercept the request
+    for(var k = 0; k < mockHandlers.length; k++) {
+      if ( !mockHandlers[k] ) {
+        continue;
+      }
+
+      mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
+      if(!mockHandler) {
+        // No valid mock found for this request
+        continue;
+      }
+
+      mockedAjaxCalls.push(requestSettings);
+
+      // If logging is enabled, log the mock to the console
+      $.mockjaxSettings.log( mockHandler, requestSettings );
+
+
+      if ( requestSettings.dataType && requestSettings.dataType.toUpperCase() === 'JSONP' ) {
+        if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
+          // This mock will handle the JSONP request
+          return mockRequest;
+        }
+      }
+
+
+      // Removed to fix #54 - keep the mocking data object intact
+      //mockHandler.data = requestSettings.data;
+
+      mockHandler.cache = requestSettings.cache;
+      mockHandler.timeout = requestSettings.timeout;
+      mockHandler.global = requestSettings.global;
+
+      // In the case of a timeout, we just need to ensure
+      // an actual jQuery timeout (That is, our reponse won't)
+      // return faster than the timeout setting.
+      if ( mockHandler.isTimeout ) {
+        if ( mockHandler.responseTime > 1 ) {
+          origSettings.timeout = mockHandler.responseTime - 1;
+        } else {
+          mockHandler.responseTime = 2;
+          origSettings.timeout = 1;
+        }
+        mockHandler.isTimeout = false;
+      }
+
+      // Set up onAfter[X] callback functions
+      if ( $.isFunction( mockHandler.onAfterSuccess ) ) {
+        origSettings.success = overrideCallback('Success', mockHandler);
+      }
+      if ( $.isFunction( mockHandler.onAfterError ) ) {
+        origSettings.error = overrideCallback('Error', mockHandler);
+      }
+      if ( $.isFunction( mockHandler.onAfterComplete ) ) {
+        origSettings.complete = overrideCallback('Complete', mockHandler);
+      }
+
+      copyUrlParameters(mockHandler, origSettings);
+
+      (function(mockHandler, requestSettings, origSettings, origHandler) {
+
+        mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
+          // Mock the XHR object
+          xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ); }
+        }));
+      })(mockHandler, requestSettings, origSettings, mockHandlers[k]);
+
+      return mockRequest;
+    }
+
+    // We don't have a mock request
+    unmockedAjaxCalls.push(origSettings);
+    if($.mockjaxSettings.throwUnmocked === true) {
+      throw new Error('AJAX not mocked: ' + origSettings.url);
+    }
+    else { // trigger a normal request
+      return _ajax.apply($, [origSettings]);
+    }
+  }
+
+  /**
+   * Copies URL parameter values if they were captured by a regular expression
+   * @param {Object} mockHandler
+   * @param {Object} origSettings
+   */
+  function copyUrlParameters(mockHandler, origSettings) {
+    //parameters aren't captured if the URL isn't a RegExp
+    if (!(mockHandler.url instanceof RegExp)) {
+      return;
+    }
+    //if no URL params were defined on the handler, don't attempt a capture
+    if (!mockHandler.hasOwnProperty('urlParams')) {
+      return;
+    }
+    var captures = mockHandler.url.exec(origSettings.url);
+    //the whole RegExp match is always the first value in the capture results
+    if (captures.length === 1) {
+      return;
+    }
+    captures.shift();
+    //use handler params as keys and capture resuts as values
+    var i = 0,
+      capturesLength = captures.length,
+      paramsLength = mockHandler.urlParams.length,
+    //in case the number of params specified is less than actual captures
+      maxIterations = Math.min(capturesLength, paramsLength),
+      paramValues = {};
+    for (i; i < maxIterations; i++) {
+      var key = mockHandler.urlParams[i];
+      paramValues[key] = captures[i];
+    }
+    origSettings.urlParams = paramValues;
+  }
+
+
+  // Public
+
+  $.extend({
+    ajax: handleAjax
+  });
+
+  $.mockjaxSettings = {
+    //url:        null,
+    //type:       'GET',
+    log:          function( mockHandler, requestSettings ) {
+      if ( mockHandler.logging === false ||
+        ( typeof mockHandler.logging === 'undefined' && $.mockjaxSettings.logging === false ) ) {
+        return;
+      }
+      if ( window.console && console.log ) {
+        var message = 'MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url;
+        var request = $.extend({}, requestSettings);
+
+        if (typeof console.log === 'function') {
+          console.log(message, request);
+        } else {
+          try {
+            console.log( message + ' ' + JSON.stringify(request) );
+          } catch (e) {
+            console.log(message);
+          }
+        }
+      }
+    },
+    logging:       true,
+    status:        200,
+    statusText:    "OK",
+    responseTime:  500,
+    isTimeout:     false,
+    throwUnmocked: false,
+    contentType:   'text/plain',
+    response:      '',
+    responseText:  '',
+    responseXML:   '',
+    proxy:         '',
+    proxyType:     'GET',
+
+    lastModified:  null,
+    etag:          '',
+    headers: {
+      etag: 'IJF@H#@923uf8023hFO@I#H#',
+      'content-type' : 'text/plain'
+    }
+  };
+
+  $.mockjax = function(settings) {
+    var i = mockHandlers.length;
+    mockHandlers[i] = settings;
+    return i;
+  };
+  $.mockjax.clear = function(i) {
+    if ( arguments.length == 1 ) {
+      mockHandlers[i] = null;
+    } else {
+      mockHandlers = [];
+    }
+    mockedAjaxCalls = [];
+    unmockedAjaxCalls = [];
+  };
+  // support older, deprecated version
+  $.mockjaxClear = function(i) {
+    window.console && window.console.warn && window.console.warn( 'DEPRECATED: The $.mockjaxClear() method has been deprecated in 1.6.0. Please use $.mockjax.clear() as the older function will be removed soon!' );
+    $.mockjax.clear();
+  };
+  $.mockjax.handler = function(i) {
+    if ( arguments.length == 1 ) {
+      return mockHandlers[i];
+    }
+  };
+  $.mockjax.mockedAjaxCalls = function() {
+    return mockedAjaxCalls;
+  };
+  $.mockjax.unfiredHandlers = function() {
+    var results = [];
+    for (var i=0, len=mockHandlers.length; i<len; i++) {
+      var handler = mockHandlers[i];
+      if (handler !== null && !handler.fired) {
+        results.push(handler);
+      }
+    }
+    return results;
+  };
+  $.mockjax.unmockedAjaxCalls = function() {
+    return unmockedAjaxCalls;
+  };
+})(jQuery);

+ 1 - 0
contrib/views/slider/src/main/resources/ui/app/assets/tests.html

@@ -28,6 +28,7 @@
 <div id="qunit-fixture"></div>
 <div id="qunit-fixture"></div>
 <script src="javascripts/qunit.js"></script>
 <script src="javascripts/qunit.js"></script>
 <script src="javascripts/vendor.js"></script>
 <script src="javascripts/vendor.js"></script>
+<script src="javascripts/jquery.mockjax.js"></script>
 <script src="javascripts/ember-qunit.js"></script>
 <script src="javascripts/ember-qunit.js"></script>
 <script src="javascripts/app.js"></script>
 <script src="javascripts/app.js"></script>
 <script>
 <script>

+ 2 - 1
contrib/views/slider/src/main/resources/ui/package.json

@@ -12,7 +12,7 @@
     "node": "~0.6.10 || 0.8 || 0.9"
     "node": "~0.6.10 || 0.8 || 0.9"
   },
   },
   "scripts": {
   "scripts": {
-    "test": "./node_modules/karma/bin/karma start --single-run --browsers PhantomJS",
+    "test": "node_modules/phantomjs/bin/phantomjs runner.js public/tests.html",
     "start": "brunch watch --server",
     "start": "brunch watch --server",
     "build": "brunch build",
     "build": "brunch build",
     "build:production": "brunch build --production",
     "build:production": "brunch build --production",
@@ -29,6 +29,7 @@
     "ember-handlebars-brunch": "git://github.com/fuseelements/ember-handlebars-brunch#fix/ember-1.3.0"
     "ember-handlebars-brunch": "git://github.com/fuseelements/ember-handlebars-brunch#fix/ember-1.3.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
+    "phantomjs": "^1.9.2",
     "karma": "*",
     "karma": "*",
     "karma-qunit": "*",
     "karma-qunit": "*",
     "karma-phantomjs-launcher": "~0.1.2"
     "karma-phantomjs-launcher": "~0.1.2"

+ 136 - 0
contrib/views/slider/src/main/resources/ui/runner.js

@@ -0,0 +1,136 @@
+/*
+ * PhantomJS Runner QUnit Plugin (List Tests) 1.2.0
+ *
+ * PhantomJS binaries: http://phantomjs.org/download.html
+ * Requires PhantomJS 1.6+ (1.7+ recommended)
+ *
+ * Run with:
+ *   phantomjs runner-list.js [url-of-your-qunit-testsuite]
+ *
+ * e.g.
+ *   phantomjs runner-list.js http://localhost/qunit/test/index.html
+ */
+
+/*global phantom:false, require:false, console:false, window:false, QUnit:false */
+
+(function() {
+  'use strict';
+
+  var url, page, timeout,
+    args = require('system').args;
+
+  // arg[0]: scriptName, args[1...]: arguments
+  if (args.length < 2 || args.length > 3) {
+    console.error('Usage:\n  phantomjs runner-list.js [url-of-your-qunit-testsuite] [timeout-in-seconds]');
+    phantom.exit(1);
+  }
+
+  url = args[1];
+  page = require('webpage').create();
+  if (args[2] !== undefined) {
+    timeout = parseInt(args[2], 10);
+  }
+
+  // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`)
+  page.onConsoleMessage = function(msg) {
+    console.log(msg);
+  };
+
+  page.onInitialized = function() {
+    page.evaluate(addLogging);
+  };
+
+  page.onCallback = function(message) {
+    var result,
+      failed;
+
+    if (message) {
+      if (message.name === 'QUnit.done') {
+        result = message.data;
+        failed = !result || !result.total || result.failed;
+
+        if (!result.total) {
+          console.error('No tests were executed. Are you loading tests asynchronously?');
+        }
+
+        phantom.exit(failed ? 1 : 0);
+      }
+    }
+  };
+
+  page.open(url, function(status) {
+    if (status !== 'success') {
+      console.error('Unable to access network: ' + status);
+      phantom.exit(1);
+    } else {
+      // Cannot do this verification with the 'DOMContentLoaded' handler because it
+      // will be too late to attach it if a page does not have any script tags.
+      var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); });
+      if (qunitMissing) {
+        console.error('The `QUnit` object is not present on this page.');
+        phantom.exit(1);
+      }
+
+      // Set a timeout on the test running, otherwise tests with async problems will hang forever
+      if (typeof timeout === 'number') {
+        setTimeout(function() {
+          console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...');
+          phantom.exit(1);
+        }, timeout * 1000);
+      }
+
+      // Do nothing... the callback mechanism will handle everything!
+    }
+  });
+
+  function addLogging() {
+    window.document.addEventListener('DOMContentLoaded', function() {
+      var currentTestAssertions = [];
+
+      QUnit.log(function(details) {
+        var response;
+
+        console.log((details.result ? "? ": "? ") + details.message);
+
+        if (!details.result) {
+          response = details.message || '';
+
+          if (typeof details.expected !== 'undefined') {
+            if (response) {
+              response += ', ';
+            }
+
+            response += 'expected: ' + details.expected + ', but was: ' + details.actual;
+          }
+
+          if (details.source) {
+            response += '\n' + details.source;
+          }
+
+          console.log('    Failed assertion: ' + response);
+        }
+      });
+
+      QUnit.moduleStart(function( details ) {
+        if (details.name) {
+          console.log('\n' + details.name);
+        }
+      });
+
+      QUnit.testStart(function(result) {
+        console.log('\n' + result.name);
+      });
+
+      QUnit.done(function(result) {
+        console.log('\n' + 'Took ' + result.runtime +  'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.');
+
+        if (typeof window.callPhantom === 'function') {
+          window.callPhantom({
+            'name': 'QUnit.done',
+            'data': result
+          });
+        }
+      });
+    }, false);
+  }
+})();

+ 28 - 0
contrib/views/slider/src/main/resources/ui/test/index.md

@@ -0,0 +1,28 @@
+<!---
+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](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.
+-->
+
+# Slider UI Unit Tests
+
+Go to slider-ui folder - `/contrib/views/slider/src/main/resources/ui/`
+Execute:
+
+1. `npm i`
+2. `brunch w -s`
+
+Open in browser `http://localhost:3333/tests.html`
+
+OR
+
+Execute in console `node_modules/phantomjs/bin/phantomjs runner.js public/tests.html`

+ 10 - 5
contrib/views/slider/src/main/resources/ui/test/integration/processes/create_new_app_test.js

@@ -107,6 +107,15 @@ var selectors = {
 QUnit.module('integration/processes - Create New App', {
 QUnit.module('integration/processes - Create New App', {
 
 
   setup: function () {
   setup: function () {
+
+    $.mockjax({
+      type: 'GET',
+      url: '*',
+      status: '200',
+      dataType: 'json',
+      responseText: {}
+    });
+
     Em.run(App, App.advanceReadiness);
     Em.run(App, App.advanceReadiness);
     Em.run(function () {
     Em.run(function () {
       App.set('viewEnabled', true); // Important!
       App.set('viewEnabled', true); // Important!
@@ -116,6 +125,7 @@ QUnit.module('integration/processes - Create New App', {
 
 
   teardown: function () {
   teardown: function () {
     App.reset();
     App.reset();
+    $.mockjax.clear();
   }
   }
 
 
 });
 });
@@ -174,11 +184,6 @@ test('basic (no errors - just valid data)', function () {
       ok(find('#step4').text().indexOf('HBASE_REGIONSERVER: ' + newApp.components.HBASE_REGIONSERVER) > -1, 'HBASE_REGIONSERVER count exists');
       ok(find('#step4').text().indexOf('HBASE_REGIONSERVER: ' + newApp.components.HBASE_REGIONSERVER) > -1, 'HBASE_REGIONSERVER count exists');
       ok(find('pre').text().indexOf('"' + newApp.newConfig.name + '":"' + newApp.newConfig.value + '"') > -1, 'Custom property exists');
       ok(find('pre').text().indexOf('"' + newApp.newConfig.name + '":"' + newApp.newConfig.value + '"') > -1, 'Custom property exists');
 
 
-      click('button.btn-success');
-      andThen(function () {
-        /* FINISHED */
-        equal(currentURL(), '/', 'After click "Finish" user is sent to index page');
-      });
     });
     });
   });
   });
 
 

+ 1 - 0
pom.xml

@@ -240,6 +240,7 @@
             <exclude>contrib/views/slider/src/main/resources/ui/app/assets/stylesheets/**</exclude>
             <exclude>contrib/views/slider/src/main/resources/ui/app/assets/stylesheets/**</exclude>
             <exclude>contrib/views/slider/src/main/resources/ui/app/assets/javascripts/**</exclude>
             <exclude>contrib/views/slider/src/main/resources/ui/app/assets/javascripts/**</exclude>
             <exclude>contrib/views/slider/src/main/resources/ui/bower_components/**</exclude>
             <exclude>contrib/views/slider/src/main/resources/ui/bower_components/**</exclude>
+            <exclude>contrib/views/slider/src/main/resources/ui/runner.js</exclude>
             <exclude>contrib/addons/package/deb/nagios_addon_deb_control</exclude>
             <exclude>contrib/addons/package/deb/nagios_addon_deb_control</exclude>
             <exclude>contrib/addons/src/addOns/nagios/conf.d/hdp_mon_nagios_addons.conf</exclude>
             <exclude>contrib/addons/src/addOns/nagios/conf.d/hdp_mon_nagios_addons.conf</exclude>
             <exclude>contrib/views/*/.classpath</exclude>
             <exclude>contrib/views/*/.classpath</exclude>