You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by de...@apache.org on 2010/09/16 17:05:33 UTC

svn commit: r997779 - in /activemq/trunk/activemq-web-demo/src/main/webapp: js/ test/ test/assets/

Author: dejanb
Date: Thu Sep 16 15:05:32 2010
New Revision: 997779

URL: http://svn.apache.org/viewvc?rev=997779&view=rev
Log:
https://issues.apache.org/activemq/browse/AMQ-2874 - ajax selectors

Added:
    activemq/trunk/activemq-web-demo/src/main/webapp/test/amq_test.html
    activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/
    activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/README
    activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/jsunittest.js
    activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/unittest.css
Modified:
    activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js
    activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_dojo_adapter.js
    activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_jquery_adapter.js
    activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_prototype_adapter.js

Modified: activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js?rev=997779&r1=997778&r2=997779&view=diff
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js (original)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/js/amq.js Thu Sep 16 15:05:32 2010
@@ -146,19 +146,21 @@ org.activemq.Amq = function() {
 		adapter.ajax(uri, options);
 	};
 
-	var sendJmsMessage = function(destination, message, type) {
+	var sendJmsMessage = function(destination, message, type, headers) {
+		var message = {
+			destination: destination,
+			message: message,
+			messageType: type
+		};
 		// Add message to outbound queue
 		if (batchInProgress) {
-			messageQueue[messageQueue.length] = {
-				destination: destination,
-				message: message,
-				messageType: type
-			};
+			messageQueue[messageQueue.length] = {message:message, headers:headers};
 		} else {
 			org.activemq.Amq.startBatch();
 			adapter.ajax(uri, { method: 'post',
-				data: 'destination=' + destination + '&message=' + message + '&type=' + type,
+				data: buildParams( [message] ),
 				error: errorHandler,
+				headers: headers,
 				success: org.activemq.Amq.endBatch});
 		}
 	};
@@ -197,11 +199,30 @@ org.activemq.Amq = function() {
 
 		endBatch : function() {
 			if (messageQueue.length > 0) {
-				var body = buildParams(messageQueue);
-				messageQueue.length = 0;
+				var messagesToSend = [];
+				var messagesToQueue = [];
+				var outgoingHeaders = null;
+				
+				// we need to ensure that messages which set headers are sent by themselves.
+				// if 2 'listen' messages were sent together, and a 'selector' header were added to one of them,
+				//	 AMQ would add the selector to both 'listen' commands.
+				for(i=0;i<messageQueue.length;i++) {
+					// a message with headers should always be sent by itself.	if other messages have been added, send this one later.
+					if ( messageQueue[ i ].headers && messagesToSend.length == 0 ) {
+						messagesToSend[ messagesToSend.length ] = messageQueue[ i ].message;
+						outgoingHeaders = messageQueue[ i ].headers;
+					} else if ( ! messageQueue[ i ].headers && ! outgoingHeaders ) {
+						messagesToSend[ messagesToSend.length ] = messageQueue[ i ].message;
+					} else {
+						messagesToQueue[ messagesToQueue.length ] = messageQueue[ i ];
+					}
+				}
+				var body = buildParams(messagesToSend);
+				messageQueue = messagesToQueue;
 				org.activemq.Amq.startBatch();
 				adapter.ajax(uri, {
 					method: 'post',
+					headers: outgoingHeaders,
 					data: body,
 					success: org.activemq.Amq.endBatch, 
 					error: errorHandler});
@@ -218,15 +239,30 @@ org.activemq.Amq = function() {
 
 		// Listen on a channel or topic.
 		// handler must be a function taking a message argument
-		addListener : function(id, destination, handler) {
+		//
+		// Supported options:
+		//  selector: If supplied, it should be a SQL92 string like "property-name='value'"
+		//            http://activemq.apache.org/selectors.html
+		//
+		// Example: addListener( 'handler', 'topic://test-topic', function(msg) { return msg; }, { selector: "property-name='property-value'" } )
+		addListener : function(id, destination, handler, options) {
 			messageHandlers[id] = handler;
-			sendJmsMessage(destination, id, 'listen');
+			var headers = options && options.selector ? {selector:options.selector} : null;
+			sendJmsMessage(destination, id, 'listen', headers);
 		},
 
 		// remove Listener from channel or topic.
 		removeListener : function(id, destination) {
 			messageHandlers[id] = null;
 			sendJmsMessage(destination, id, 'unlisten');
+		},
+		
+		// for unit testing
+		getMessageQueue: function() {
+			return messageQueue;
+		},
+		testPollHandler: function( data ) {
+			return pollHandler( data );
 		}
 	};
 }();

Modified: activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_dojo_adapter.js
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_dojo_adapter.js?rev=997779&r1=997778&r2=997779&view=diff
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_dojo_adapter.js (original)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_dojo_adapter.js Thu Sep 16 15:05:32 2010
@@ -45,6 +45,7 @@ org.activemq.AmqAdapter = {
  *             - xhr:    The XmlHttpRequest object.
  *             - status: A text string of the status.
  *             - ex:     The exception that caused the error.
+ *  - headers: An object containing additional headers for the ajax request.
  */
 	ajax: function(uri, options) {
 		if (options.method == 'post') {
@@ -52,10 +53,11 @@ org.activemq.AmqAdapter = {
 				url: uri,
 				handleAs: "xml",
 				postData: options.data,
+				headers: options.headers,
 				load : options.success ? options.success : function() {},
 				error: options.error ? function(ex, ioargs) {
 						options.error(ioargs.xhr,ioargs.xhr.status, ex);
-					} : function() {}				
+					} : function() {}
 			});
 		} else {
 			if (options.data)
@@ -64,8 +66,9 @@ org.activemq.AmqAdapter = {
 				uri += options.data;
 			}
 			dojo.xhrGet({
-                                url: uri,
+				url: uri,
 				handleAs: "xml",
+				headers: options.headers,
 				load : options.success ? options.success : function() {},
 				error: options.error ? function(ex, ioargs) {
 						options.error(ioargs.xhr,ioargs.xhr.status, ex);
@@ -77,4 +80,5 @@ org.activemq.AmqAdapter = {
 	log: function(message, exception) {
 		if (typeof console != 'undefined' && console.log) console.log(message);
 	}
+
 };

Modified: activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_jquery_adapter.js
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_jquery_adapter.js?rev=997779&r1=997778&r2=997779&view=diff
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_jquery_adapter.js (original)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_jquery_adapter.js Thu Sep 16 15:05:32 2010
@@ -45,33 +45,41 @@ org.activemq.AmqAdapter = {
 	 *             - xhr:    The XmlHttpRequest object.
 	 *             - status: A text string of the status.
 	 *             - ex:     The exception that caused the error.
+	 *  - headers: An object containing additional headers for the ajax request.
 	 */
 	ajax: function(uri, options) {
+		request = {
+			url: uri,
+			data: options.data,
+			success: options.success || function(){},
+			error: options.error || function(){}
+		}
+		var headers = {};
+		if( options.headers ) {
+			headers = options.headers;
+		}
+		
 		if (options.method == 'post') {
-			jQuery.ajax({
-				type: "POST",
-				url: uri,
-				data: options.data,
-				success: options.success || function(){},
-				error: options.error || function(){},
-				beforeSend: function(xhr) {
-					/* Force "Connection: close" for Mozilla browsers to work around
-					 * a bug where XMLHttpReqeuest sends an incorrect Content-length
-					 * header. See Mozilla Bugzilla #246651.
-					 */
-					xhr.setRequestHeader("Connection", 'close');
+			request.type = 'POST';
+			/* Force "Connection: close" for Mozilla browsers to work around
+			 * a bug where XMLHttpReqeuest sends an incorrect Content-length
+			 * header. See Mozilla Bugzilla #246651.
+			 */
+			headers[ 'Connection' ] = 'close';
+		} else {
+			request.type = 'GET';
+			request.dataType = 'xml';
+		}
+		
+		if( headers ) {
+			request.beforeSend = function(xhr) {
+				for( h in headers ) {
+					xhr.setRequestHeader( h, headers[ h ] );
 				}
-			});
-		} else {        
-			jQuery.ajax({
-				type: "GET",
-				url: uri,
-				data: options.data,
-				success: options.success || function(){},
-				error: options.error || function(){},
-				dataType: 'xml'
-				});
+			}
 		}
+		
+		jQuery.ajax( request );
 	},
 
 	log: function(message, exception) {

Modified: activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_prototype_adapter.js
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_prototype_adapter.js?rev=997779&r1=997778&r2=997779&view=diff
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_prototype_adapter.js (original)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/js/amq_prototype_adapter.js Thu Sep 16 15:05:32 2010
@@ -45,46 +45,40 @@ org.activemq.AmqAdapter = {
  *             - xhr:    The XmlHttpRequest object.
  *             - status: A text string of the status.
  *             - ex:     The exception that caused the error.
+ *  - headers: An object containing additional headers for the ajax request.
  */
 	ajax: function(uri, options) {
-		if (options.method == 'post') {
-			new Ajax.Request(uri, {
-				method: "post",
-				postBody: options.data,
-				onSuccess: options.success ? function(xhr, header) {
-					if (options.success) {
-						var ct = xhr.getResponseHeader("content-type");
-						var xml = ct && ct.indexOf("xml") >= 0;
-						var data = xml ? xhr.responseXML : xhr.responseText;
-						options.success(data);
-					}
-				} : function() {},
-				onFailure: options.error || function() {
-				},
-				onException: options.error || function() {
+		request = {
+			onSuccess: options.success ? function(xhr, header) {
+				if (options.success) {
+					var ct = xhr.getResponseHeader("content-type");
+					var xml = ct && ct.indexOf("xml") >= 0;
+					var data = xml ? xhr.responseXML : xhr.responseText;
+					options.success(data);
 				}
-			});
+			} : function() {},
+			onFailure: options.error || function() {
+			},
+			onException: options.error || function() {
+			}
+		}
+		
+		if( options.headers ) {
+			request.requestHeaders = options.headers;
+		}
+		
+		if (options.method == 'post') {
+			request.postBody = options.data;
 		} else {
-			new Ajax.Request(uri, {
-				method: "get",
-				parameters: options.data,
-				onSuccess: function(xhr, header) {
-					if (options.success) {
-						var ct = xhr.getResponseHeader("content-type");
-						var xml = ct && ct.indexOf("xml") >= 0;
-						var data = xml ? xhr.responseXML : xhr.responseText;
-						options.success(data);
-					}
-				},
-				onFailure: options.error || function() {
-				},
-				onException: options.error || function() {
-				}
-			});
+			request.parameters = options.data;
+			request.method = 'get';
 		}
+		
+		new Ajax.Request( uri, request );
 	},
 
 	log: function(message, exception) {
 		if (typeof console != 'undefined' && console.log) console.log(message);
 	}
+
 };

Added: activemq/trunk/activemq-web-demo/src/main/webapp/test/amq_test.html
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/test/amq_test.html?rev=997779&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/test/amq_test.html (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/test/amq_test.html Thu Sep 16 15:05:32 2010
@@ -0,0 +1,291 @@
+<!--
+  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.
+-->
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <title>AMQ test</title>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  <script src="assets/jsunittest.js" type="text/javascript"></script>
+
+  <script>
+  var org = org || {};
+  org.activemq = org.activemq || {};
+
+  org.activemq.AmqAdapter = {
+    // implement org.activemq.AmqAdapter API
+    init: function(options) {},
+    ajax: function(uri, options) {
+      ajaxRequests[ajaxRequests.length] = { uri:uri, options:options };
+    },
+    
+    // add additional functionality for testing.
+    ajaxRequests: [],
+    getRequests: function() {
+      return ajaxRequests;
+    },
+    reset: function() {
+      ajaxRequests=[];
+    }
+  };
+  </script>
+  <script src="../js/amq.js" type="text/javascript"></script>
+  
+  <link rel="stylesheet" href="assets/unittest.css" type="text/css" />
+</head>
+<body>
+
+<div id="content">
+
+  <div id="header">
+    <h1>AMQ tests</h1>
+    <p>
+      This file tests amq.js.
+    </p>
+  </div>
+
+  <!-- Log output (one per Runner, via {testLog: "testlog"} option)-->
+  <div id="testlog"></div>
+  
+  <!-- Put sample/test html here -->
+  <div id="sample">
+  </div>
+</div>
+
+<script type="text/javascript">
+  function createXmlFromString( xmlString ) {
+    // http://developer.taboca.com/cases/en/client-javascript-dom-parser/
+    // Mozilla and Netscape browsers
+    if (document.implementation.createDocument) {
+      var parser = new DOMParser()
+      response = parser.parseFromString( xmlString, "text/xml")
+    // MSIE
+    } else if (window.ActiveXObject) {
+      response = new ActiveXObject("Microsoft.XMLDOM")
+      response.async="false"
+      response.loadXML( xmlString )
+    }
+    return response;
+  }
+  
+// <![CDATA[
+  new Test.Unit.Runner({
+    setup: function() {
+      org.activemq.AmqAdapter.reset();
+      org.activemq.Amq.init({ uri: '../amq', timeout: 30 });
+    },
+    
+    teardown: function() {
+      org.activemq.Amq.endBatch();
+    },
+    
+    testMessagesAreSentToUrlDefinedInInit: function() { with( this ) {
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 2, requests.length );
+      assertEqual( '../amq', requests[ 0 ].uri );
+      assertEqual( '../amq', requests[ 1 ].uri );
+    }},
+    
+    testFirstMessageIsAPoll: function() { with( this ) {
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      var requests = org.activemq.AmqAdapter.getRequests();
+      
+      assertEqual( 'get', requests[ 0 ].options.method );
+      assert( requests[ 0 ].options.data.match( /timeout=30000&d=\d+&r=[\d.]+/ ) );
+    }},
+    
+    testPostIsSentIfNoBatchIsInProgress: function() { with( this ) {
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      var requests = org.activemq.AmqAdapter.getRequests();
+      
+      assertEqual( 2, requests.length );
+      assertEqual( 'post', requests[ 1 ].options.method );
+      assertEqual( 'destination=queue://test&message=<message>test</message>&type=send', requests[ 1 ].options.data );
+    }},
+    
+    testMessagesAreDeliveredInABatchIfAjaxRequestIsInProgressWhenSendMessageIsCalled: function() { with( this ) {
+      // use startBatch to indicate a previous message POST is currently in progress.
+      org.activemq.Amq.startBatch();
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.sendMessage( 'queue://test2', '<message>test2</message>' );
+      // endBatch is the callback once the previous POST finishes.  Triggers delivery of queued messages.
+      org.activemq.Amq.endBatch();
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 2, requests.length );
+      assertEqual( 'post', requests[1].options.method );
+      assertEqual( "destination=queue://test&message=<message>test</message>&type=send&d1=queue://test2&m1=<message>test2</message>&t1=send", requests[1].options.data );
+    }},
+    
+    testAddListenerSendsListenMessage: function() { with( this ) {
+      org.activemq.Amq.addListener( 'client_id', 'topic://test', function(){} );
+      var requests = org.activemq.AmqAdapter.getRequests();
+      
+      assertEqual( 2, requests.length );
+      assertEqual( 'post', requests[1].options.method );
+      assertEqual( "destination=topic://test&message=client_id&type=listen", requests[1].options.data );
+    }},
+    
+    testAddListenerMayIncludeASelector: function() { with( this ) {
+      org.activemq.Amq.addListener( 'client_id', 'topic://test', function(){}, {selector:"identifier='ALPHA'"} );
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 2, requests.length );
+      assertEqual( 'post', requests[1].options.method );
+      assertEqual( "destination=topic://test&message=client_id&type=listen", requests[1].options.data );
+      assertHashEqual( { selector: "identifier='ALPHA'" }, requests[1].options.headers );
+    }},
+    
+    testAllQueuedMessagesContainingHeadersAreDeliveredIndividuallyToPreventHeaderConflicts: function() { with( this ) {
+      org.activemq.Amq.startBatch();
+      org.activemq.Amq.addListener( 'client_id_1', 'topic://test1', function(){}, {selector:"identifier='ALPHA'"} );
+      org.activemq.Amq.addListener( 'client_id_2', 'topic://test2', function(){}, {selector:"identifier='BRAVO'"} );
+      
+      // simulate 1st post returning, which triggers 2nd post.
+      org.activemq.Amq.endBatch();
+      
+      // poll & first listen have been sent.
+      assertEqual( 2, org.activemq.AmqAdapter.getRequests().length );
+      
+      // second listen is still in the queue.
+      var queued = org.activemq.Amq.getMessageQueue();
+      assertEqual( 1, queued.length );
+      assertHashEqual( { selector: "identifier='BRAVO'" }, queued[ 0 ].headers );
+      assertHashEqual( { destination: 'topic://test2', message: 'client_id_2', messageType: 'listen' }, queued[ 0 ].message );
+      
+      // when first post returns, the second listen gets sent.
+      // this endBatch simulates that second post returning.
+      org.activemq.Amq.endBatch();
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      var queued = org.activemq.Amq.getMessageQueue();
+      
+      assertEqual( 3, requests.length );
+      assertEqual( 'post', requests[1].options.method );
+      assertEqual( "destination=topic://test1&message=client_id_1&type=listen", requests[1].options.data );
+      assertHashEqual( { selector: "identifier='ALPHA'" }, requests[1].options.headers );
+      
+      assertEqual( 'post', requests[2].options.method );
+      assertEqual( "destination=topic://test2&message=client_id_2&type=listen", requests[2].options.data );
+      assertHashEqual( { selector: "identifier='BRAVO'" }, requests[2].options.headers );
+      
+      assertEqual( 0, queued.length );
+    }},
+    
+    // Is this desired behavior?  Message order changes from that specified by the client.
+    testQueuedMessagesWithoutHeadersAreDeliveredInASinglePost: function() { with( this ) {
+      org.activemq.Amq.startBatch();
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.addListener( 'client_id_1', 'topic://test1', function(){}, {selector:"identifier='ALPHA'"} );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.addListener( 'client_id_2', 'topic://test2', function(){}, {selector:"identifier='BRAVO'"} );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      
+      // poll & all sendMessage calls go out first.
+      org.activemq.Amq.endBatch();
+      assertEqual( 2, org.activemq.AmqAdapter.getRequests().length );
+      assertEqual( 2, org.activemq.Amq.getMessageQueue().length );
+      
+      // first listen goes next.
+      org.activemq.Amq.endBatch();
+      assertEqual( 3, org.activemq.AmqAdapter.getRequests().length );
+      assertEqual( 1, org.activemq.Amq.getMessageQueue().length );
+      
+      // final listen goes out.
+      org.activemq.Amq.endBatch();
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 4, requests.length );
+      assertEqual( 0, org.activemq.Amq.getMessageQueue().length );
+      
+      assertEqual( "destination=queue://test&message=<message>test</message>&type=send&d1=queue://test&m1=<message>test</message>&t1=send&d2=queue://test&m2=<message>test</message>&t2=send", requests[ 1 ].options.data );
+      assertHashEqual( {}, requests[ 1 ].options.headers );
+      
+      assertEqual( "destination=topic://test1&message=client_id_1&type=listen", requests[ 2 ].options.data );
+      assertEqual( "destination=topic://test2&message=client_id_2&type=listen", requests[ 3 ].options.data );
+      
+    }},
+    
+    testSelectorFromQueuedListenerIsNotAddedToLaterMessages: function() { with( this ) {
+      org.activemq.Amq.startBatch();
+      org.activemq.Amq.addListener( 'client_id_1', 'topic://test1', function(){}, {selector:"identifier='ALPHA'"} );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      
+      // poll & listener go out first.
+      org.activemq.Amq.endBatch();
+      assertEqual( 2, org.activemq.AmqAdapter.getRequests().length );
+      assertEqual( 2, org.activemq.Amq.getMessageQueue().length );
+      
+      // 2 sendMessages go next.
+      org.activemq.Amq.endBatch();
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 3, requests.length );
+      assertEqual( 0, org.activemq.Amq.getMessageQueue().length );
+      
+      assertEqual( "destination=topic://test1&message=client_id_1&type=listen", requests[ 1 ].options.data );
+      assertHashEqual( { selector: "identifier='ALPHA'" }, requests[ 1 ].options.headers );
+      assertEqual( "destination=queue://test&message=<message>test</message>&type=send&d1=queue://test&m1=<message>test</message>&t1=send", requests[ 2 ].options.data );
+      assertHashEqual( {}, requests[ 2 ].options.headers );
+    }},
+    
+    testAddListenerWithoutSelectorWillBeBatchedWithOtherMessages: function() { with( this ) {
+      org.activemq.Amq.startBatch();
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.addListener( 'client_id_1', 'topic://test1', function(){} );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      org.activemq.Amq.addListener( 'client_id_2', 'topic://test2', function(){} );
+      org.activemq.Amq.sendMessage( 'queue://test', '<message>test</message>' );
+      
+      org.activemq.Amq.endBatch();
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 2, requests.length );
+      assertEqual( 0, org.activemq.Amq.getMessageQueue().length );
+      assertEqual( "destination=queue://test&message=<message>test</message>&type=send&d1=topic://test1&m1=client_id_1&t1=listen&d2=queue://test&m2=<message>test</message>&t2=send&d3=topic://test2&m3=client_id_2&t3=listen&d4=queue://test&m4=<message>test</message>&t4=send", requests[ 1 ].options.data );
+    }},
+    
+    testRemoveListenerSendsUnlistenMessage: function() { with( this ) {
+      org.activemq.Amq.removeListener( 'client_id', 'topic://test' );
+      
+      var requests = org.activemq.AmqAdapter.getRequests();
+      assertEqual( 2, requests.length );
+      assertEqual( 'post', requests[1].options.method );
+      assertEqual( "destination=topic://test&message=client_id&type=unlisten", requests[1].options.data );
+    }},
+    
+    testAddListenerCallbackIsCalledForReceivedMessages: function() { with( this ) {
+      // build an XML document like the one which the ajax implementers would pass to pollHandler
+      response = createXmlFromString( '<ajax-response><response id="client_id" destination="queue://test" >test message</response></ajax-response>' );
+      
+      // we'll expect the callback to set this value
+      var callbackValue;
+      
+      org.activemq.Amq.addListener( 'client_id', 'queue://test', function( msg ) { callbackValue = msg; } );
+      org.activemq.Amq.testPollHandler( response );
+      
+      assertEqual( 'test message', callbackValue.textContent );
+    }}
+    
+  }); 
+// ]]>
+</script>
+</body>
+</html>

Added: activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/README
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/README?rev=997779&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/README (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/README Thu Sep 16 15:05:32 2010
@@ -0,0 +1,4 @@
+jsunittest.js from http://jsunittest.com/
+
+This directory is for core jsunittest files only! Put your unit test .html files up one level.
+You should be referencing files in public/javascripts/ in your test files. 

Added: activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/jsunittest.js
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/jsunittest.js?rev=997779&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/jsunittest.js (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/jsunittest.js Thu Sep 16 15:05:32 2010
@@ -0,0 +1,1017 @@
+/*  Jsunittest, version 0.7.3
+ *  (c) 2008 Dr Nic Williams
+ *
+ *  Jsunittest is freely distributable under
+ *  the terms of an MIT-style license.
+ *  For details, see the web site: http://jsunittest.rubyforge.org
+ *
+ *--------------------------------------------------------------------------*/
+
+var JsUnitTest = {
+  Unit: {},
+  inspect: function(object) {
+    try {
+      if (typeof object == "undefined") return 'undefined';
+      if (object === null) return 'null';
+      if (typeof object == "string") {
+        var useDoubleQuotes = arguments[1];
+        var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) {
+          var character = String.specialChar[match[0]];
+          return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+        });
+        if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+        return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+      };
+      return String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+  $: function(element) {
+    if (arguments.length > 1) {
+      for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+        elements.push(this.$(arguments[i]));
+      return elements;
+    }
+    if (typeof element == "string")
+      element = document.getElementById(element);
+    return element;
+  },
+  gsub: function(source, pattern, replacement) {
+    var result = '', match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += JsUnitTest.String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+  scan: function(source, pattern, iterator) {
+    this.gsub(source, pattern, iterator);
+    return String(source);
+  },
+  escapeHTML: function(data) {
+    return data.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  arrayfromargs: function(args) {
+  	var myarray = new Array();
+  	var i;
+
+  	for (i=0;i<args.length;i++)
+  		myarray[i] = args[i];
+
+  	return myarray;
+  },
+  hashToSortedArray: function(hash) {
+    var results = [];
+    for (key in hash) {
+      results.push([key, hash[key]]);
+    }
+    return results.sort();
+  },
+  flattenArray: function(array) {
+    var results = arguments[1] || [];
+    for (var i=0; i < array.length; i++) {
+      var object = array[i];
+      if (object != null && typeof object == "object" &&
+        'splice' in object && 'join' in object) {
+          this.flattenArray(object, results);
+      } else {
+        results.push(object);
+      }
+    };
+    return results;
+  },
+  selectorMatch: function(expression, element) {
+    var tokens = [];
+    var patterns = {
+      // combinators must be listed first
+      // (and descendant needs to be last combinator)
+      laterSibling: /^\s*~\s*/,
+      child:        /^\s*>\s*/,
+      adjacent:     /^\s*\+\s*/,
+      descendant:   /^\s/,
+
+      // selectors follow
+      tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+      id:           /^#([\w\-\*]+)(\b|$)/,
+      className:    /^\.([\w\-\*]+)(\b|$)/,
+      pseudo:
+  /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+      attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
+      attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+    };
+
+    var assertions = {
+      tagName: function(element, matches) {
+        return matches[1].toUpperCase() == element.tagName.toUpperCase();
+      },
+
+      className: function(element, matches) {
+        return Element.hasClassName(element, matches[1]);
+      },
+
+      id: function(element, matches) {
+        return element.id === matches[1];
+      },
+
+      attrPresence: function(element, matches) {
+        return Element.hasAttribute(element, matches[1]);
+      },
+
+      attr: function(element, matches) {
+        var nodeValue = Element.readAttribute(element, matches[1]);
+        return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]);
+      }
+    };
+    var e = this.expression, ps = patterns, as = assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+  toQueryParams: function(query, separator) {
+    var query = query || window.location.search;
+    var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    var hash = {};
+    var parts = match[1].split(separator || '&');
+    for (var i=0; i < parts.length; i++) {
+      var pair = parts[i].split('=');
+      if (pair[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          var object = hash[key];
+          var isArray = object != null && typeof object == "object" &&
+            'splice' in object && 'join' in object
+          if (!isArray) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+    };
+    return hash;
+  },
+
+  String: {
+    interpret: function(value) {
+      return value == null ? '' : String(value);
+    }
+  }
+};
+
+JsUnitTest.gsub.prepareReplacement = function(replacement) {
+  if (typeof replacement == "function") return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+JsUnitTest.Version = '0.7.3';
+
+JsUnitTest.Template = function(template, pattern) {
+  this.template = template; //template.toString();
+  this.pattern = pattern || JsUnitTest.Template.Pattern;
+};
+
+JsUnitTest.Template.prototype.evaluate = function(object) {
+  if (typeof object.toTemplateReplacements == "function")
+    object = object.toTemplateReplacements();
+
+  return JsUnitTest.gsub(this.template, this.pattern, function(match) {
+    if (object == null) return '';
+
+    var before = match[1] || '';
+    if (before == '\\') return match[2];
+
+    var ctx = object, expr = match[3];
+    var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+    match = pattern.exec(expr);
+    if (match == null) return before;
+
+    while (match != null) {
+      var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1];
+      ctx = ctx[comp];
+      if (null == ctx || '' == match[3]) break;
+      expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+      match = pattern.exec(expr);
+    }
+
+    return before + JsUnitTest.String.interpret(ctx);
+  });
+}
+
+JsUnitTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+JsUnitTest.Event = {};
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel, Matthias Miller, Diego Perini
+// namespaced by Dr Nic Williams 2008
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+// http://dean.edwards.name/weblog/2005/10/add-event2/
+JsUnitTest.Event.addEvent = function(element, type, handler) {
+	if (element.addEventListener) {
+		element.addEventListener(type, handler, false);
+	} else {
+		// assign each event handler a unique ID
+		if (!handler.$$guid) handler.$$guid = JsUnitTest.Event.addEvent.guid++;
+		// create a hash table of event types for the element
+		if (!element.events) element.events = {};
+		// create a hash table of event handlers for each element/event pair
+		var handlers = element.events[type];
+		if (!handlers) {
+			handlers = element.events[type] = {};
+			// store the existing event handler (if there is one)
+			if (element["on" + type]) {
+				handlers[0] = element["on" + type];
+			}
+		}
+		// store the event handler in the hash table
+		handlers[handler.$$guid] = handler;
+		// assign a global event handler to do all the work
+		element["on" + type] = this.handleEvent;
+	}
+};
+// a counter used to create unique IDs
+JsUnitTest.Event.addEvent.guid = 1;
+
+JsUnitTest.Event.removeEvent = function(element, type, handler) {
+	if (element.removeEventListener) {
+		element.removeEventListener(type, handler, false);
+	} else {
+		// delete the event handler from the hash table
+		if (element.events && element.events[type]) {
+			delete element.events[type][handler.$$guid];
+		}
+	}
+};
+
+JsUnitTest.Event.handleEvent = function(event) {
+	var returnValue = true;
+	// grab the event object (IE uses a global event object)
+	event = event || JsUnitTest.Event.fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
+	// get a reference to the hash table of event handlers
+	var handlers = this.events[event.type];
+	// execute each event handler
+	for (var i in handlers) {
+		this.$$handleEvent = handlers[i];
+		if (this.$$handleEvent(event) === false) {
+			returnValue = false;
+		}
+	}
+	return returnValue;
+};
+
+JsUnitTest.Event.fixEvent = function(event) {
+	// add W3C standard event methods
+	event.preventDefault = this.fixEvent.preventDefault;
+	event.stopPropagation = this.fixEvent.stopPropagation;
+	return event;
+};
+JsUnitTest.Event.fixEvent.preventDefault = function() {
+	this.returnValue = false;
+};
+JsUnitTest.Event.fixEvent.stopPropagation = function() {
+	this.cancelBubble = true;
+};
+
+JsUnitTest.Unit.Logger = function(element) {
+  this.element = JsUnitTest.$(element);
+  if (this.element) this._createLogTable();
+};
+
+JsUnitTest.Unit.Logger.prototype.start = function(testName) {
+  if (!this.element) return;
+  var tbody = this.element.getElementsByTagName('tbody')[0];
+
+  var tr = document.createElement('tr');
+  var td;
+
+  //testname
+  td = document.createElement('td');
+  td.appendChild(document.createTextNode(testName));
+  tr.appendChild(td)
+
+  tr.appendChild(document.createElement('td'));//status
+  tr.appendChild(document.createElement('td'));//message
+
+  tbody.appendChild(tr);
+};
+
+JsUnitTest.Unit.Logger.prototype.setStatus = function(status) {
+  var logline = this.getLastLogLine();
+  logline.className = status;
+  var statusCell = logline.getElementsByTagName('td')[1];
+  statusCell.appendChild(document.createTextNode(status));
+};
+
+JsUnitTest.Unit.Logger.prototype.finish = function(status, summary) {
+  if (!this.element) return;
+  this.setStatus(status);
+  this.message(summary);
+};
+
+JsUnitTest.Unit.Logger.prototype.message = function(message) {
+  if (!this.element) return;
+  var cell = this.getMessageCell();
+
+  // cell.appendChild(document.createTextNode(this._toHTML(message)));
+  cell.innerHTML = this._toHTML(message);
+};
+
+JsUnitTest.Unit.Logger.prototype.summary = function(summary) {
+  if (!this.element) return;
+  var div = this.element.getElementsByTagName('div')[0];
+  div.innerHTML = this._toHTML(summary);
+};
+
+JsUnitTest.Unit.Logger.prototype.getLastLogLine = function() {
+  var tbody = this.element.getElementsByTagName('tbody')[0];
+  var loglines = tbody.getElementsByTagName('tr');
+  return loglines[loglines.length - 1];
+};
+
+JsUnitTest.Unit.Logger.prototype.getMessageCell = function() {
+  var logline = this.getLastLogLine();
+  return logline.getElementsByTagName('td')[2];
+};
+
+JsUnitTest.Unit.Logger.prototype._createLogTable = function() {
+  var html = '<div class="logsummary">running...</div>' +
+  '<table class="logtable">' +
+  '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+  '<tbody class="loglines"></tbody>' +
+  '</table>';
+  this.element.innerHTML = html;
+};
+
+JsUnitTest.Unit.Logger.prototype.appendActionButtons = function(actions) {
+  // actions = $H(actions);
+  // if (!actions.any()) return;
+  // var div = new Element("div", {className: 'action_buttons'});
+  // actions.inject(div, function(container, action) {
+  //   var button = new Element("input").setValue(action.key).observe("click", action.value);
+  //   button.type = "button";
+  //   return container.insert(button);
+  // });
+  // this.getMessageCell().insert(div);
+};
+
+JsUnitTest.Unit.Logger.prototype._toHTML = function(txt) {
+  return JsUnitTest.escapeHTML(txt).replace(/\n/g,"<br/>");
+};
+JsUnitTest.Unit.MessageTemplate = function(string) {
+  var parts = [];
+  var str = JsUnitTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
+    parts.push(part[0]);
+  });
+  this.parts = parts;
+};
+
+JsUnitTest.Unit.MessageTemplate.prototype.evaluate = function(params) {
+  var results = [];
+  for (var i=0; i < this.parts.length; i++) {
+    var part = this.parts[i];
+    var result = (part == '?') ? JsUnitTest.inspect(params.shift()) : part.replace(/\\\?/, '?');
+    results.push(result);
+  };
+  return results.join('');
+};
+// A generic function for performming AJAX requests
+// It takes one argument, which is an object that contains a set of options
+// All of which are outline in the comments, below
+// From John Resig's book Pro JavaScript Techniques
+// published by Apress, 2006-8
+JsUnitTest.ajax = function( options ) {
+
+    // Load the options object with defaults, if no
+    // values were provided by the user
+    options = {
+        // The type of HTTP Request
+        type: options.type || "POST",
+
+        // The URL the request will be made to
+        url: options.url || "",
+
+        // How long to wait before considering the request to be a timeout
+        timeout: options.timeout || 5000,
+
+        // Functions to call when the request fails, succeeds,
+        // or completes (either fail or succeed)
+        onComplete: options.onComplete || function(){},
+        onError: options.onError || function(){},
+        onSuccess: options.onSuccess || function(){},
+
+        // The data type that'll be returned from the server
+        // the default is simply to determine what data was returned from the
+        // and act accordingly.
+        data: options.data || ""
+    };
+
+    // Create the request object
+    var xml = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
+
+    // Open the asynchronous POST request
+    xml.open(options.type, options.url, true);
+
+    // We're going to wait for a request for 5 seconds, before giving up
+    var timeoutLength = 5000;
+
+    // Keep track of when the request has been succesfully completed
+    var requestDone = false;
+
+    // Initalize a callback which will fire 5 seconds from now, cancelling
+    // the request (if it has not already occurred).
+    setTimeout(function(){
+         requestDone = true;
+    }, timeoutLength);
+
+    // Watch for when the state of the document gets updated
+    xml.onreadystatechange = function(){
+        // Wait until the data is fully loaded,
+        // and make sure that the request hasn't already timed out
+        if ( xml.readyState == 4 && !requestDone ) {
+
+            // Check to see if the request was successful
+            if ( httpSuccess( xml ) ) {
+
+                // Execute the success callback with the data returned from the server
+                options.onSuccess( httpData( xml, options.type ) );
+
+            // Otherwise, an error occurred, so execute the error callback
+            } else {
+                options.onError();
+            }
+
+            // Call the completion callback
+            options.onComplete();
+
+            // Clean up after ourselves, to avoid memory leaks
+            xml = null;
+        }
+    };
+
+    // Establish the connection to the server
+    xml.send(null);
+
+    // Determine the success of the HTTP response
+    function httpSuccess(r) {
+        try {
+            // If no server status is provided, and we're actually
+            // requesting a local file, then it was successful
+            return !r.status && location.protocol == "file:" ||
+
+                // Any status in the 200 range is good
+                ( r.status >= 200 && r.status < 300 ) ||
+
+                // Successful if the document has not been modified
+                r.status == 304 ||
+
+                // Safari returns an empty status if the file has not been modified
+                navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined";
+        } catch(e){}
+
+        // If checking the status failed, then assume that the request failed too
+        return false;
+    }
+
+    // Extract the correct data from the HTTP response
+    function httpData(r,type) {
+        // Get the content-type header
+        var ct = r.getResponseHeader("content-type");
+
+        // If no default type was provided, determine if some
+        // form of XML was returned from the server
+        var data = !type && ct && ct.indexOf("xml") >= 0;
+
+        // Get the XML Document object if XML was returned from
+        // the server, otherwise return the text contents returned by the server
+        data = type == "xml" || data ? r.responseXML : r.responseText;
+
+        // If the specified type is "script", execute the returned text
+        // response as if it was JavaScript
+        if ( type == "script" )
+            eval.call( window, data );
+
+        // Return the response data (either an XML Document or a text string)
+        return data;
+    }
+
+};
+JsUnitTest.Unit.Assertions = {
+  buildMessage: function(message, template) {
+    var args = JsUnitTest.arrayfromargs(arguments).slice(2);
+    return (message ? message + '\n' : '') +
+      new JsUnitTest.Unit.MessageTemplate(template).evaluate(args);
+  },
+
+  flunk: function(message) {
+    this.assertBlock(message || 'Flunked', function() { return false });
+  },
+
+  assertBlock: function(message, block) {
+    try {
+      block.call(this) ? this.pass() : this.fail(message);
+    } catch(e) { this.error(e) }
+  },
+
+  assert: function(expression, message) {
+    message = this.buildMessage(message || 'assert', 'got <?>', expression);
+    this.assertBlock(message, function() { return expression });
+  },
+
+  assertEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
+    this.assertBlock(message, function() { return expected == actual });
+  },
+
+  assertNotEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
+    this.assertBlock(message, function() { return expected != actual });
+  },
+
+  assertEnumEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
+    var expected_array = JsUnitTest.flattenArray(expected);
+    var actual_array   = JsUnitTest.flattenArray(actual);
+    this.assertBlock(message, function() {
+      if (expected_array.length == actual_array.length) {
+        for (var i=0; i < expected_array.length; i++) {
+          if (expected_array[i] != actual_array[i]) return false;
+        };
+        return true;
+      }
+      return false;
+    });
+  },
+
+  assertEnumNotEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual);
+    var expected_array = JsUnitTest.flattenArray(expected);
+    var actual_array   = JsUnitTest.flattenArray(actual);
+    this.assertBlock(message, function() {
+      if (expected_array.length == actual_array.length) {
+        for (var i=0; i < expected_array.length; i++) {
+          if (expected_array[i] != actual_array[i]) return true;
+        };
+        return false;
+      }
+      return true;
+    });
+  },
+
+  assertHashEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual);
+    var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected));
+    var actual_array   = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual));
+    var block = function() {
+      if (expected_array.length == actual_array.length) {
+        for (var i=0; i < expected_array.length; i++) {
+          if (expected_array[i] != actual_array[i]) return false;
+        };
+        return true;
+      }
+      return false;
+    };
+    this.assertBlock(message, block);
+  },
+
+  assertHashNotEqual: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual);
+    var expected_array = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(expected));
+    var actual_array   = JsUnitTest.flattenArray(JsUnitTest.hashToSortedArray(actual));
+    // from now we recursively zip & compare nested arrays
+    var block = function() {
+      if (expected_array.length == actual_array.length) {
+        for (var i=0; i < expected_array.length; i++) {
+          if (expected_array[i] != actual_array[i]) return true;
+        };
+        return false;
+      }
+      return true;
+    };
+    this.assertBlock(message, block);
+  },
+
+  assertIdentical: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
+    this.assertBlock(message, function() { return expected === actual });
+  },
+
+  assertNotIdentical: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
+    this.assertBlock(message, function() { return expected !== actual });
+  },
+
+  assertNull: function(obj, message) {
+    message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
+    this.assertBlock(message, function() { return obj === null });
+  },
+
+  assertNotNull: function(obj, message) {
+    message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
+    this.assertBlock(message, function() { return obj !== null });
+  },
+
+  assertUndefined: function(obj, message) {
+    message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
+    this.assertBlock(message, function() { return typeof obj == "undefined" });
+  },
+
+  assertNotUndefined: function(obj, message) {
+    message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
+    this.assertBlock(message, function() { return typeof obj != "undefined" });
+  },
+
+  assertNullOrUndefined: function(obj, message) {
+    message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
+    this.assertBlock(message, function() { return obj == null });
+  },
+
+  assertNotNullOrUndefined: function(obj, message) {
+    message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
+    this.assertBlock(message, function() { return obj != null });
+  },
+
+  assertMatch: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
+    this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
+  },
+
+  assertNoMatch: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
+    this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
+  },
+
+  assertHasClass: function(element, klass, message) {
+    element = JsUnitTest.$(element);
+    message = this.buildMessage(message || 'assertHasClass', '? doesn\'t have class <?>.', element, klass);
+    this.assertBlock(message, function() {
+      var elementClassName = element.className;
+      return (elementClassName.length > 0 && (elementClassName == klass ||
+        new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName)));
+      // return !!element.className.match(new RegExp(klass))
+    });
+  },
+
+  assertNotHasClass: function(element, klass, message) {
+    element = JsUnitTest.$(element);
+    message = this.buildMessage(message || 'assertNotHasClass', '? does have class <?>.', element, klass);
+    this.assertBlock(message, function() {
+      var elementClassName = element.className;
+      return !(elementClassName.length > 0 && (elementClassName == klass ||
+        new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName)));
+    });
+  },
+
+  assertHidden: function(element, message) {
+    element = JsUnitTest.$(element);
+    message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
+    this.assertBlock(message, function() { return !element.style.display || element.style.display == 'none' });
+  },
+
+  assertInstanceOf: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
+    this.assertBlock(message, function() { return actual instanceof expected });
+  },
+
+  assertNotInstanceOf: function(expected, actual, message) {
+    message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
+    this.assertBlock(message, function() { return !(actual instanceof expected) });
+  },
+
+  assertRespondsTo: function(method, obj, message) {
+    message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
+    this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
+  },
+
+  assertRaise: function(exceptionName, method, message) {
+    message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName);
+    var block = function() {
+      try {
+        method();
+        return false;
+      } catch(e) {
+        if (e.name == exceptionName) return true;
+        else throw e;
+      }
+    };
+    this.assertBlock(message, block);
+  },
+
+  assertNothingRaised: function(method, message) {
+    try {
+      method();
+      this.assert(true, "Expected nothing to be thrown");
+    } catch(e) {
+      message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
+      this.flunk(message);
+    }
+  },
+
+  _isVisible: function(element) {
+    element = JsUnitTest.$(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && (element.style.display == 'none'))
+      return false;
+
+    return arguments.callee.call(this, element.parentNode);
+  },
+
+  assertVisible: function(element, message) {
+    message = this.buildMessage(message, '? was not visible.', element);
+    this.assertBlock(message, function() { return this._isVisible(element) });
+  },
+
+  assertNotVisible: function(element, message) {
+    message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
+    this.assertBlock(message, function() { return !this._isVisible(element) });
+  },
+
+  assertElementsMatch: function() {
+    var pass = true, expressions = JsUnitTest.arrayfromargs(arguments);
+    var elements = expressions.shift();
+    if (elements.length != expressions.length) {
+      message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
+      this.flunk(message);
+      pass = false;
+    }
+    for (var i=0; i < expressions.length; i++) {
+      var expression = expressions[i];
+      var element    = JsUnitTest.$(elements[i]);
+      if (JsUnitTest.selectorMatch(expression, element)) {
+        pass = true;
+        break;
+      }
+      message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element);
+      this.flunk(message);
+      pass = false;
+    };
+    this.assert(pass, "Expected all elements to match.");
+  },
+
+  assertElementMatches: function(element, expression, message) {
+    this.assertElementsMatch([element], expression);
+  }
+};
+JsUnitTest.Unit.Runner = function(testcases) {
+  var argumentOptions = arguments[1] || {};
+  var options = this.options = {};
+  options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog';
+  options.resultsURL = this.queryParams.resultsURL;
+  options.testLog = JsUnitTest.$(options.testLog);
+
+  this.tests = this.getTests(testcases);
+  this.currentTest = 0;
+  this.logger = new JsUnitTest.Unit.Logger(options.testLog);
+
+  var self = this;
+  JsUnitTest.Event.addEvent(window, "load", function() {
+    setTimeout(function() {
+      self.runTests();
+    }, 0.1);
+  });
+};
+
+JsUnitTest.Unit.Runner.prototype.queryParams = JsUnitTest.toQueryParams();
+
+JsUnitTest.Unit.Runner.prototype.portNumber = function() {
+  if (window.location.search.length > 0) {
+    var matches = window.location.search.match(/\:(\d{3,5})\//);
+    if (matches) {
+      return parseInt(matches[1]);
+    }
+  }
+  return null;
+};
+
+JsUnitTest.Unit.Runner.prototype.getTests = function(testcases) {
+  var tests = [], options = this.options;
+  if (this.queryParams.tests) tests = this.queryParams.tests.split(',');
+  else if (options.tests) tests = options.tests;
+  else if (options.test) tests = [option.test];
+  else {
+    for (testname in testcases) {
+      if (testname.match(/^test/)) tests.push(testname);
+    }
+  }
+  var results = [];
+  for (var i=0; i < tests.length; i++) {
+    var test = tests[i];
+    if (testcases[test])
+      results.push(
+        new JsUnitTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown)
+      );
+  };
+  return results;
+};
+
+JsUnitTest.Unit.Runner.prototype.getResult = function() {
+  var results = {
+    tests: this.tests.length,
+    assertions: 0,
+    failures: 0,
+    errors: 0,
+    warnings: 0
+  };
+
+  for (var i=0; i < this.tests.length; i++) {
+    var test = this.tests[i];
+    results.assertions += test.assertions;
+    results.failures   += test.failures;
+    results.errors     += test.errors;
+    results.warnings   += test.warnings;
+  };
+  return results;
+};
+
+JsUnitTest.Unit.Runner.prototype.postResults = function() {
+  if (this.options.resultsURL) {
+    // new Ajax.Request(this.options.resultsURL,
+    //   { method: 'get', parameters: this.getResult(), asynchronous: false });
+    var results = this.getResult();
+    var url = this.options.resultsURL + "?";
+    url += "tests="+ this.tests.length + "&";
+    url += "assertions="+ results.assertions + "&";
+    url += "warnings="  + results.warnings + "&";
+    url += "failures="  + results.failures + "&";
+    url += "errors="    + results.errors;
+    JsUnitTest.ajax({
+      url: url,
+      type: 'GET'
+    })
+  }
+};
+
+JsUnitTest.Unit.Runner.prototype.runTests = function() {
+  var test = this.tests[this.currentTest], actions;
+
+  if (!test) return this.finish();
+  if (!test.isWaiting) this.logger.start(test.name);
+  test.run();
+  var self = this;
+  if(test.isWaiting) {
+    this.logger.message("Waiting for " + test.timeToWait + "ms");
+    // setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    setTimeout(function() {
+      self.runTests();
+    }, test.timeToWait || 1000);
+    return;
+  }
+
+  this.logger.finish(test.status(), test.summary());
+  if (actions = test.actions) this.logger.appendActionButtons(actions);
+  this.currentTest++;
+  // tail recursive, hopefully the browser will skip the stackframe
+  this.runTests();
+};
+
+JsUnitTest.Unit.Runner.prototype.finish = function() {
+  this.postResults();
+  this.logger.summary(this.summary());
+};
+
+JsUnitTest.Unit.Runner.prototype.summary = function() {
+  return new JsUnitTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings').evaluate(this.getResult());
+};
+JsUnitTest.Unit.Testcase = function(name, test, setup, teardown) {
+  this.name           = name;
+  this.test           = test     || function() {};
+  this.setup          = setup    || function() {};
+  this.teardown       = teardown || function() {};
+  this.messages       = [];
+  this.actions        = {};
+};
+// import JsUnitTest.Unit.Assertions
+
+for (method in JsUnitTest.Unit.Assertions) {
+  JsUnitTest.Unit.Testcase.prototype[method] = JsUnitTest.Unit.Assertions[method];
+}
+
+JsUnitTest.Unit.Testcase.prototype.isWaiting         = false;
+JsUnitTest.Unit.Testcase.prototype.timeToWait        = 1000;
+JsUnitTest.Unit.Testcase.prototype.assertions        = 0;
+JsUnitTest.Unit.Testcase.prototype.failures          = 0;
+JsUnitTest.Unit.Testcase.prototype.errors            = 0;
+JsUnitTest.Unit.Testcase.prototype.warnings          = 0;
+JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port;
+
+// JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711;
+
+JsUnitTest.Unit.Testcase.prototype.wait = function(time, nextPart) {
+  this.isWaiting = true;
+  this.test = nextPart;
+  this.timeToWait = time;
+};
+
+JsUnitTest.Unit.Testcase.prototype.run = function(rethrow) {
+  try {
+    try {
+      if (!this.isWaiting) this.setup();
+      this.isWaiting = false;
+      this.test();
+    } finally {
+      if(!this.isWaiting) {
+        this.teardown();
+      }
+    }
+  }
+  catch(e) {
+    if (rethrow) throw e;
+    this.error(e, this);
+  }
+};
+
+JsUnitTest.Unit.Testcase.prototype.summary = function() {
+  var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings\n';
+  return new JsUnitTest.Template(msg).evaluate(this) +
+    this.messages.join("\n");
+};
+
+JsUnitTest.Unit.Testcase.prototype.pass = function() {
+  this.assertions++;
+};
+
+JsUnitTest.Unit.Testcase.prototype.fail = function(message) {
+  this.failures++;
+  var line = "";
+  try {
+    throw new Error("stack");
+  } catch(e){
+    line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
+  }
+  this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
+};
+
+JsUnitTest.Unit.Testcase.prototype.warning = function(message) {
+  this.warnings++;
+  var line = "";
+  try {
+    throw new Error("stack");
+  } catch(e){
+    line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
+  }
+  this.messages.push("Warning: " + message + (line ? " Line #" + line : ""));
+};
+JsUnitTest.Unit.Testcase.prototype.warn = JsUnitTest.Unit.Testcase.prototype.warning;
+
+JsUnitTest.Unit.Testcase.prototype.info = function(message) {
+  this.messages.push("Info: " + message);
+};
+
+JsUnitTest.Unit.Testcase.prototype.error = function(error, test) {
+  this.errors++;
+  this.actions['retry with throw'] = function() { test.run(true) };
+  this.messages.push(error.name + ": "+ error.message + "(" + JsUnitTest.inspect(error) + ")");
+};
+
+JsUnitTest.Unit.Testcase.prototype.status = function() {
+  if (this.failures > 0) return 'failed';
+  if (this.errors > 0) return 'error';
+  if (this.warnings > 0) return 'warning';
+  return 'passed';
+};
+
+JsUnitTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) {
+  var startAt = new Date();
+  (iterations || 1).times(operation);
+  var timeTaken = ((new Date())-startAt);
+  this.info((arguments[2] || 'Operation') + ' finished ' +
+     iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+  return timeTaken;
+};
+
+Test = JsUnitTest

Added: activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/unittest.css
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/unittest.css?rev=997779&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/unittest.css (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/test/assets/unittest.css Thu Sep 16 15:05:32 2010
@@ -0,0 +1,54 @@
+body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
+  font-family: sans-serif;
+}
+
+body {
+  font-size:0.8em;
+}
+
+#log {
+  padding-bottom: 1em;
+  border-bottom: 2px solid #000;
+  margin-bottom: 2em;
+}
+
+.logsummary {
+  margin-top: 1em;
+  margin-bottom: 1em;
+  padding: 1ex;
+  border: 1px solid #000;
+  font-weight: bold;
+}
+
+.logtable {
+  width:100%;
+  border-collapse: collapse;
+  border: 1px dotted #666;
+}
+
+.logtable td, .logtable th {
+  text-align: left;
+  padding: 3px 8px;
+  border: 1px dotted #666;
+}
+
+.logtable .passed {
+  background-color: #cfc;
+}
+
+.logtable .failed, .logtable .error {
+  background-color: #fcc;
+}
+
+.logtable .warning {
+  background-color: #FC6;
+}
+
+.logtable td div.action_buttons {
+  display: inline;
+}
+
+.logtable td div.action_buttons input {
+  margin: 0 5px;
+  font-size: 10px;
+}